使用AngularJS製作一個簡單的RSS閱讀器的教程,angularjsrss
簡介
幾年前,我用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.
My Code和它的代碼有相似的地方,但仍有不同,因為我想要實現更多的功能。 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條目就這樣簡單地被銷毀了,所以要小心處理。如果你在添加任何反饋之前使用這個按鈕,沒有任何問題,否則你將會失去你已經添加的反饋。
我很懶,我使用它僅僅是為了我的測試,你還是應該把它去掉的。懶惰還真是一個偉大開發人員的標誌啊。