使用AngularJS制作一个简单的RSS阅读器的教程
简介
几年前,我用c#写了一个rss阅读器,但是我想如果把它做成一个spa(单页应用)效果会更好。 angular使一些事情变得简单,rss阅读器就是其中之一。 我也用twitter bootstrap(做ui)实现了rss阅读器,调试页面样式是最难的地方之一...可能是因为我不擅长css的原因。
背景
我有一些自己喜欢的网站( codeproject, dr.dobb's journal, computerworld, inc. magazine)。 然而,我发现其中很多网站都有烦人的广告、风格不好的布局,我实在不愿意看到这些东西。当我说这话的时候,并不包括 codeproject网站。
在这些网站之间来回切换浪费了很多时间。 因此我更喜欢浏览文章标题和简介,这样我可以决定是否进入文章内容页面。 这就是我决定写freedreadr 单页应用的原因。
freedreadr 响应是比较快的,因为它读取的数据量(rss源)比较小。
下面是点击codeproject选项的效果图:
下面是freedreadr 加载某一个站点数据的效果图:
你现在可以试下效果:
http://newtonsaber.com/freedreadr
差点忘了,我在创建自己的rss 阅读器之前在google上搜索了这个想法,发现jsfiddle中一段比较好的代码: angularjs feed reader alt.
我的代码和它的代码有相似的地方,但仍有不同,因为我想要实现更多的功能。 freedreadr 允许你本地存储自己的rss源数据,这样你就可以一直使用应用来创建自定义的rss源。 另外,它的代码基于twitter bootstrap 2,freedreadr 基于新版本twitter bootstrap 3。
使用代码
如果你熟悉angular,开发时代码并不多。 大部分的难点是在angular中使用bootstrap。
其它问题可以在”angular编程思想”中找到解决方法。$scope 的用法和控制器工作的方式有点不同。 首先你必须在html中设置应用程序的作用域。 类似下面的使用ng-app="freedreadr"的代码,设置了html中$scope的作用域:所有div标签内的对象 –- 在下面的示例中作用域是整个页面。 我只需要一个控制器来处理整个应用程序逻辑,我对这一点比较满意。
<body ng-app="freedreadr"> <div data-ng-controller="feedctrl"> <h4>rss feed reader using angularjs</h4> <form class="form-horizontal col-md-12" role="form"> <div class = "row"> <div class="col-md-6">
在上面的html代码中,你可以看到angular 应用模板的名称是freedreadr。 当我设置应用程序模板、添加控制器(feedctrl) 代码时,我在main.js文件中使用了相同的名字。让我们看一下main.js中设置参数的代码。
var app = angular.module('freedreadr', []); app.factory('feedservice',function($http){ return { parsefeed : function(url){ return $http.jsonp('//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=50&callback=json_callback&q=' + encodeuricomponent(url)); } } }); app.controller("feedctrl", ['$scope','feedservice', feedctrl]);
在上面的js代码中,第一行是创建angularjs 应用模板。 注意,它的名称是freedreadr,我们在html代码中使用相同的名称以引用这个模板。
接下来,我们创建一个angular工厂类,后面会用它访问rss源url来获取真实的源数据。 认真地看下代码中使用的 $http.jsonp请求。url格式如下:
//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=50&callback=json_callback&q=
url里调用了google api,以前我并不知道google api。这就是我在文章开头提到的jsfiddle示例的主要代码。 如果你想更多地了解google api,你可以在这里下载:https://developers.google.com/feed/v1/jsondevguide。
在上面的js代码中,你会发现我们调用了encodeuricomponent 函数,它是用来转换url的。
配置angular 控制器
控制器用来处理应用程序的逻辑,因此大部分js函数应该在控制器内部。这就是angular 帮助你组织(混乱的)js代码的方式。 我刚有大声说出来吗?
好吧,javascript鼓励杂乱无章,不是吗?这是它不好的地方.提到javascript的全局变量的时候,你不用带着恐惧尖声叫喊着说吗?如果不用,那么我们可能要撤销你的开发者许可证.你有编写软件的许可,不是吗?(译者注:因为你有开发者许可证,所以你自然也是带着恐惧尖声叫喊着说javascript的"全局变量"的--这里有些夸张的说法,意在说明开发者对于javascript的"全局变量"的使用应该有所顾忌.)
现在,在我们深入了解应用程序具体做了些什么的时候,看看控制器提供的功能.看看在控制器中的每个函数,以便了解freedreadr可以做些什么.
检查localstorage, 存在则加载
我想做的一件事是,希望你在浏览器的slocalstorage中保存你的源.我不希望因为设置数据库而弄得一团糟,但我希望你可以增加自定义的源,并且让他们在你的浏览器中一直可用.
localstorage的限制
当然,localstorage的限制是,它存在于一个特定浏览器的给定域名.
这意味着,如果你从newtonsaber.com/freedreadr运行应用程序并保存一些自定义的源,当你从浏览器打开最初打开的连接的时候,你将会再次只看到列表中的这些内容.每个浏览器的localstorage都是私有的.所以,如果你某天使用ie增加了一些源,在接下来的一天将无法使用chrome来查看已经添加的源.
对我来说,这个版本的应用程序已经ok了.因为它是我所希望的,用非常快速和了解其限制的技术开始.修正限制可能是以后另一篇文章的事情了.
你会发现在控制器代码中,我首先调用了一个函数:retrievefromlocalstorage()。
函数如下:
function retrievefromlocalstorage() { $scope.allfeeds = []; console.log("retrieving localstorage..."); try { $scope.allfeeds = json.parse(localstorage["feeds"]); console.log($scope.allfeeds.length); // console.log(json.stringify($scope.allfeeds)); if ($scope.allfeeds === null) { console.log("couldn't retrieve feeds" ); loaddefaultfeeds(); } } catch (ex) { console.log("ex: " + ex); loaddefaultfeeds(); savetolocalstorage($scope.allfeeds); } }
这是一个非常简单的函数。 它在 $scope的作用域内定义了一个名为 allfeeds的数组变量。 然后,通过 json.parse方法从localstorage数组中读取一个 feeds对象,这个对象保存的是已存在的 rss源。如果这个 feeds对象为 undefined(这是首次运行应用程序),程序会抛出一个异常。 当异常被抛出时,应用程序会加载一些默认的 rss源(loaddefaultfeeds()),然后将这些源保存到localstorage*下次使用。
先看看loaddefaultfeeds()函数,然后我们再看savetolocalstorage()函数.
function loaddefaultfeeds() { $scope.allfeeds = [{titletext:"load (from textbox)",url:""}, {titletext:"codeproject c#",url:"http://www.codeproject.com/webservices/articlerss.aspx?cat=3"}, {titletext:"computerworld - news",url:"http://www.computerworld.com/index.rss"}, {titletext:"dr. dobb's",url:"http://www.drdobbs.com/rss/all"}, {titletext:"infoworld today's news",url:"http://www.infoworld.com/news/feed"}, {titletext:"inc. magazine",url:"http://www.inc.com/rss/homepage.xml"}, {titletext:"techcrunch",url:"http://feeds.feedburner.com/techcrunch"}, {titletext:"cnn",url:"http://rss.cnn.com/rss/cnn_topstories.rss"} ]; }
就像你所看到的,我增加了一些我最喜欢的源,这样,你就可以很容易的试用这个应用程序.它仅仅只是一个对象数组,通过titletext和url来定义.
只要它们都加载到$scope类型的变量allfeeds之中,你就可以使用ng-repeat从html获取它们了,它看起来像下面这样:
<li ng-repeat="feed in allfeeds"> <a href="#" ng-click="loadfeed($event,feed.url);">{{feed.titletext}}</a> </li>
这创建了选项的列表,当你点击下拉框按钮的时候,它们将会显示在上面.如你所看到的,我们在ng-repeat语句中引用了allfeeds $scope变量,然后我们引用了feed.titletext来生成入口.
现在你的按钮已经加载了很好的标题了。
我说过会给你介绍savetolocalstorage()方法,现在让我们看一看。
function savetolocalstorage(feeds) { // put the object into storage localstorage.setitem('feeds', angular.tojson(feeds)); console.log(angular.tojson(feeds)); console.log("wrote feeds to localstorage"); }
这是一个非常简单的方法。方法允许你传入 feeds 对象(这应该是一个feed对象数组)。然后我们简单地调用 localstorage.setitem( ) 方法。正如方法的名字所说,我们可以用该方法来保存对象。要注意的是,当我们保存对象的时候,我们会调用对象的 angular.tojson() 方法。这是个方法会帮助我们去除一些angular特有的属性,而这些属性是我们不想保存的。所以调用这个方法非常重要,因为angular会在对象中保存一些特有属性,这些属性会让你感到迷惑。
现在,应用程序已经加载了一些默认的rss源,如果你想获取某一个rss源的数据,点击下拉框按钮,选择其中一个值,然后应用程序会运行下面的代码来获取相关的数据。 我们在$scope作用域内添加一个loadfeed函数,函数如下。 下面的函数会被调用,因为我们在html中给按钮绑定了事件ng-click="loadfeed($event,feed.url);"。
loadfeed=function(e,url){ $scope.currentbuttontext = angular.element(e.target).text(); // 清空过滤文本框中的上次展示的信息, // 当我们选择一个新的rss源时,如果不清空会让人疑惑 $scope.filtertext = ""; console.log("loadfeed / click event fired"); if ($scope.currentbuttontext == $scope.allfeeds[0].titletext) { //console.log($scope.feedsrc); url = $scope.feedsrc; } $scope.feedsrc = url; if (url === undefined || url === "") { $scope.phmessage = "please enter a valid feed url & try again."; return; } console.log("button text: " + angular.element(e.target).text()); console.log("value of url: " ); console.log(url); feedservice.parsefeed(url).then(function(res){ $scope.loadbutontext=angular.element(e.target).text(); $scope.feeds=res.data.responsedata.feed.entries; }); }
通过点击事件调用上面的函数时,我们先判断用户选择的信息与下拉框第一个选项 "load (from textbox)"是否相同。 这么做是为了判断用户是否想加载文本框中提供的 rss源。 如果是这样,我们调用feedservice.parsefeed 方法时,直接传入文本框中 url值。如果不是这样,我们从相关的源对象中获取 url。
结果列表
当源信息返回的时候,html代码使用另一个ng-repeat来迭代遍历每个项,并用友好的格式显示它们.html代码看起来像下面这样:
<ul class="unstyled"> <span class="badge badge-warning top-buffer" ng-show="feeds.length > 0">{{(feeds | filter:filtertext).length}} items</span> <li ng-repeat="feed in feeds | filter:filtertext"> <h5><a href="{{feed.link}}" target="_blank"><span class="titletext" >{{feed.title}}</span></a><span class="small"> {{feed.publisheddate}}</span></h5> <p class="text-left">{{feed.contentsnippet}}</p> </li> </ul>
html也创建了一个好友的标记,用来显示获取到的文本链接数.
搜索结果里的内容
它也显示了一个搜索文本框,可以用来输入文字,根据文本链接的内容来过滤下拉列表.
点击链接: 在新的页面加载
最后,如果你点击一个链接,它会在浏览器的新页面或窗口加载.这让这个工具很容易使用.
保存新的源
这个部分我做的很简单,因为我完成这个应用程序只是为了我自己使用.
如果你在文本框中输入一个url,然后点击保存按钮(向下的箭头图标),你将看到一个javascript提示框,它看起来像下面展示的图片这样:
你可以简单的输入标题(它将显示在下拉列表中)用来标识新的源,然后点击ok按钮.
只要你这么做了,这个项就会增加到下拉列表.这个过程通过增加这个项到allfeeds对象来实现,同时,这个项也将立即保存到localstorage之中.
这就是所有的内容.
希望你和我一样喜欢这个工具,它节省了很多时间.
发布提示
请注意许多外部库的链接是cdn的,除了下载的bootstrap文件,其他下载的文件在一个3rdpartylibs(第三方库)的目录里。你可以下载代码,解压和运行它们。
为本地存储准备web服务器是必需的
你必须运行一个web服务器使它正常工作。注意,在下载的文件中已经包含一个精致小巧的web服务器(mongoose web server)和配置文件,因此,如果你希望使用它,你可以双击mongoose.exe,它就开始运行了,之后,你可以简单地加载: http://localhost:999/freedreadr/ 它就从你的电脑上运行了。
最后一个要注意的是:那个【x】按钮是什么?
保存按钮后面的那个[x]按钮是什么?那个按钮允许你释放所有来自你的localstorage的反馈。如果你点击它,它们会被移除。被命名为反馈的localstorage条目就这样简单地被销毁了,所以要小心处理。如果你在添加任何反馈之前使用这个按钮,没有任何问题,否则你将会失去你已经添加的反馈。
我很懒,我使用它仅仅是为了我的测试,你还是应该把它去掉的。懒惰还真是一个伟大开发者的标志啊。