使用 AngularJS 創建的 RSS 閱讀器 —— FreedReadR
下載所有的源碼和mongoose web服務器 – 538.1 KB
簡介
幾年前,我用C#寫了一個RSS閱讀器,但是我想如果把它做成一個SPA(單頁應用)效果會更好。 Angular使一些事情變得簡單,RSS閱讀器就是其中之一。 我也用推ter 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源。 另外,它的代碼基于推ter Bootstrap 2,FreedReadR 基于新版本推ter 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條目就這樣簡單地被銷毀了,所以要小心處理。如果你在添加任何反饋之前使用這個按鈕,沒有任何問題,否則你將會失去你已經添加的反饋。
我很懶,我使用它僅僅是為了我的測試,你還是應該把它去掉的。懶惰還真是一個偉大開發者的標志啊。
有意思的點
坐在一個咖啡店,在我的Google Nexus 7上用這個藍牙鍵盤(iClever bluetooth @ amazon)寫了整篇文章的初稿。用 Dolphin Web Browser (v 11.2.0)(海豚瀏覽器)和 CodeProject Editor編輯器工作很不錯。這讓我印象深刻。