運用JavaScript構建你的第一個Metro式應用程序(on Windows 8)

包括 HTML、CSS 和 JavsScript 的 Web 技術正被 Metro 風格的程序采納為視窗程序中首類(first-class)的開發技術。比較起來,JavaScript 不像傳統 Web 服務器那樣部署在一張又一張的頁面上,Metro App 是本地安裝在客戶機器上的。這點很像傳統的 Win 程序,直接使用 JavaScript 便可以訪問所在的底層,還能和其他程序相溝通。

註意,如果您傾向於 C#, C++, or VB withXAML,開發,請參閱 Buildingyour first Windows Metro style app with C#, C++, or VB。


目標
在本教程中我們將會為您介紹如何透過 JavaScrip 來構建 Metro 風格的程序。通過 HTML5、層疊樣式表(CSS3)和 JavaScript 這些技術,與您一起打造一個輕型的 RSS 客戶端,並且盡量使得這個程序更好地彰顯個性化(that conveys the right personality)。我們將引入包括控件、功能、佈局、模板、數據綁定以及 JS 庫這些核心的內容,而且,所述的這些技術已經在 Metro App 開發中都得到優化,可放心使用。根據此教程,閣下學習完畢後,應已具備初步開發 JS 版 Metro App 的能力。教程劃分為三小節,估計20分鐘閱讀完一小節,當然,如果做好每個習題的話,時間可能會稍長一點。
Hello Win Metro
跟 web 頁面那樣,得先有一份 HTML 文件去寫 Metro App。


view plaincopy to clipboardprint?<!DOCTYPE html> 
<html> 
  <head><title>你好Windows Metro styleapps!</title></head> 
  <body><h1>歡迎來到 Windows Metro styleapps</h1></body> 
</html> 
<!DOCTYPE html>
<html>
  <head><title>你好Windows Metro styleapps!</title></head>
  <body><h1>歡迎來到 Windows Metro styleapps</h1></body>
</html>

如果把這 HTML 放在瀏覽器中運行,肯定沒啥意思。不用多說,如果在 Metro App 中執行,則又是另外一番天地,當然您的客戶,一般不會在乎那究竟是瀏覽器還是 Metro  App。隻不過,有明顯不同,就是 Metro App 會安裝在 Window Store 中,跟其他 Win 程序一樣。再者,若說單張 Web 頁面,根本算不上叫做完整的程序,哪怕涉及樣式、代碼、圖片資源的那些。現在,Metro App 不但包括這些,而且還提格為標準的應用程序,於是將有元數據和程序資源文件的出現:

用於描述程序本身的清單文件(manifest),包括程序名稱、簡介、啟動頁等等。

用於外觀的圖片文件或者圖標文件。

用於Windows Store顯示的圖標 logo。

用於你程序啟動時歡迎屏幕(a splash screen)。

程序清單(manifest)是一份名為 appxmanifest.xml 的 XML 文件。該文件包括瞭諸如程序名稱、程序描述、圖片引用等的詳細情況,其中一項當屬最重要的是指定程序的啟動頁。Microsoft Visual Studio 11 Express for Windows Developer Preview 可以自動導出程序清單,而還可以幫助你透過 JavaScript 來完成一系列任務,這些任務就是創建、編輯、打包、啟動與調試您的 Metro 風格應用程序。

在 VS 中開發Windows Metro Apps
Microsoft Visual Studio 為開發視窗程序的工具,也是開發 Metro式程序的第一個工具。它不但提供瞭 HTML、CSS、JavaScript、圖片編輯,以及用 JavaScript 來編輯程序清單等等此類一般開發任務,還包括整個開發周期的項目管理,涉及到源碼管理、整合構建(build)以及部署等的許多過程。有幾種 VS 版本的制定,我們這裡拿來使用的是免費的 Visual Studio 11 Express for Windows Developer Preview 預覽版本,在 Windows Software Development Kit (SDK)  for MetroApp 就有。安裝一遍後就等於安裝好瞭編譯、打包、部署的那些工具。VisualStudio 11 Express for Windows Developer Preview 本身提供若幹模板,如下圖所示:

 


