使用jQuery Mobile實(shí)現(xiàn)手機(jī)新聞瀏覽器(第二章)
在上一篇文章中,已經(jīng)討論了程序的結(jié)構(gòu)和頁(yè)面的布局,并簡(jiǎn)單介紹了一些jQuery Mobile的使用技巧。在本篇文章中,筆者將繼續(xù)完成我們web應(yīng)用的新聞瀏覽器的設(shè)計(jì)。
程序的啟動(dòng)
我們現(xiàn)在來(lái)研究一下程序的啟動(dòng)。當(dāng)程序啟動(dòng)的時(shí)候,展示給用戶的是新聞分類列表的頁(yè)面,為了讓每次這個(gè)新聞分類頁(yè)面不為空,我們需要記下用戶之前選擇了哪些感興趣的分類。為了實(shí)現(xiàn)這個(gè)目的,我們通過(guò)使用jQuery的一個(gè)插件DST.js plugin去把用戶每次選擇的新聞分類都保存在HTML5的localStorage中。如果用戶移除了某個(gè)分類,則也會(huì)在瀏覽器中的本地存儲(chǔ)區(qū)域中移走(注意,要在支持HTML5標(biāo)準(zhǔn)的瀏覽器中才能實(shí)現(xiàn)這個(gè)功能)。當(dāng)頁(yè)面裝載的時(shí)候,我們可以在jQuery的$(document).ready()函數(shù)中獲得已保存的新聞分類并且逐一處理每個(gè)新聞分類下的最新新聞,代碼如下:
- var COMMA = ',';
- var COOKIE_NAME = 'news';
- // numNewsToRestore保存用戶選擇的新聞分類個(gè)數(shù)
- var numNewsToRestore= 0;
- var storedNewsArr;
- ...
- $(document).ready(function () {
- showProgress();
- var storedNewsTxt = $.DSt.get(COOKIE_NAME);
- if(storedNewsTxt != null && storedNewsTxt.length > 0){
- storedNewsArr = storedNewsTxt.split(COMMA);
- }else{
- storedNewsArr = new Array();
- }
- numNewsToRestore = storedNewsArr.length;
- restore();
- });
在上面的代碼中,當(dāng)用戶選擇了新聞分類后,會(huì)將選擇的新聞以逗號(hào)的方式連接,存儲(chǔ)在變量storedNewsArr中,比如用戶選擇了“Top Stories”和“Politics”,則在localStorage存儲(chǔ)區(qū)域中,會(huì)包含字符串“topstories,politics”,接著我們調(diào)用如下所示的restore()函數(shù):
- function restore(){
- if(numNewsToRestore > 0){
- getNews(storedNewsArr[--numNewsToRestore],restoreNews);
- }else{
- showCategories();
- }
- }
在restore()方法中,如果不存在新聞分類目錄,則我們只需要顯示一個(gè)空的新聞頁(yè)。如果存在新聞分類,則調(diào)用getNews()方法,并將最新的一個(gè)分類作為參數(shù)傳進(jìn)去。getNews()方法的另外一個(gè)參數(shù)是restoreNews,接下來(lái)看下getNews()和restoreNews()方法:
- var NEWS_URI = 'bridge.php?fwd=http://rss.news.yahoo.com/rss/';
- ...
- function getNews(varCat,handler){
- var varURI = NEWS_URI + varCat;
- $.ajax({type: GET, dataType: XML, url: varURI, success: handler});
- return false;
- }
- ...
- function restoreNews(xml){
- populateSingleNews(xml);
- restore();
- }
getNews()方法中提供了向ajax發(fā)起請(qǐng)求的地址varURI,這個(gè)uri形如如下的形式:bridge.php?fwd=http://rss.news.yahoo.com/rss/+ varCat,其中varCat就是用戶選擇的新聞目錄,比如bridge.php?fwd=http://rss.news.yahoo.com/rss/topstories。而ajax返回的回調(diào)函數(shù)為restoreNews()。在restoreNews()中,又調(diào)用了一個(gè)自定義的方法populateSingleNews(),這個(gè)方法稍后會(huì)解析。而最后又重新調(diào)用了restore方法,形成了一個(gè)遞歸調(diào)用,順序?yàn)椋?/p>
- restore -> getNews -> restoreNews -> restore -> ...
其中參數(shù)numNewsToRestore指的是用戶選擇了多少個(gè)新聞分類。在jQuery的ready方法中,首先使用插件DST.js plugin把cookie讀取出來(lái)后,形成了字符串?dāng)?shù)組storedNewsArr,接著調(diào)用restore方法,將storedNewsArr中的最后一個(gè)元素(也就是最新用戶選擇的分類)傳入getNews方法中進(jìn)而獲得該分類下的新聞,并通過(guò)restoreNews()方法去處理ajax回調(diào)返回的內(nèi)容,最后又再調(diào)用restore() 方法處理storedNewsArr數(shù)組中的倒數(shù)第2個(gè)新聞分類,如此類推。#p#
增加新聞分類
現(xiàn)在我們討論如何增加一個(gè)新聞分類,這將在populateSingleNews()中實(shí)現(xiàn)。而populateSingleNews()中是根據(jù)返回的XML使用jQuery進(jìn)行解析,將解析后的結(jié)果通過(guò)jQuery Mobile的UI展現(xiàn)出來(lái)。為了方便討論,先選取一段Yahoo News返回的RSS進(jìn)行討論,如下:
- <rss>
- <channel>
- ...
- <category>business</category>
- <description>Business News</description>
- ...
- <item>
- ...
- <title>Retirement Looms: Time to Get Nervous (BusinessWeek)</title>
- ...
- </item>
- <item>
- ...
- <title>European stocks rise as Japan pledges help (AFP)</title>
- ...
- </item>
- ...
- </channel>
- </rss>
上面是Business分類下的兩條新聞的RSS XML文件摘錄,其中對(duì)XML的解析結(jié)果會(huì)保存在currentNews變量中。接下來(lái),將分步介紹populateSingleNews方法的實(shí)現(xiàn)。
1)獲得新聞分類和新聞分類的描述
- var CATEGORY = 'category';
- var DESCR = 'description';
- ...
- function populateSingleNews(xml){
- var tmpTxt = $(xml).find(CATEGORY).first().text();
- var desc = $(xml).find(DESCR).first().text();
- ...
- }
首先,我們調(diào)用jQuery的find().first().text()方法去解析xml,分別獲得了分類的目錄和描述,以上面的xml為例子,得到的結(jié)果是tmpTxt='business' 和desc='Business News'.
2) 獲得新聞分類的列表
- var CAT_ = 'cat_';
- var _D = '_d';
- var _LI = '_li';
- var _A = '_a';
- ...
- function populateSingleNews(xml){
- ...
- var category = CAT_ + tmpTxt;
- var categoryDel = category + _D;
- var categoryLi = categoryDel + _LI;
- var categoryA = category + _A;
- ...
- }
上面的代碼,實(shí)際上會(huì)組合成如下形式的變量:
- category='cat_business'
- categoryDel='cat_business_d'
- categoryLi='cat_business_d_li'
- categoryA='cat_business_a'Next
接下來(lái),為了重用代碼,我們編寫(xiě)了如下的代碼段:
- var HTML_FRG1 = '<li id="';
- var HTML_FRG2 = '"><h3><a id="';
- var HTML_FRG3 = '" href="#">';
- var HTML_FRG4 = '</a></h3><p id="';
- var HTML_FRG5 = '"></p><a href="#" data-transition="slideup" id="';
- var HTML_FRG6 = '"/></li>';
最后,將上述的代碼添加到currentNews后,代碼如下:
- var currentNewsVar = $('#currentNews');
- ...
- function populateSingleNews(xml){
- ...
- $(HTML_FRG1 + categoryLi + HTML_FRG2 + categoryA + HTML_FRG3 + desc + HTML_FRG4 + category + HTML_FRG5 +
- categoryDel + HTML_FRG6).prependTo(currentNewsVar);
- ...
- }
實(shí)際上,以上代碼的效果就會(huì)使用jQuery中的prependTo()方法,把如下的代碼加到id="currentNews"的元素之后,即:
- <li id="cat_business_d_li">
- <h3><a id="cat_business_a" href="#">Business News</a></h3>
- <p id="cat_business"></p>
- <a href="#" data-transition="slideup" id="cat_business_d"/>
- </li>
如果你還不是很清楚的話,下面這個(gè)圖,將生動(dòng)的講解了其對(duì)應(yīng)的結(jié)構(gòu):
圖1 新聞分類列表DOM結(jié)構(gòu)圖
這里的p標(biāo)簽,即id="cat_business"部分,是稍后用來(lái)做動(dòng)畫(huà)變化時(shí)用到的;
觀察這里的標(biāo)簽,這個(gè)標(biāo)簽是用來(lái)當(dāng)點(diǎn)右邊的刪除按鈕時(shí),產(chǎn)生的刪除事件效果時(shí)要用到的Business News;
data-split-icon樣式是使用了jQuery Mobile中默認(rèn)的刪除按鈕;
還記得categoryLi='cat_business_d_li'么?我們調(diào)用了$.doTimeout( categoryLi, false )去實(shí)現(xiàn)了當(dāng)刪除新聞分類時(shí),出現(xiàn)的動(dòng)畫(huà)效果。$.doTimeout是來(lái)自插件jquery-dotimeout-plugin實(shí)現(xiàn)的功能,我們?cè)谏院畹膭?dòng)畫(huà)部分將會(huì)詳細(xì)討論。接下來(lái)找到了新聞分類的標(biāo)記newListItem并使用jQuery的remove()方法將其刪除。在刪除新聞分類后,再調(diào)用storeCurrentNews()方法,重新將當(dāng)前剩下的新聞分類進(jìn)行保存,這個(gè)方法稍后會(huì)詳細(xì)討論。#p#
查看新聞頁(yè)
當(dāng)用戶點(diǎn)某個(gè)新聞分類的標(biāo)題后,就會(huì)跳轉(zhuǎn)到新聞列表頁(yè),其中會(huì)列出所選新聞分類下的新聞,代碼如下:
- function populateSingleNews(xml){
- ...
- var newDescItem = document.getElementById(categoryA);
- $(newDescItem).click(function() {
- showProgress();
- getNews(category.substring(4),populateNewsItems);
- });
- ...
- }
我們找到了標(biāo)簽categoryA,這個(gè)值其實(shí)就是新聞分類標(biāo)題的id值,即:
- <h3><a id="cat_business_a" href="#">Business News</a></h3>
接著,調(diào)用showProgress去顯示等待進(jìn)度的圖標(biāo)。
還記得我們之前的category變量為'cat_business'么?這里我們用substring(4)方法,取得了實(shí)際的分類名,也就是business。
這里再次調(diào)用了getNews方法,但這次回調(diào)的函數(shù)是populateNewsItems,稍后會(huì)詳細(xì)介紹。#p#
顯示新聞標(biāo)題時(shí)的動(dòng)畫(huà)效果
populateSingleNews方法中的最后一個(gè)部分就是當(dāng)每個(gè)分類下有最新新聞時(shí),動(dòng)畫(huà)顯示其新聞標(biāo)題的效果,將其內(nèi)容顯示在<p id="cat_business">中,代碼如下:
- var REFRESH = 'refresh';
- ...
- var ITEM = 'item';
- var TITLE = 'title';
- ...
- function populateSingleNews(xml){
- ...
- var ind = 0;
- var newsArray = new Array();
- $(xml).find(ITEM).each(function(){
- var txt = $(this).find(TITLE).text();
- newsArray[ind++] = txt;
- });
- var newItem = document.getElementById(category);
- $(newItem).text(newsArray[0]);
- currentNewsVar.listview(REFRESH);
- animate(newsArray,$(newItem),categoryLi);
- }
首先通過(guò)find()和each方法將XML中的新聞標(biāo)題(即RSS XML中的title標(biāo)簽中的內(nèi)容)保存到數(shù)組newsArray中。
接著將newsArray數(shù)組中的第1個(gè)元素,也就是最新的一條新聞,通過(guò)jQuery的text方法放到<p id="cat_business">標(biāo)簽中去。
之后調(diào)用jQuery Mobile中封裝好的listview的refresh方法,就可以刷新當(dāng)前目錄區(qū)域內(nèi)的內(nèi)容。
最后,調(diào)用animate方法,其中傳入的參數(shù)是newsArray數(shù)組,
標(biāo)簽和當(dāng)前目錄分類的區(qū)域標(biāo)簽(categoryLi='cat_business_d_li'),下面來(lái)講解下如何實(shí)現(xiàn)動(dòng)畫(huà)效果。
動(dòng)畫(huà)效果
代碼如下:
- var TWO_SECONDS = 2000;
- ...
- function animate(pArr,animationTarget,handle){
- var len = pArr.length;
- var currInd = 1;
- animationTarget.doTimeout(handle,TWO_SECONDS, function(){
- this.fadeOut(function(){
- currInd = currInd % len;
- animationTarget.text(pArr[currInd++]);
- animationTarget.fadeIn();
- });
- return true;
- });
- }
在顯示動(dòng)畫(huà)的方法中,pArr參數(shù)是傳入的新聞列表,animationTarget是最新新聞要顯示的位置區(qū)域。而通過(guò)使用jquery-dotimeout-plugin這個(gè)插件去實(shí)現(xiàn)最新新聞的淡入淡出顯示,這個(gè)插件的效果有點(diǎn)象Javascript中的setTimeout()方法。這里我們定義了每隔2秒,就顯示新聞列表數(shù)組pArr中的內(nèi)容。
而這個(gè)動(dòng)畫(huà)效果會(huì)持續(xù)運(yùn)行下去,但持續(xù)到什么時(shí)候結(jié)束呢?將會(huì)知道執(zhí)行$.doTimeout(…,false)時(shí)才結(jié)束。還記得在populateSingleNews方法中,在刪除新聞分類時(shí),有一行$.doTimeout(categoryLi, false)么?這里實(shí)際上就是說(shuō)在刪除新聞分類前,先停止動(dòng)畫(huà)效果的更新。#p#
查看新聞詳細(xì)頁(yè)的編寫(xiě)
現(xiàn)在我們來(lái)看下,當(dāng)用戶點(diǎn)某個(gè)新聞分類標(biāo)題后,將會(huì)跳轉(zhuǎn)到列出該分類下的新聞列表這個(gè)功能如何實(shí)現(xiàn),為方便起見(jiàn),先以如下的RSS XML為例子進(jìn)行說(shuō)明:
- <rss>
- <channel>
- ...
- <category>business</category>
- <description>Business News</description>
- ...
- <item>
- ...
- <title>Retirement Looms: Time to Get Nervous (BusinessWeek)</title>
- ...
- <description>Let the retirement parties begin: The oldest members of the 1946-64 demographic
- wave known as the Baby Boom turn 65 this month.</description>
- </item>
- ...
- </channel>
- </rss>
下面看下populateNewsItems()的編寫(xiě):
- var EMPTY = '';
- var ITEM = 'item';
- var DESCR = 'description';
- ...
- var HTML_FRG7 = '<p>';
- var HTML_FRG8 = '</p><hr></hr>';
- ...
- var contentNewsVar = $('#contentNews');
- ...
- function populateNewsItems(xml){
- var tmpTxt = EMPTY;
- $(xml).find(ITEM).each(function(){
- var txt = $(this).find(DESCR).text();
- tmpTxt = tmpTxt + HTML_FRG7 + txt + HTML_FRG8;
- });
- contentNewsVar.html(tmpTxt);
- showNews();
- }
我們通過(guò)jQuery的find()和each()解析XML,對(duì)于每個(gè)item元素,取出其新聞的詳細(xì)內(nèi)容即description子元素內(nèi)容放到變量txt中去,最后用<p></p>將其包裹起來(lái),并最后加上一個(gè)水平線作為分隔。
最后通過(guò)jQuery的html()方法將<div id="contentNews">的值設(shè)置為tmpTxt,記得<div id="contentNews">就是新聞內(nèi)容的區(qū)域。
增加用戶自己喜好的新聞分類
當(dāng)在增加新聞分類頁(yè)中,點(diǎn)‘Get Category’按鈕后,觸發(fā)buttonGetCategoryVar.click()事件,代碼如下:
- var buttonGetCategoryVar = $('#buttonGetCategory');
- var categoryVar = $('#category');
- var EMPTY = '';
- ...
- buttonGetCategoryVar.click(function() {
- if(categoryVar.val() != EMPTY){
- showProgress();
- return getNews(categoryVar.val(),addNews);
- }else{
- showCategories();
- return false;
- }
- });
- ...
- function addNews(xml){
- populateSingleNews(xml);
- storeCurrentNews();
- showCategories();
- }
在點(diǎn)‘Get Category’ 按鈕后,首先判斷下拉列表框中用戶是否選擇了新聞分類,如果選擇了新聞分類則調(diào)用getNews方法,getNews()方法我們已經(jīng)討論過(guò),實(shí)際上就是這里把選擇的新聞分類名稱通過(guò)ajax調(diào)用發(fā)送到服務(wù)端,獲得服務(wù)端返回的XML內(nèi)容。
在getNews方法的回調(diào)方法中,調(diào)用的是 populateSingleNews(xml);原因是在增加完新聞分類后,跳轉(zhuǎn)到的頁(yè)面中,是顯示剛新增的一個(gè)分類以及其最新的一條新聞,如下圖,再調(diào)用storeCurrentNews(),把用戶增加的這個(gè)新聞分類保存到HTML5中的localStorage中。
圖2 上面這個(gè)圖中,用戶選擇了兩個(gè)新聞分類U.S.News和Economy News。
保存用戶選擇的新聞分類
現(xiàn)在我們回過(guò)頭來(lái)看如何使用HTML5的localStorage特性去保存用戶選擇的新聞分類。
代碼如下:
- var COOKIE_NAME = 'news';
- var COMMA = ',';
- var EMPTY = '';
- var LI = 'li';
- var PAR = 'p';
- var ID = 'id';
- ...
- var currentNewsVar = $('#currentNews');
- ...
- function storeCurrentNews(){
- $.DSt.set(COOKIE_NAME, EMPTY);
- var tmp = EMPTY;
- currentNewsVar.find(LI).each(function(){
- tmp = tmp + COMMA + $(this).find(PAR).attr(ID).substring(4);
- });
- $.DSt.set(COOKIE_NAME, tmp.substring(1));
- }
在storeCurrentNews方法中,首先調(diào)用DST.js plugin插件清除掉原先保存的cookie。
接著, 使用jQuery的find和each方法,去獲得圖2獲得新聞分類列表中的每個(gè)分類的名稱,這里實(shí)際上是獲得<p id='cat_business'>中的cat_business部分,然后再用substring方法獲得其實(shí)際名稱,這里即business為其分類名稱。
最后使用DST.js plugin中的set方法,把用戶選擇的新聞分類列表都保存在cookie中。#p#
其他事件代碼講解
新聞詳細(xì)頁(yè)返回主頁(yè)的事件代碼
在第一部分中,曾經(jīng)提到在新聞詳細(xì)內(nèi)容頁(yè)的頭部和底部都有按鈕返回到主頁(yè),其代碼如下,非常簡(jiǎn)單,只是調(diào)用showCategories代碼,具體見(jiàn)下載附件。
- var buttonHdrShowCategoriesVar = $('#buttonHdrShowCategories');
- var buttonFtrShowCategoriesVar = $('#buttonFtrShowCategories');
- buttonHdrShowCategoriesVar.click(function() {
- showCategories();
- return false;
- });
- buttonFtrShowCategoriesVar.click(function() {
- showCategories();
- return false;
- });
關(guān)于AJAX請(qǐng)求
最后我們來(lái)討論ajax請(qǐng)求部分,在這里,我們是通過(guò)使用bridge.php作為中轉(zhuǎn),對(duì)Yahoo的新聞發(fā)起ajax請(qǐng)求,重新復(fù)習(xí)下getNews的代碼:
- var NEWS_URI = 'bridge.php?fwd=http://rss.news.yahoo.com/rss/';
- function getNews(varCat,handler){
- var varURI = NEWS_URI + varCat;
- $.ajax({type: GET, dataType: XML, url: varURI, success: handler});
- return false;
- }
在上面的代碼中,index.html和bridge.php都運(yùn)行在同一服務(wù)器環(huán)境中,實(shí)際向yahoo發(fā)出的請(qǐng)求會(huì)是這個(gè)樣子:
bridge.php?fwd=http://rss.news.yahoo.com/rss/business,而bridge.php會(huì)通過(guò)php的cUrl方法向Yahoo發(fā)出請(qǐng)求,將獲得的xml信息寫(xiě)在文件tmpFile.txt中,具體代碼如下,關(guān)于php的cUrl方法請(qǐng)參考PHP手冊(cè),這里不再詳細(xì)介紹。
- <?php
- header('Content-Type: application/xml');
- $tmpFile = 'tmpFile.txt';
- $val = $_GET["fwd"];
- $curlHandle = curl_init($val);
- $filePointer = fopen($tmpFile, "w");
- curl_setopt($curlHandle, CURLOPT_FILE, $filePointer);
- curl_exec($curlHandle);
- curl_close($curlHandle);
- fclose($filePointer);
- $linesArr = file($tmpFile);
- foreach($linesArr as $eachLine){
- echo($eachLine);
- }
- ?>
#p#
Web項(xiàng)目的結(jié)構(gòu)
最后,我們簡(jiǎn)單介紹下web項(xiàng)目的結(jié)構(gòu),詳細(xì)的請(qǐng)參考附件。
項(xiàng)目根目錄下包含 index.html and bridge.php.
css-js下包含了所有的css和Javascript文件,如下
jquery-1.4.4.min.js, jquery.mobile-1.0a2.min.js,jquery.mobile-1.0a2.min.css
這些都是jQuery Mobile的文件
jquery.ba-dotimeout.js 為 jquery-dotimeout-plugin 庫(kù)文件.
jquery.dst.js 為 DST.js plugin 庫(kù)文件.
news\img\wait.gif 為等待圖標(biāo),最后,記得引用這些Javascript代碼庫(kù),如下:
- <link rel="stylesheet" href="css-js/jquery.mobile-1.0a2.min.css" />
- <script src="css-js/jquery-1.4.4.min.js"></script>
- <script src="css-js/jquery.mobile-1.0a2.min.js"></script>
- <script src="css-js/jquery.ba-dotimeout.js"></script>
- <script src="css-js/jquery.dst.js"></script>
小結(jié)
在本文中,介紹了如何使用jQuery Mobile去實(shí)現(xiàn)一個(gè)web版本的新聞閱讀器,其中講解了jQuery Mobile及jquery-dotimeout插件,jquery.dst插件的使用。