|
將網路資料變成 Widget:RSS 新聞閱讀器 Widget 範例
在剛剛的 HelloWorld 程式中,您已經了解 widget 的基本構成方式,還有其事件處理程式的基本模式。接下來我們在這個 RSS 新聞閱讀器 widget 範例中,我們將告訴您:
以下是本節課程會用到的檔案:
如何結合獨立的 JavaScript 檔案到 .kon 檔案 首先,為何要這麼作?您可以看到,在 Hello World 範例中,一個 .kon 檔案就包含了 widget 介面、屬性定義,及其 JavaScript 事件處理程式。但是萬一您往後的 widget 的程式邏輯越來越複雜,不是十幾二十行可以解決的時候,與介面定義混在一起時有下列缺點:
要將 JavaScript 的檔案 include 進來非常容易,您只要在 <widget> tag 之後加入 <action trigger="onLoad" file="JavaScript檔名"/> 敘述即可,這樣便可將 JavaScript 碼獨立在一個檔案中,且會在 widget 載入時就自動執行該 JavaScript 檔案中的程式碼。 在各位常常看到現有的 widget 中,大部分都是跟網路相關,方式都是從網站自入資料、分析之後,然後再以 widget 的方式呈現在使用者面前,因此 widget 程式設計裡,有一個很重要的課題,就是如何從網站載入資料並萃取我們想要的資料,再呈現在 widget 中。 如果您知道並設計過 AJAX,相信您對 XMLHttpRequest 這個 class 不陌生。在 Yahoo! Widget Engine 3.0 之後的版本也加入了 XMLHttpRequest 的支援,這個 class 讓您可以用 JavaScript 動態去載入外部網路的資料,而如果載入的資料是 XML 的話,這個 class 也會自動將載入的資料分解成 Document Object Model(DOM)的樹狀結構化物件(DOMDocument 物件),讓您方便的存取 XML 文件的任一個部份。 在這個範例中,我們會設計下面這個 loadRSS 的函式,會利用 XMLHttpRequest 來載入網站的 RSS XML 資料。以下是 XMLHttpRequest 的一般使用方式: 程式列表 1 var req = new XMLHttpRequest(); 1 req.open("GET", "http://tw.news.yahoo.com/rss/realtime", false); 2 req.send(); 3
以上使用 XMLHttpRequest 來載入網站資料的基本使用方法。根據這樣的使用方法,我們來建立一個載入 RSS 2.0 Feed 的函式 - loadRSS(): 程式列表 2
/*
loadRSS 從 feedURL 載入 RSS feed 內容,並傳回一個 DOMDocument 物件。
如果不是 RSS 2.0 feed 或載入有問題時,將傳回 null。
注意,本 function 使用同步 XMLHttpRequest,當網路發生問題時,會有可能發生延滯現象。
input:
feedURL RSS Feed URL
Output:
DOMDocument 含有 RSS XML、已 parse 的結構化物件
*/
function loadRSS(feedURL) {
// 使用 XMLHttpRequest 將資料由 feedURL 指定的網址下載下來
var req = new XMLHttpRequest();
req.open("GET", feedURL, false);
// 先試著請伺服器傳回 Content-Type 為 text/xml 的資料,以方便直接 parse 成 XML
req.setRequestHeader("Content-Type", "text/xml"); 1
req.send();
if (req.status != 200)
return null; // 沒有載入成功,傳回 null
if (req.responseXML == null) {
// 會沒有 DOMDocument 有可能伺服器回應的 Content-Type 不是 text/xml
//(有些會傳回 application/xml)
// 用 XMLDOM 強制來 parse 成 XML
try {
var xml = XMLDOM.parse(req.responseText); 2
} catch (e) {
return null; // 試著 parse 成 XML 但失敗,傳回 null
}
} else
var xml = req.responseXML;
// 檢查是否為 RSS 2.0
var rsss = xml.evaluate("rss"); // 使用 XPath 來取得我們要的 RSS 區段 3
if (rsss == null)
return null; // 沒有 RSS,傳回 null
if (parseFloat(rsss.item(0).getAttribute("version")) >= 2.0)
return xml; // 是 RSS 2.0 feed,傳回載入的 DOMDocument
else
return null; // 不是 RSS 2.0 feed,傳回 null
}
loadRSS 這個函式會利用 XMLHttpRequest 載入網頁資料,然後將載入資料分析成結構化的 DOMDocument 物件。其中有幾行值得注意的:
在 loadRSS 執行完之後,便會回傳 null 或是含有 DOMDocument 的物件。如果載入資料無誤並確定為 RSS 2.0 Feed,我們就必須有編寫另一個函式來將載入資料轉化為 widget 的顯示資料及使用者介面的行為。 接下來的部份,我們會撰寫 showRSS 函式,將載入的新聞主題與時間以列表方式呈現,然後當使用者將滑鼠指標移向個別新聞標題或時間時,就會改變文字顏色;反之,如果移出該新聞標題,就會還原文字顏色。如果按下某一新聞標題或時間時,就會打開預設瀏覽器並直接連到該新聞的網頁。 先看看我們現在有的東西,loadRSS 會輸出一個 DOMDocument 物件,我們要如何將這樣的物件抽出我們要的標題、時間與網頁連結呢?首先我們必須對 RSS 2.0 Feed 的 XML 語法有所了解才行。 以下是僅含有一則 RSS 2.0 Yahoo! 奇摩新聞的簡化 XML 檔案範例(實際上一個 RSS 有很多則新聞): 程式列表 3 <rss version="2.0"> <channel> <title>Yahoo!奇摩新聞-即時新聞</title> <link>http://tw.news.yahoo.com/realtime/0.html</link> <description>Yahoo!奇摩新聞-即時新聞</description> <language>zh-tw</language> <lastBuildDate>Sun, 5 Feb 2006 02:15:07 GMT</lastBuildDate> <ttl>5</ttl> <item> <title>強風迫使美國華盛頓州16萬戶斷電</title> <link>http://tw.news.yahoo.com/060205/4/2to0n.html</link> <pubDate>Sun, 5 Feb 2006 02:00:00 GMT</pubDate> </item> </channel> </rss> 在這教材裡,我們並不會特別去詳細解說 RSS Feed 的結構,我們只講解我們會利用到的部份。我們無論是利用 XMLHttpRequest 或 XMLDOM.parse 去分析這些資料並轉換成 DOMDocument,其結果都是一樣的,就是在 DOMDocument 裡面,如果以檔案的樹狀結構來解釋上面的 XML,就會像:
以剛剛 loadRSS 函式用來判別是否為 RSS 2.0 Feed 的這個需求來說,我們使用了 rsss = xml.evaluate("rss") 來萃取在載入資料中所有含有 rss tag 的項目(有可能 0 或多個 rss tag 項目),並利用 rsss.item(0).getAttribute("version") 來粹取第一個 rss 的 version 屬性的值。其中 item(0) 就是指定萃取出的所有 rss tag 的第一個 rss 項目。然後再利用物件函式 getAttribute("version") 來萃取其 version 屬性。 請注意,XML 語法是有可能同時存在好幾個相同的 tag 的項目,所以當您使用 DOMDocument 的 evaluate 函式時,萃取出來的有可能是 0 到多個含有同樣 tag 的項目(每個項目皆為 DOMNode 物件,傳回的是 DOMNodes 物件集合),想知道有多少個的話,可以檢查 length 屬性,想要個別存取每個項目的話,可以用 item(n),其中 n 是 0 到 length-1 的數字。 在 xml.evaluate("rss")(以此範例來說,xml 是一個 DOMDocument 物件)這個函式中,其參數使一個 XPath 字串,所謂的 XPath 基本上就像你在存取檔案或網頁的路徑一樣。假設我們想存取 rss 下的 channel 下的所有的 item tag 項目,那麼你可以下如 xml.evaluate("rss/channel/item") 的指令便可傳回所有 rss -> channel 下的所有 item tag 項目。其實 XPath 還有更多強大功能,讓您方便準確地存取 XML 裡的任一部份,在 Yahoo! Widget Engine 3.0 Reference 裡的 XPath Support 小節裡有更進一步的 XPath 說明。 有了這個概念,我們接下來看我們即將利用的 DOMDocument 的 evaluate 函式來進行萃取並轉化成 widget 呈現方式的 showRSS 函式。 這個 showRSS 函式最基本的邏輯大致如下:
程式列表 4
/*
showRSS 將 rssDOM 內含已 parse 過的 RSS DOMDocument object 來顯示於 widget 的 itemList frame 裡
*/
function showRSS(rssDOM) {
var theItem, newItem, titleView, timeView;
// 先清除目前 itemList frame 的內容
while ((theItem = gRSSItems.shift()) != null) {
theItem.view.removeFromSuperview();
}
var nodes = rssDOM.evaluate("rss/channel/item"); 1
var curY = 0;
for (var i = 0; i < nodes.length; i++) {
newItem = new Array(); 2
newItem.title = (nodes.item(i).getElementsByTagName("title")).item(0).firstChild.data; 3
newItem.link = (nodes.item(i).getElementsByTagName("link")).item(0).firstChild.data;
newItem.pubDate = new Date(Date.parse((nodes.item(i).getElementsByTagName("pubDate")).
item(0).firstChild.data)); 4
newItem.view = new Frame(); // 新增 RSS 新聞項目 sub frame
newItem.view.hOffset = 0;
newItem.view.vOffset = curY;
newItem.view.width = itemList.width;
newItem.view.height = 18;
newItem.view.onMouseEnter = "hiliteItem(" + i + ")";
newItem.view.onMouseExit = "dimItem(" + i + ")";
newItem.view.onMouseUp = "openURL('" + newItem.link + "')";
titleView = new Text(); // 新增 RSS 新聞項目的新聞 title 文字區塊
titleView.data = newItem.title;
titleView.vOffset = 15;
titleView.hOffset = 0;
titleView.font = "MS UI Gothic, Arial, Helvetica";
titleView.color = "#FFFFFF";
titleView.size = 12;
titleView.width = itemList.width - 100;
titleView.truncation = "end";
titleView.tooltip = newItem.title;
newItem.view.addSubview(titleView); // 加到 RSS 新聞項目 sub frame 中
timeView = new Text(); // 新增 RSS 新聞項目的新聞 title 文字區塊
timeView.data = newItem.pubDate.getMonth() + "月" + newItem.pubDate.getDate() +
"日 " + newItem.pubDate.toLocaleTimeString();
timeView.vOffset = 15;
timeView.hOffset = itemList.width - 95;
timeView.font = "MS UI Gothic, Arial, Helvetica";
timeView.color = "#FFFFFF";
timeView.size = 12;
timeView.truncation = "end";
timeView.width = 90;
newItem.view.addSubview(timeView); // 加到 RSS 新聞項目 sub frame 中
gRSSItems.push(newItem); // 將所有 RSS 新聞項目記錄在 gRSSItems 陣列中
itemList.addSubview(newItem.view);
curY += 18;
updateNow();
}
}
首先我們先從第 2、3 點這些跟分析資料相關的重點程式碼來說明:
YWE 3.0 新增的 Frame 物件的用法與其 sub frame(subview)的關係 在萃取到我們要的新聞標題、日期與連結後,我們可以依據這些資料來建立顯示的資料。我們首先必須把每個顯示介面分開來看:
這是我們即將用來顯現新聞列表的介面,整個列表(紅框區,命名為 itemList)與包含 0 到多個新聞項目(行)還有一個捲動軸(綠框區,命名為 sb)。其中黃框區是單一的新聞項目(黃框區),其中一個新聞項目包含有兩個主要顯示文字區塊(藍框區,新聞標題與發佈時間),然後有三個事件處理程式(onMouseEnter 當滑鼠指標移入時,將文字以橘色顯示;onMouseExit 當滑鼠指標移出時,將文字以白色顯示;onMouseUp 當滑鼠按鍵被按下又放開時,會打開瀏覽器並連結至新聞網頁)。 我們假設在 MyRSS.kon 檔案我們定義了一個名為 itemList 的 Frame 區域給新聞列表顯示之用,如下: 程式列表 5 <frame name="itemList"> <hOffset>25</hOffset> <vOffset>45</vOffset> <width>340</width> <height>220</height> <vScrollBar>sb</vScrollBar> </frame> 其中比較特別的是我們利用 程式列表 6 <scrollbar name="sb"> <hOffset>365</hOffset> <vOffset>44</vOffset> <width>16</width> <height>220</height> <thumbColor>#dddddd</thumbColor> <autoHide>true</autoHide> </scrollbar> 首先說明 Frame 這個使用介面物件。在 Yahoo! Widget Engine 3.0 版之前,並沒有階層式的使用介面概念,也沒有 subview 的顯示方式。也就是說,所有顯示在 widget 裡的座標位置,都是只用同一個座標系統來標示,而當您要將一組含有多的使用介面元件做變化時(如隱藏或顯示),您必須要一個個去作。這樣還好,但是另一個帶來的問題就是,如果您有很多資料要顯示時,您並不能像正常的程式一樣,可以去利用捲視軸利用有限空間來顯示那麼多資料,因為也沒有所謂的剪裁區域(clipping region)物件讓您這麼作。 在新的 Yahoo! Widget Engine 3.0 版引進了這樣階層式的使用介面概念,並有了 Frame 與 scrollbar 物件,且提供非常容易的應用方式,一次解決上述的問題。 以這個範例為例,我們可以說,itemList 這個 Frame 是我們新聞列表最上層的顯示區域。這之下有 0 到多個的新聞項目 subview(也就是 Frame),給個新聞項目 subview 有兩個 Text 物件,一個 Text 物件是新聞標題,另一個是發佈時間。 我們來看剛剛的程式列表的一部份: 程式列表 7
newItem.view = new Frame(); // 新增 RSS 新聞項目 sub frame
newItem.view.hOffset = 0;
newItem.view.vOffset = curY;
newItem.view.width = itemList.width;
newItem.view.height = 18;
newItem.view.onMouseEnter = "hiliteItem(" + i + ")";
newItem.view.onMouseExit = "dimItem(" + i + ")";
newItem.view.onMouseUp = "openURL('" + newItem.link + "')";
以這段程式來說,這是我們定義每一行新聞項目的 subview(Frame),其中包括座標(curY 是累積的縱向座標,會隨著項目的順序而一次增加,越後面的項目縱向座標越下面,每次遞增 18)、顯示大小(寬度等於 itemList.width、高度 18),並定義 onMouseEnter、onMouseExit、onMouseUp 三個事件處理程式個別指向 hiliteItem()、dimItem() 及 openURL 函式。接下來看其下面的 subview 的程式: 程式列表 8 titleView = new Text(); // 新增 RSS 新聞項目的新聞 title 文字區塊 titleView.data = newItem.title; titleView.vOffset = 15; titleView.hOffset = 0; titleView.font = "MS UI Gothic, Arial, Helvetica"; titleView.color = "#FFFFFF"; titleView.size = 12; titleView.width = itemList.width - 100; titleView.truncation = "end"; 1 titleView.tooltip = newItem.title; 2 newItem.view.addSubview(titleView); // 加到 RSS 新聞項目 sub frame 中 3 timeView = new Text(); // 新增 RSS 新聞項目的新聞 title 文字區塊 timeView.data = newItem.pubDate.getMonth() + "月" + newItem.pubDate.getDate() + "日 " + newItem.pubDate.toLocaleTimeString(); timeView.vOffset = 15; timeView.hOffset = itemList.width - 95; timeView.font = "MS UI Gothic, Arial, Helvetica"; timeView.color = "#FFFFFF"; timeView.size = 12; timeView.truncation = "end"; timeView.width = 90; newItem.view.addSubview(timeView); // 加到 RSS 新聞項目 sub frame 中 在這段程式裡面有幾個值得注意的:
接下來看迴圈最後的幾行程式: 程式列表 9 gRSSItems.push(newItem); // 將所有 RSS 新聞項目記錄在 gRSSItems 陣列中 1 itemList.addSubview(newItem.view); 2 curY += 18; updateNow(); 3
使用 Widget Converter 將 RSS 新聞閱讀器 Widget 打包成跨平台的 YWE Widget 接下來來做最後的打包工作。在剛剛的 Hello World 範例中,我們使用了手動的方式來打包 widget,但是事實上有更簡便的方法。您可以到這裡下載 Widget Converter 這個 widget,下載後打開後,請用下列步驟將 RSS 新聞閱讀器打包起來: 1. 將「RSS Reader」這個檔案夾拖到 Widget Convert 的視窗。
2. 之後會出現如下圖的變化,此時請按下「convert」按鈕。
3. 過一下子,會出現下面的視窗,並會在您「RSS Reader」所在檔案夾中出現「RSS Reader.widget」的 widget 檔案。這樣便大功告成。
其實 Widget Converter 還可以做相反的事,就是如果您將一個 widget 檔案拖到 Widget Converter 的視窗中並按下「convert」按鈕,便會將壓縮的 widget 檔案轉成檔案夾,非常方便。
|
||||||||||||||||||||||||||||||||||||||||