最簡單的項目模板就是 Blank Application。運行之後生成  Visual Studio 11 Express for WindowsDeveloper Preview Metro style app using JavaScript 項目專屬文件(.wwaproj )。

 


現在打開預設的 default.html 的話,你會發現這幾乎全是空的內容,打開 js 目錄下的 default.js 也同樣如是。明顯這裡提供的是主幹文件,等著你創造些內容添加進去。

欲進行調試,你點擊“Debug > StartDebugging”就可以,它提供瞭開發者所熟悉的幾項調試工具:

調試器,打斷點、步進和監視 JS 數據和行為。

JS 控制臺窗口(JavaScript ConsoleWindow),與JS對象命令行交互的地方。

DOM瀏覽器窗口,觀察 HTML Document Object Model 或者元素樣式的地方。

模擬器(Simulator),在開發環境中模擬相關設備的事件。

采用 JavaScript 編寫的 MetroApp,除卻語法,在訪問 Windows 底層平臺的時候與其他語言無異。但根據 JavaScript 程序員的某些特性又應該有一些盡量預設的包,可以反復使用的設計,也就是說,Windows 類庫針對 JavaScript 特定提供瞭一組可復用的 JS 和 CSS 文件,以便在此基礎上更好地體現 Windows 新特性。VS 模板中已經包含瞭一系列的CSS 樣式規則表,以便提供劃一的WinApp的風格感官(look& feel)。最簡捷的方式就是通過項目模板文件引入,它會自動包含 WinJS 所需的文件。

Blank Application 項目文件雖說空無一物的,卻已包含有一定的樣式在內。Metro 風格之所以稱“風格”,應該說對樣式有一點要求的。樣式本身固然不是說要統一一致,因為許多情況下有你需要特定的佈局、動作個性化的設計。我們這裡就會舉一個例子。實際上,盡管所謂 Metro 風格的程序是新鮮事物,但過往的經驗仍值得學習研究。這裡為大傢介紹 Window 項目組裡的一位精於此道的開發人員,Raymond Chen。陳先生還是一本優秀圖書《The Old New Thing》的作者,同時他還有 Weblog 的。

陳先生的主要成果在於看到瞭一般人不容易看到Win平臺上的缺失之處,或曰美中不足,並且將文字連同示例Sample資源積極地發表在他博客上。這樣,都是 Win的 程序,我們可以通過這些素材展示新的構造方式。

由此,我們需要在他博文下載數據,才可以清楚哪些地方放置代碼。

調用WinJS.Application對象
項目模板生成的 default.html 文件默認加載 WinJS 所需最低要求的 JS 和 CSS 文件:


view plaincopy to clipboardprint?<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="utf-8" /> 
    <title>RssReader</title> 
    <!– WinJS references –> 
    <link rel="stylesheet" href="/winjs/css/ui-dark.css" /> 
    <script src="/winjs/js/base.js"></script> 
    <script src="/winjs/js/wwaapp.js"></script> 
    <!– RssReader references –> 
    <link rel="stylesheet" href="/css/default.css" /> 
    <script src="/js/default.js"></script> 
</head> 
<body> 
</body> 
</html> 
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>RssReader</title>
    <!– WinJS references –>
    <link rel="stylesheet" href="/winjs/css/ui-dark.css" />
    <script src="/winjs/js/base.js"></script>
    <script src="/winjs/js/wwaapp.js"></script>
    <!– RssReader references –>
    <link rel="stylesheet" href="/css/default.css" />
    <script src="/js/default.js"></script>
</head>
<body>
</body>
</html>加入我們自己的內容:


view plaincopy to clipboardprint?<body> 
  <h1>The Old New Thing</h1> 
  <p id="downloadStatus"></p> 
  <p id="posts"></p> 
</body> 
<body>
  <h1>The Old New Thing</h1>
  <p id="downloadStatus"></p>
  <p id="posts"></p>
</body>

上面分別是一標題和兩個 p 元素用於容器(placeholder)的作用,一個表示 RSS 下載時的狀態如何;另一個為我們獲取好 RSS 內容將來顯示的地方。別忘瞭,我們要用到陳兄的那些樣式文件:

view plaincopy to clipboardprint?/* default.css */ 
body 

    background-color: #fff; 
    color: #000; 
    font-family: Verdana; 
    padding: 8pt; 

 
