在上一篇文章中,已经讨论了程序的结构和页面的布局,并简单介绍了一些jQuery Mobile的使用技巧。在本篇文章中,笔者将继续完成我们web应用的新闻浏览器的设计。
程序的启动
我们现在来研究一下程序的启动。当程序启动的时候,展示给用户的是新闻分类列表的页面,为了让每次这个新闻分类页面不为空,我们需要记下用户之前选择了哪些感兴趣的分类。为了实现这个目的,我们通过使用jQuery的一个插件DST.js plugin去把用户每次选择的新闻分类都保存在HTML5的localStorage中。如果用户移除了某个分类,则也会在浏览器中的本地存储区域中移走(注意,要在支持HTML5标准的浏览器中才能实现这个功能)。当页面装载的时候,我们可以在jQuery的$(document).ready()函数中获得已保存的新闻分类并且逐一处理每个新闻分类下的最新新闻,代码如下:
- var COMMA = ',';
- var COOKIE_NAME = 'news';
- // numNewsToRestore保存用户选择的新闻分类个数
- 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();
- });
在上面的代码中,当用户选择了新闻分类后,会将选择的新闻以逗号的方式连接,存储在变量storedNewsArr中,比如用户选择了“Top Stories”和“Politics”,则在localStorage存储区域中,会包含字符串“topstories,politics”,接着我们调用如下所示的restore()函数:
- function restore(){
- if(numNewsToRestore > 0){
- getNews(storedNewsArr[--numNewsToRestore],restoreNews);
- }else{
- showCategories();
- }
- }
在restore()方法中,如果不存在新闻分类目录,则我们只需要显示一个空的新闻页。如果存在新闻分类,则调用getNews()方法,并将最新的一个分类作为参数传进去。getNews()方法的另外一个参数是restoreNews,接下来看下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发起请求的地址varURI,这个uri形如如下的形式:bridge.php?fwd=http://rss.news.yahoo.com/rss/+ varCat,其中varCat就是用户选择的新闻目录,比如bridge.php?fwd=http://rss.news.yahoo.com/rss/topstories。而ajax返回的回调函数为restoreNews()。在restoreNews()中,又调用了一个自定义的方法populateSingleNews(),这个方法稍后会解析。而最后又重新调用了restore方法,形成了一个递归调用,顺序为:
- restore -> getNews -> restoreNews -> restore -> ...
其中参数numNewsToRestore指的是用户选择了多少个新闻分类。在jQuery的ready方法中,首先使用插件DST.js plugin把cookie读取出来后,形成了字符串数组storedNewsArr,接着调用restore方法,将storedNewsArr中的最后一个元素(也就是最新用户选择的分类)传入getNews方法中进而获得该分类下的新闻,并通过restoreNews()方法去处理ajax回调返回的内容,最后又再调用restore() 方法处理storedNewsArr数组中的倒数第2个新闻分类,如此类推。#p#
增加新闻分类
现在我们讨论如何增加一个新闻分类,这将在populateSingleNews()中实现。而populateSingleNews()中是根据返回的XML使用jQuery进行解析,将解析后的结果通过jQuery Mobile的UI展现出来。为了方便讨论,先选取一段Yahoo News返回的RSS进行讨论,如下:
- <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文件摘录,其中对XML的解析结果会保存在currentNews变量中。接下来,将分步介绍populateSingleNews方法的实现。
1)获得新闻分类和新闻分类的描述
- var CATEGORY = 'category';
- var DESCR = 'description';
- ...
- function populateSingleNews(xml){
- var tmpTxt = $(xml).find(CATEGORY).first().text();
- var desc = $(xml).find(DESCR).first().text();
- ...
- }
首先,我们调用jQuery的find().first().text()方法去解析xml,分别获得了分类的目录和描述,以上面的xml为例子,得到的结果是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;
- ...
- }
上面的代码,实际上会组合成如下形式的变量:
- category='cat_business'
- categoryDel='cat_business_d'
- categoryLi='cat_business_d_li'
- categoryA='cat_business_a'Next
接下来,为了重用代码,我们编写了如下的代码段:
- 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);
- ...
- }
实际上,以上代码的效果就会使用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>
如果你还不是很清楚的话,下面这个图,将生动的讲解了其对应的结构:
图1 新闻分类列表DOM结构图
这里的p标签,即id="cat_business"部分,是稍后用来做动画变化时用到的;
观察这里的标签,这个标签是用来当点右边的删除按钮时,产生的删除事件效果时要用到的Business News;
data-split-icon样式是使用了jQuery Mobile中默认的删除按钮;
The data-split-theme属性指定使用了什么风格样式的删除按钮,这里我们使用了d样式的风格删除按钮,如果不指定的话,默认使用的是a风格样式的按钮。
还记得categoryLi='cat_business_d_li'么?我们调用了$.doTimeout( categoryLi, false )去实现了当删除新闻分类时,出现的动画效果。$.doTimeout是来自插件jquery-dotimeout-plugin实现的功能,我们在稍侯的动画部分将会详细讨论。接下来找到了新闻分类的标记newListItem并使用jQuery的remove()方法将其删除。在删除新闻分类后,再调用storeCurrentNews()方法,重新将当前剩下的新闻分类进行保存,这个方法稍后会详细讨论。#p#
查看新闻页
当用户点某个新闻分类的标题后,就会跳转到新闻列表页,其中会列出所选新闻分类下的新闻,代码如下:
- function populateSingleNews(xml){
- ...
- var newDescItem = document.getElementById(categoryA);
- $(newDescItem).click(function() {
- showProgress();
- getNews(category.substring(4),populateNewsItems);
- });
- ...
- }
我们找到了标签categoryA,这个值其实就是新闻分类标题的id值,即:
- <h3><a id="cat_business_a" href="#">Business News</a></h3>
接着,调用showProgress去显示等待进度的图标。
还记得我们之前的category变量为'cat_business'么?这里我们用substring(4)方法,取得了实际的分类名,也就是business。
这里再次调用了getNews方法,但这次回调的函数是populateNewsItems,稍后会详细介绍。#p#
显示新闻标题时的动画效果
populateSingleNews方法中的最后一个部分就是当每个分类下有最新新闻时,动画显示其新闻标题的效果,将其内容显示在<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);
- }
首先通过find()和each方法将XML中的新闻标题(即RSS XML中的title标签中的内容)保存到数组newsArray中。
接着将newsArray数组中的第1个元素,也就是最新的一条新闻,通过jQuery的text方法放到<p id="cat_business">标签中去。
之后调用jQuery Mobile中封装好的listview的refresh方法,就可以刷新当前目录区域内的内容。
最后,调用animate方法,其中传入的参数是newsArray数组,
标签和当前目录分类的区域标签(categoryLi='cat_business_d_li'),下面来讲解下如何实现动画效果。
动画效果
代码如下:
- 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;
- });
- }
在显示动画的方法中,pArr参数是传入的新闻列表,animationTarget是最新新闻要显示的位置区域。而通过使用jquery-dotimeout-plugin这个插件去实现最新新闻的淡入淡出显示,这个插件的效果有点象Javascript中的setTimeout()方法。这里我们定义了每隔2秒,就显示新闻列表数组pArr中的内容。
而这个动画效果会持续运行下去,但持续到什么时候结束呢?将会知道执行$.doTimeout(…,false)时才结束。还记得在populateSingleNews方法中,在删除新闻分类时,有一行$.doTimeout(categoryLi, false)么?这里实际上就是说在删除新闻分类前,先停止动画效果的更新。#p#
查看新闻详细页的编写
现在我们来看下,当用户点某个新闻分类标题后,将会跳转到列出该分类下的新闻列表这个功能如何实现,为方便起见,先以如下的RSS XML为例子进行说明:
- <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()的编写:
- 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();
- }
我们通过jQuery的find()和each()解析XML,对于每个item元素,取出其新闻的详细内容即description子元素内容放到变量txt中去,最后用<p></p>将其包裹起来,并最后加上一个水平线作为分隔。
最后通过jQuery的html()方法将<div id="contentNews">的值设置为tmpTxt,记得<div id="contentNews">就是新闻内容的区域。
增加用户自己喜好的新闻分类
当在增加新闻分类页中,点‘Get Category’按钮后,触发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();
- }
在点‘Get Category’ 按钮后,首先判断下拉列表框中用户是否选择了新闻分类,如果选择了新闻分类则调用getNews方法,getNews()方法我们已经讨论过,实际上就是这里把选择的新闻分类名称通过ajax调用发送到服务端,获得服务端返回的XML内容。
在getNews方法的回调方法中,调用的是 populateSingleNews(xml);原因是在增加完新闻分类后,跳转到的页面中,是显示刚新增的一个分类以及其最新的一条新闻,如下图,再调用storeCurrentNews(),把用户增加的这个新闻分类保存到HTML5中的localStorage中。
图2 上面这个图中,用户选择了两个新闻分类U.S.News和Economy News。
保存用户选择的新闻分类
现在我们回过头来看如何使用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方法中,首先调用DST.js plugin插件清除掉原先保存的cookie。
接着, 使用jQuery的find和each方法,去获得图2获得新闻分类列表中的每个分类的名称,这里实际上是获得<p id='cat_business'>中的cat_business部分,然后再用substring方法获得其实际名称,这里即business为其分类名称。
最后使用DST.js plugin中的set方法,把用户选择的新闻分类列表都保存在cookie中。#p#
其他事件代码讲解
新闻详细页返回主页的事件代码
在第一部分中,曾经提到在新闻详细内容页的头部和底部都有按钮返回到主页,其代码如下,非常简单,只是调用showCategories代码,具体见下载附件。
- var buttonHdrShowCategoriesVar = $('#buttonHdrShowCategories');
- var buttonFtrShowCategoriesVar = $('#buttonFtrShowCategories');
- buttonHdrShowCategoriesVar.click(function() {
- showCategories();
- return false;
- });
- buttonFtrShowCategoriesVar.click(function() {
- showCategories();
- return false;
- });
关于AJAX请求
最后我们来讨论ajax请求部分,在这里,我们是通过使用bridge.php作为中转,对Yahoo的新闻发起ajax请求,重新复习下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都运行在同一服务器环境中,实际向yahoo发出的请求会是这个样子:
bridge.php?fwd=http://rss.news.yahoo.com/rss/business,而bridge.php会通过php的cUrl方法向Yahoo发出请求,将获得的xml信息写在文件tmpFile.txt中,具体代码如下,关于php的cUrl方法请参考PHP手册,这里不再详细介绍。
- <?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项目的结构
最后,我们简单介绍下web项目的结构,详细的请参考附件。
项目根目录下包含 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 库文件.
jquery.dst.js 为 DST.js plugin 库文件.
news\img\wait.gif 为等待图标,最后,记得引用这些Javascript代码库,如下:
- <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>
小结
在本文中,介绍了如何使用jQuery Mobile去实现一个web版本的新闻阅读器,其中讲解了jQuery Mobile及jquery-dotimeout插件,jquery.dst插件的使用。