a:link, a:visited, a:active 

    color: #700; 
    font-weight: inherit; 

 
h1 

    text-transform: none; 
    font-family: inherit; 
    font-size: 22pt; 

 
#posts 

    width: 99%; 
    height: 100%; 
    overflow: auto; 

 
.postTitle 

    color: #700; 
    font-size: 1.2em; 
    font-weight: bold; 

 
.postDate 

    color: #666; 
    font-size: 11pt; 

 
.postContent 

    font-size: medium; 
    line-height: 18px; 

/* default.css */
body
{
    background-color: #fff;
    color: #000;
    font-family: Verdana;
    padding: 8pt;
}

a:link, a:visited, a:active
{
    color: #700;
    font-weight: inherit;
}

h1
{
    text-transform: none;
    font-family: inherit;
    font-size: 22pt;
}

#posts
{
    width: 99%;
    height: 100%;
    overflow: auto;
}

.postTitle
{
    color: #700;
    font-size: 1.2em;
    font-weight: bold;
}

.postDate
{
    color: #666;
    font-size: 11pt;
}

.postContent
{
    font-size: medium;
    line-height: 18px;
}為瞭使得 MetroApp 與其他應用程序之間整合,Windows 提供瞭一組程序級別(app-level)的消息事件,均由 WinJS 完備封裝。那份 default.js 文件內就訂閱瞭(或登記瞭)程序的加載事件,程序於是開啟時便會執行:

view plaincopy to clipboardprint?// default.js  
(function () { 
  'use strict'; 
  // Uncomment the following line to enable first chanceexceptions.  
  // Debug.enableFirstChanceException(true);  
  WinJS.Application.onmainwindowactivated = function (e) { 
    if (e.detail.kind ===Windows.ApplicationModel.Activation.ActivationKind.launch) { 
    // TODO: 這裡寫啟動代碼  
   } 
 } 
  WinJS.Application.start(); 
})(); 
// default.js
(function () {
  'use strict';
  // Uncomment the following line to enable first chanceexceptions.
  // Debug.enableFirstChanceException(true);
  WinJS.Application.onmainwindowactivated = function (e) {
    if (e.detail.kind ===Windows.ApplicationModel.Activation.ActivationKind.launch) {
    // TODO: 這裡寫啟動代碼
   }
 }
  WinJS.Application.start();
})();首先你必須留意的一件事是,上述代碼都是處在一個自動執行的匿名函數中去運行的。這個是一個避免污染全局空間的 JavaScript 實踐方法,也是常常被人推薦的技巧。第二請留意 Debug 命名空間下調用的 enableFirstChanceException 方法。取消註釋的話就會向VS的調試器報告初期的 JavaScript 異常,確實十分方便的說。

前期工作完畢後,由 WinJS 命名空間下得到的對象,此時此刻,代碼就會訂閱在程序及其資源(如default.html)都已經加載好瞭才會觸發的事件。這是一個執行初始化絕佳的地方和時刻,待會我們會見到。對於觸發任何程序事件,你一定要讓程序曉得你已經準備好介紹消息瞭,也就是要執行方法是怎樣。onmainwindowactivated 的事件處理器(event handler)就是我們接著將要下載陳先生博客的絕佳之地。

運用 WinJS.xhr 下載數據
WinJS 命名空間下 xhr 函數提供一系列的可選項,以備下載來自 HTTP 協議的數據,普通文本和 XML 的都可以。xhr 為 XMLHttpRequest 的意思。WinJS 裡的 xhr 是一個包裝器,可送入許多參數,包括 HTTP 動詞(默認 GET)、HTTP 頭(默認 none )和獲取數據的源地址 URL。


view plaincopy to clipboardprint?WinJS.Application.onmainwindowactivated = function (e) { 
    if (e.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) { 
        // 開始下載  
        downloadStatus.innerText = "downloading posts…"; 
        WinJS.xhr({ url: "aspx”>http://blogs.msdn.com/b/oldnewthing/rss.aspx" }). 
            then(processPosts, downloadError); 
    } 

WinJS.Application.onmainwindowactivated = function (e) {
    if (e.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
        // 開始下載
        downloadStatus.innerText = "downloading posts…";
        WinJS.xhr({ url: "http://blogs.msdn.com/b/oldnewthing/rss.aspx" }).
            then(processPosts, downloadError);
    }
}

與 XMLHttpRequest 對象可選擇數據同步(synchronously)或異步(asynchronously)通訊的方式不同,WinJs.xhr()隻能選擇異步方式的通訊,其考量是為瞭防止同步通信而造成的UI阻塞。異步模式下編碼模型,能夠幫助你編寫更靈敏響應的應用程序。該例子中,我們不知道請求花去多少時間,於是,我們安排一個 p 來作為反饋下載狀態downloadStatus 的 UI 容器。

WinJS 對象下的異步函數稱作“允諾 promise”,表示將來發生的結果。Promise 對象暴露瞭then 函數,即 success 函數、failure 函數和 prgress 函數。調用 xhr() 立刻返回promise 對象,於是我們設定這三個函數的具體情形。


view plaincopy to clipboardprint?function processPosts(request) { 
  // 清空提示內容  
  downloadStatus.innerText = ""; 
 
  // 解析RSS  
  var items = request.responseXML.selectNodes("//item"); 
  if( items.length == 0 ) { downloadStatus.innerText = "下載帖子出錯。"; } 
 
  for( var i = 0, len = items.length; i < len; i++ ) { 
    var item = items[i]; 
    // 向 #posts p 添加數據  
    var parent = document.createElement("p"); 
    appendDiv(parent, item.selectNodes("title")[0].text, "postTitle"); 
    appendDiv(parent, item.selectNodes("pubDate")[0].text, "postDate"); 
    appendDiv(parent, item.selectNodes("description")[0].text, "postContent"); 
    posts.appendChild(parent); 
  } 

 
function appendDiv(parent, html, className) { 
  var p = document.createElement("p"); 
  p.innerHTML = html; 
  p.className = className; 
  parent.appendChild(p); 

 
function downloadError() { 
  downloadStatus.innerText = "下載帖子出錯。";  

function processPosts(request) {
  // 清空提示內容
  downloadStatus.innerText = "";

  // 解析RSS
  var items = request.responseXML.selectNodes("//item");
  if( items.length == 0 ) { downloadStatus.innerText = "下載帖子出錯。"; }

  for( var i = 0, len = items.length; i < len; i++ ) {
    var item = items[i];
    // 向 #posts p 添加數據
    var parent = document.createElement("p");
    appendDiv(parent, item.selectNodes("title")[0].text, "postTitle");
    appendDiv(parent, item.selectNodes("pubDate")[0].text, "postDate");
    appendDiv(parent, item.selectNodes("description")[0].text, "postContent");
    posts.appendChild(parent);
  }
}

function appendDiv(parent, html, className) {
  var p = document.createElement("p");
  p.innerHTML = html;
  p.className = className;
  parent.appendChild(p);
}

function downloadError() {
  downloadStatus.innerText = "下載帖子出錯。";
}當調用 HTTP 請求完畢,我們接著執行 processPosts 函數,其參數請求對象(a request object)一如 XMLHttpRequest 的那樣子,好比我們所關心的 responseXML 屬性,這個就是我們所請求的結果,也就是 RSS 數據。RSS 本身也是一份標準的 XML 文檔,於是我們可用XPath表達式選取 RSSfeed 裡面的節點,提取每項條目的標題、pubDta 和內容等等,最後渲染到 p 元素中。對於既往的 “TheOld New Thing” 讀者,看到程序輸出的樣子也會覺得是相似的。

 

有人會問,是否 xhr 函數會工作,因為web服務器的生產的頁面可能會失敗的。可以工作起來的原因在於使用 JavaScript Metro 式程序是運行在一個本地而且安全的上下文環境中,僅僅隻能使用程序所列明的請求和用戶所限定的功能。

接下來
你已經懂得 Metro App 怎麼用 JS 編寫的一點門徑瞭,但是所接觸到 API 隻是 Win 類庫中的一小部分。接下來將要預告的是《Extendingyour first Windows Metro style app》,與您一道發現 Windows Runtime 和控件的奧秘!

 作者“zhangxin的專欄”

You May Also Like