XML 用戶界面語言(XUL)開發簡介

原文:http://www.ibm.com/developerworks/cn/education/xml/x-xulintro/index.html


介紹:

XUL 是經過測試的真正的應用程序框架。事實上,最近發佈的 Firefox 3.0 不僅僅是由 XUL 構建的,但它提供了一個 XUL 運行時環境,允許任何 Firefox 用戶運行其他 XUL 應用程序。在本教程,您開始使用 XUL 進行編程,並學習一些幫助您開發 XUL 應用程序的工具。當您的 Web 開發技術得到提高時,您可以構建一個基於 XUL 的博客編輯器,並通過它使用 XUL 構建桌面應用程序。

開始之前

本教程適用於對桌面開發感興趣又不想學習太多新技術的有經驗的 Web 開發人員。XUL(發音與 cool 類似)使運用 Web 開發技能構建桌面應用程序變得很容易。它提供了豐富的 UI 部件集,這些工具使用的是所有 Web 開發人員都很熟悉的語法。使用 XUL,可以直接與 HTML 混合使用並可大量使用 JavaScript。

常用的縮寫詞

  • Ajax:異步 JavaScript + XML
  • API:應用編程接口
  • CSS:層疊樣式表
  • DOM:文檔對象模型
  • HTML:超文本標記語言
  • OS:操作系統
  • UI:用戶界面
  • XML:可擴展標記語言

XUL 是一種基於 XML 的語言,因此需要對 XML(特別是 XML 名稱空間)很熟悉。XUL 建立在我們熟悉和喜歡的 Web 技術之上:HTML、JavaScript 和 CSS。如果想有效地使用 XUL,需要對這些技術非常熟悉。使用 XPCOM 可以在很大程度上提高 XUL 應用程序的功能。這是一種跟分佈式計算技術(例如 CORBA/IDL 和 COM)類似的技術。如果熟悉這些技術,在學習 XPCOM 時將會有所幫助,但並不做硬性要求。

關於本教程

在本教程中,您將瞭解以下內容:

  • 瞭解 XUL 的起源和它在 Mozilla 項目中的應用。
  • 瞭解 XUL 的主要優點及其架構設計,以及如何使用現有的 Web 應用程序技術構建桌面應用程序。
  • 發現 Firefox 3.0 向 XUL 開發人員呈現的機會。
  • 深入瞭解 XUL 並編寫一個簡單的應用程序來創建、保存和發佈博客條目。這個基於 XUL 的博客編輯器提供了大量基礎的文本編輯功能,並允許您在本地保存草稿以便隨後重新加載並進行編輯。這個編輯器還加入了 XUL 的繪製功能,它允許用戶使用博客電子簽名。

先決條件

XUL 完全是開源的。爲了使用 XUL 進行開發以及實踐本教程的示例,您需要下載:

XUL 是什麼?

XUL 表示 XML 用戶界面語言(XML User Interface Language)。因爲是 XML,所以 XUL 是一種聲明性語言。XUL 提供了豐富的 UI 部件集合,這些部件可以加速開發進程。它是一種跨平臺的語言,可以在 Linux™ 上構建自己的 XUL 應用程序,然後在 Windows® 上運行該程序。XUL 大量使用了 Web 技術,例如 JavaScript 和 Cascading StyleSheets(CSS)。甚至可以將 HTML 直接集成到 XUL 應用程序中。深入瞭解 XUL 以及它爲何成爲備受關注的開發平臺。

XUL 歷史回放

XUL 與 Netscape 和 Mozilla Foundation 是同義詞。Netscape 瀏覽器最初的意圖是作爲一個跨平臺瀏覽器。這需要將 UI 框架從特定於操作系統的佈局和控制部件中分離出來。還需要一種方法讓這些分離出的元素和本地進程(用於網絡連接、文件 I/O 等)進行通信。要構建跨平臺且能夠和 HTML 和 Web 元素協作的應用程序,所有這些元素都非常必要。這個框架被叫做 XPFE(跨平臺前端),用於構建 Netscape Communicator 以及該系列的其他產品,如它的電子郵件和聊天客戶端。

您可能對 Netscape 公司的發展歷程比較熟悉。該公司在 1995 年的 IPO 標誌着 dot-com 輝煌的開始。直到 1998 年,公司雖然在財政上不太順利,但是取得了一些重要的技術成就。這些成就的核心就是 Mozilla 項目。這是從 Netscape Communicator 4.0 的代碼在獲得開源許可之後公開發行開始的。 事實證明這個代碼庫難於開發和維護,但是幸運的是,在他們的計劃中也有好的方面。Netscape 不但讓現有的 Communicator 開放源代碼,他們的下一代佈局引擎代碼也是開源的。這個佈局引擎將會成爲 Gecko。它的一個重要功能是支持聲明性的、基於 XML 的 UI 語言,也就是 XUL。

XUL:XML、JavaScript 和 CSS

XUL 是爲 Gecko 引擎構建的私有 UI 語言。它受到基於 Gecko 的 Web 瀏覽器開發人員的青睞。這是因爲它是構建在標準技術(例如 XML、JavaScript 和 CSS)之上的。

XUL 是一種 XML 語言。這使得它的語法很簡單,並且容易閱讀(和解析)!XUL 跟 HTML 有很多相似之處,因此 Web 開發人員對它很熟悉。它甚至允許 XHTML 元素與 XUL 部件混合使用。XUL 通過許多方式證明了 XML 非常適合於創建 UI 語言,由一些類似語言的出現也能看出這一點。例如,來自 Adobe™ 的 MXML(Adobe Flex 框架中的 UI 語言)和來自 Microsoft® 的 XAML(.NET 3.0 和 Windows Presentation Foundation 中的 UI 語言)。

當然,聲明性編程存在固有的侷限性。它不可避免地需要一些強制性編程。XUL 直接支持 JavaScript,而不是發明一種新語言或者創建一些基於 XML 的語法。現在 JavaScript 作爲一種編程語言經常受到負面的評價。JavaScript 被認爲是一種適合於非編程人員的語言,並且充滿了特定於瀏覽器的擴展和特性。然而,JavaScript 是一種強大的語言,它是 Web 應用程序開發的中堅力量。畢竟,JavaScript 是 Ajax 中的 “J”。它是一種函數性編程語言,但很容易以過程或面向對象的方式使用它。XUL 把 JavaScript 當作一種桌面編程語言,並將其放在最前面的位置。XUL 也非常依賴 JavaScript 中的 DOM 實現 — 畢竟,XUL 是基於 XML 的。

XUL 中用於 Web 開發的另一個重要方面是 CSS。CSS 已經成爲向 Web 頁面添加樣式的事實標準。它的層疊特性具有強大的功能和靈活性,這種特性允許將樣式應用於對象和子對象,同時也允許這些子對象根據需要重寫樣式。XUL 將這種功能和靈活性應用到桌面應用程序中。

JavaScript 和 CSS 的另一個共同之處是,其行爲都會根據瀏覽器的不同而變化。瀏覽器嗅探在 JavaScript 中非常常見,因此程序員可以在基於用戶使用的瀏覽器類型和版本的多個實現中編寫相同的函數。在 CSS 中條件樣式的使用也具有相同的特性。如果做過許多 Web 開發,就可能遭遇過這些瀏覽器怪僻。如果屬於這種情況,您將會喜歡上使用 XUL 編程。爲什麼呢? 因爲使用 XUL 時只需要考慮一個瀏覽器。就像在全世界都使用 Firefox 的情況下開發 Web 應用程序。

XPCOM 和 XBL

如果已經熟悉了 XUL,但可能又忘記了 XUL 的兩個重要功能:XPCOM 和 XBL。別擔心,現在將介紹這些技術,而且本教程稍後還顯示它的功能。您將會看到如何使用這些技術來增強所開發的應用程序的功能。首先介紹 XPCOM。

XPCOM(即跨平臺組件模塊)與 CORBA 和 Microsoft COM 類似。XPCOM 允許用一個 IDL 模塊(就像 Java™ 或者 C# 代碼中的接口或者 Web 服務的 WSDL)表示代碼庫。用其他語言編寫的應用程序可以通過 XPConnect 解釋程序來引用這個代碼庫。例如,Gecko 引擎的幾乎所有功能在 XPCOM 中都是公開的。這個引擎是用 C++ 編寫的,但是,有了 XPCOM,您就可以使用任何具備 XPCOM 支持的語言來利用庫中的任何資源,例如 JavaScript、C++、Perl 和 Python。例如,Gecko 的網絡庫是一個 XPCOM 組件,因此可以從 JavaScript 訪問它。

XPCOM 可以使您利用來自許多庫的功能。這是 XUL 中重複出現的一個主題:爲開發人員提供他們需要的所有構建塊,並讓他們專注於構建自己的應用程序。並且,XUL 提供了一個大型的 UI 部件庫。它也提供了一種方法,此方法使用 XML 綁定語言(XML Binding Language,XBL)來更改這些部件的行爲和功能。使用 XBL 可以爲部件創建自己的行爲,然後將這種行爲綁定到部件。如何綁定呢?這是 XBL 一個很聰明的部分。使用一個 CSS 選擇器來進行綁定。使用選擇器選擇一個或多個部件,然後用特定的 CSS 特性 -moz-binding 來指定到包含此行爲的 XUL 文件的 URL。

廣泛採用 XUL

從純技術的立場上看,XUL 是一個用於跨平臺應用程序開發的有趣框架。也許僅僅是一個有趣的技術框架。但是另一個產品證明它不僅僅是一個技術框架,那就是 Firefox。XUL 是通過重寫 Netscape 發展起來的,這種重寫是通過讓 Netscape 更加模塊化來實現的。相同的思想也應用在了 Mozilla Firefox Web 瀏覽器開發中。

創建 Firefox 的動機是構建使用 Gecko 引擎支持的精簡瀏覽器。這隻有使用 Gecko 的模塊化結構纔可能實現。結果被證明是成功的。至 2008 年 9 月,Firefox 已佔到了 19% 的全球市場份額,擁有 1.4 億用戶。它還獲得了主流媒體(例如,Forbes 和 PC World)的好評。

Firefox 最初的成功很大程度上來自於它的快速呈現引擎(Gecko)以及它在安全方面的優越性。Firefox 持續獲得成功和採用的一個原因在於它的擴展系統。該擴展系統使開發人員能夠輕鬆地在 Firefox 之上構建專有功能。針對 Firefox 的擴展已變得非常流行。在編寫此教程時,Mozilla 的官方擴展列表上已有超過 1800 種擴展。而且,許多其他的擴展沒有收錄到 Mozilla 的官方列表中。

Firefox 擴展的關鍵在於,創建擁有強大功能的擴展非常簡單。這很簡單:可以用 XUL 編寫 Firefox 擴展,就像 Firefox UI 一樣。它們可以利用 XUL 強大的覆蓋特性。有了覆蓋,就可以定位一部分現有的 UI 組件,並插入自己製作的新的 UI 組件。圖 1 顯示安裝了一些擴展的 Firefox。

圖 1. 帶有擴展的 Firefox
帶有幾個可見擴展的 Firefox 瀏覽器

5 個紅色的矩形表示來自擴展的 UI 元素。導航工具欄內包含一個大工具欄和 3 個按鈕。此外,狀態欄上還有幾個圖標。單擊這些圖標將打開大的對話框,每個對話框都將用戶界面和菜單、選項卡等關聯起來。這表明 Firefox 擴展本身就是強大的應用程序,因爲它們使用 Firefox,所以就使用了 XUL 作爲開發平臺。

超越 Firefox:XULRunner

Firefox 將 XUL 奉獻給了數百萬用戶。但 XUL 並不僅僅是一個創建 Firefox 及其擴展的技術。Firefox 的用於電子郵件的姊妹應用程序是 Mozilla Thunderbird。這個程序也是用 XUL 編寫的,並且擁有一個活躍的擴展庫,通過 XUL 覆蓋實現。儘管它沒有 Firefox 那麼流行,但是它擁有 5 百萬活動用戶。機會在於您的 ISP 提供了一些指令,如果他們爲您提供了電子郵件帳戶,那麼這些指令可用來將 Thunderbird 設置爲該帳戶的一個 IMAP 或者 POP 客戶機。XUL 並不侷限於 Mozilla 項目。它也被設計爲一個框架,用於跨平臺桌面應用程序的開發。然而,像 Firefox 和 Thunderbird 這樣的應用程序是圍繞 Gecko 引擎構建的。它們需要 Gecko 引擎來呈現 HTML 頁面和 HTML 電子郵件,但是 Gecko 引擎也呈現了它們的 UI。一般而言,大多數桌面應用程序不需要呈現 HTML,因此它們也不需要 Gecko 引擎。但是沒有 Gecko,它們如何使用 XUL 呢?答案就在於 XULRunner。XULRunner 在 Gecko 引擎之外提供純 XUL 運行時環境,從而延續了 Gecko 的模塊化特性。這允許您構建應用程序代碼中直接包含 XULRunner 的應用程序。

Firefox 3.0

構建運行在 XULRunner 上的應用程序時,一個不足之處是需要在應用程序中包含 XULRunner。這導致應用程序大概增加了 12MB。這對於像 Songbird 這樣的媒體播放器來說不算什麼。畢竟現在大多數媒體播放器都比較大。對於像 Joost 這樣的流視頻應用程序來說也不算什麼。畢竟,流視頻需要快速連接,因此對大多數 Joost 用戶來說,額外的 12MB 可能很快就能下載下來。但是對於許多應用程序來說,XULRunner 運行時跟應用程序本身一樣大,或者更大。這使得 XULRunner 不再那麼有吸引力。

然而,您不再需要將 XUL 應用程序和 XULRunner 捆綁在一起。因爲 Firefox 3.0 已經構建在 XULRunner 之上。Firefox 和 XULRunner 使用相同的核心庫和 libxul,這允許任何 XUL 應用程序使用 Firefox 作爲 XUL 運行時,而不是 XULRunner。到 2008 年 9 月份爲止,全世界一共有 1.4 億 Firefox 用戶。在這些用戶當中,有 68% 已經更新到 Firefox 3.0。這相當於超過 9500 萬用戶已經安裝了 XUL 運行時。即您的 XUL 的潛在使用者多達 9500 萬。這個事實也適用於 Firefox 開發人員。在本教程後面的詳細論述中,您將看到,只需添加 -app 命令行參數,就可以將 Firefox 3 作爲任何 XUL 應用程序的 XUL 運行時。

XUL 開發

我們已經瞭解了 XUL 的起源和發展情況。更重要的是,已經明白了可以用 XUL 和它提供給開發人員的有利時機來做什麼。我希望您現在已經迫不及待想要進行 XUL 開發了。首先,您將設置一個 XUL 開發環境。

XUL 開發環境

在瞭解 XUL 時已經注意到,可以使用 XUL 做許多不同的事情。因此,沒有絕對適合的 XUL 開發環境。一般而言,您將會基於 XUL 的不同用途配置環境。

基本原理

首先,XUL 是一種基於 XML 的 UI 語言。要創建 XUL 文件,只需要能夠創建 XML 文件。您或許想編寫一些腳本,以使應用程序具有交互性,因此需要編寫一些 JavaScript。要創建 XML 和 JavaScript 文件,不需要特定的編譯器。XUL 運行時將會解釋這些文件。但是,您還需要做一些事情。

也許最重要的一點是 XUL 應用程序的目錄結構。在本教程中將會創建一個稱爲 xulblogger 的應用程序。圖 2 顯示了該程序的目錄結構。

圖 2. XUL 應用程序的目錄結構
XUL 應用程序的目錄結構

圖 2 展示了 3 個重要文件。首先是 application.ini。這個文件必須放在應用程序的根目錄下。它最重要的用途是告訴 XUL 運行時它需要什麼版本的運行時,如清單 1 所示。

清單 1. application.ini 文件
[App]
Vendor=developerworks
Name=xulblogger
Version=0.2
BuildID=20080924

[Gecko]
MinVersion=1.9

下一個重要的配置文件是 chrome.manifest。這個文件必須放在 chrome 目錄中。通常需要在 chrome 目錄中包含一個子目錄,用於存放所有的 XUL 文件。可以根據自己的喜好爲其命名。它在清單 2 中叫做 “xulblogger”,但是許多應用程序將其命名爲 “content”。chrome.manifest 用於告訴 XUL 運行時如何找到您的文件。清單 2 顯示了 chrome.manifest 的一個示例。

清單 2. chrome.manifest 文件
content xulblogger file:xulblogger/

可以看到,這個文件只包含一行簡單的配置。最後一個重要的文件是 prefs.js。此文件必須放在 /defaults/preferences 目錄下。它告訴運行時首先需要載入什麼樣的 XUL 文件,如清單 3 所示。

清單 3. prefs.js 文件
pref("toolkit.defaultChromeURI", "chrome://xulblogger/content/home.xul");

您或許還注意到了 extensions 和 updates 目錄。不用擔心這兩個目錄,XUL 運行時將會自動創建它們。

關於這裏描述的結構,還有一個需要注意的事情是:XUL 應用程序通常是通過創建頂級目錄的一個 JAR 文件來部署的。如果安裝了 Java 開發工具,可以使用 Java jar 命令來創建 JAR,或者可以直接將目錄壓縮,然後將其擴展名由 .zip 更改爲 .jar。

Eclipse 和 XUL

作爲一個有經驗的開發人員,您可能已經知道 Integrated Development Environment (IDE) 的價值。您可能考慮 IDE 是否可用於 XUL。這有大量的選擇,有幾個可用的 XUL IDE 構建在非常通用的 Eclipse 平臺之上。對於 XML、JavaScript 和 CSS 編輯,XULBooster(參見 參考資料)使用流行的 Eclipse Web Tools Platform。它還使用 XULRunner 來執行應用程序,並且連接到 XULRunner 進行調試。圖 3 顯示了 XULBooster 的屏幕截圖。

圖 3. XULBooster
XULBooster 應用程序的屏幕截圖

另一個選項是 Spket。這可以作爲獨立的 IDE 獲得,也可以作爲 Eclipse 插件獲得(參見 參考資料)。這不是特定於 XUL 的 IDE,但它提供了幾個對 XUL 開發人員非常有用的特性。Spket 提供 XUL 和 XBL 控件,以及 XUL 和 JavaScript 的詳細代碼。圖 4 展示了 Spket 的屏幕截圖。

圖 4. Spket IDE
Spket IDE 屏幕截圖

不管選擇什麼樣的編輯器,您最終都需要運行代碼。再聲明一下,您有很多選擇,其中一些還使用 Firefox。

運行 XUL 應用程序

可以選擇 3 種方式來運行 XUL 應用程序:

  • 對於簡單的 UI 測試(chrome 測試),只需要打開 Firefox(或者任何基於 Mozilla 的瀏覽器,例如 Seamonkey 或者 Mac OSX 中的 Camino)中的 .xul 文件。這種方法對於測試非常簡單的應用程序很有用。Firefox 不知道 chrome.manifest,因此它也不會找到您從主要的 chrome 引用的其他 chrome 文件。
  • 下一個測試方法是使用 XULRunner。可以下載一個 XULRunner 安裝工具,或者從源文件構建 XULRunner。如果從源文件構建 XULRunner,同時也會從源文件構建 Gecko SDK。一旦安裝了 XULrunner,只需要將你的 application.ini 文件位置傳遞給它就行了。XULrunner 將會讀取此文件,以及前面提到的其他兩個配置文件,以初始化應用程序。
  • 最後,您可以使用 Firefox 3.0 作爲 XUL 運行時。它的功能和 XULRunner 很相似。如果通過 xulrunner <path_to_app>/application.ini在命令行使用 XULRunner 調用了您的應用程序,那麼要使用 Firefox 3.0 的話,就需要使用 firefox -app <path_to_app>/application.ini 命令。

博客編輯器

XUL 開發環境準備就緒後,就可以使用 XUL 構建一個示例應用程序了。我們將會構建一個簡單的博客編輯器,這個編輯器可以創建並預覽博客條目。也可以在本地保存博客條目並在以後重新載入。編輯器將會使用 XUL 作爲用戶界面,並使用 JavaScript 來完成每件事情。開始之前,先設置用戶界面。

博客編輯器的用戶界面

這是最令開發人員憎惡的應用程序部分。創建用戶界面非常繁瑣,但是 XUL 使這變得很容易。XUL 有許多控件,用於創建部件和指定佈局。請看清單 4 中定義的一個簡單 UI。

清單 4. XUL(/chrome/xulblogger/home.xul)中定義的 UI
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<xul:window id="xulblogger" title="Create Blog Entry" orient="horizontal"
     align="start" xmlns="http://www.w3.org/1999/xhtml" height="1000"
     xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
     <xul:script src="blog.js"/>
     <xul:script src="json.js"/>

     <xul:vbox height="800">
          <xul:hbox>
               <xul:label value="Name of entry"/>
               <xul:textbox id="name" multiline="false" cols="70"/>
                 <xul:label value="Signature"/>
                  <canvas id="canvas" width="300" height="10" 
style="border:1px solid gray;">
                </canvas>
          </xul:hbox>          
          <xul:textbox id="entry" multiline="true" rows="10" cols="80"/>
          <xul:hbox>
               <xul:label value="Tags"/>
               <xul:textbox id="tags" cols="80" multiline="false"/>
               <xul:button id="saveBtn" class="btnClass" label="Save" />
               <xul:button id="previewBtn" label="Preview" onclick="preview()"/>
          </xul:hbox>
          <xul:hbox>
                 <xul:label value="Publish Date"/>
                 <xul:datepicker type="grid" value="{new Date()}"/>              
          </xul:hbox>

          <div id="preview"></div>
     </xul:vbox>     
     <xul:script src="canvas.js"/>
     <xul:script>read();</xul:script>
</xul:window>

這是一個很簡單的 UI。XUL vbox 和 hbox 組件使佈局變得很簡單。按從左到右的順序,vbox 在垂直方向上依次排列各個對象,而 hbox 在水平方向排列各個對象。UI 有 2 個標籤、3 個文本框(包括一個多行文本框)和 2 個按鈕。這些都是非常直觀的代碼;即使您以前沒有見過 XUL,也能知道這些代碼的用途。該 UI 還使用了幾個更高級的控件。它使用了一個 datepicker 控件。這是在 Firefox 3 中引入的新控件。注意使用 JavaScript 表達式初始化 datepicker 的開始日期(值屬性)的方式。此外,還需要注意使用戶可以在 XUL 控件內部繪製的畫布控件,該控件使他們能夠對博客發佈進行電子簽名。仔細研究這個控件的工作原理。

畫布控件

畫布控件並不是一個真正的 XUL 控件。它是一個 HTML 控件。雖然 Safari 瀏覽器引入了畫布元素,但 Firefox 仍然支持它???Web Hypertext Application Technology Working Group (WHATWG) 使畫布成爲將要推出的 HTML 5 規範的一部分。不過,當前所有版本的 Internet Explorer® 都沒有支持它,包括 Internet Explorer 8 的 beta 版。因此,大部分 Web 開發人員不能利用這一特性,除非他們的用戶不使用 Internet Explorer。然而,進行 XUL 開發時,這並不是什麼問題。通過 XUL 應用程序,您可以使用任何 Firefox 支持的 HTML、CSS 和 JavaScript。它只在 XUL 應用程序的內部執行,而不是 Web 瀏覽器。因此,您不用擔心它不能在 Internet Explorer 上使用。

畫布控件允許應用程序在控件內部繪製。這種繪製通常使用 JavaScript 自動完成。同樣,要使用戶能夠進行繪製,您可以使用 JavaScript 監聽帶有該控件的用戶交互,然後使用畫布 API 進行繪製。在這個應用程序中,canvas.js 腳本完成了所有這些任務。清單 5 展示了該文件的內容。

清單 5. JavaScript 畫布控件代碼
// courtesy of Mozilla's Mark Finkler
// http://starkravingfinkle.org/blog
  function  Scribbler_init() {
    Scribbler.init();
  }
 
  var Scribbler = {
    canvas : null,
    ctx : null,
    drawing : false,
 
    init : function() {
      this.canvas = document.getElementById("canvas");
      this.ctx = this.canvas.getContext("2d");
      this.drawing = false;
 
      this.canvas.addEventListener("mousedown", this.doDrawStart, false);
      addEventListener("mouseup", this.doDrawStop, false);
      this.canvas.addEventListener("mousemove", this.doDrawUpdate, false);
    },
 
    doDrawStart : function(event) {
      // Calculate the position of the mouse over an element. To do this, subtract
      // the position of the element the mouse is over from the mouse position. The
      // element's position can be determined from its boxObject.
      // We are using the <box> container as a XUL wrapper 
      // for the HTML <canvas>
      var offsetX = (event.clientX - event.target.parentNode.boxObject.x);
      var offsetY = (event.clientY - event.target.parentNode.boxObject.y);
 
      Scribbler.ctx.beginPath();
      Scribbler.ctx.moveTo(offsetX, offsetY);
      Scribbler.drawing = true;
    },
 
    doDrawStop : function(event) {
      if (Scribbler.drawing) {
        Scribbler.ctx.closePath();
        Scribbler.drawing = false;
      }
    },
 
    doDrawUpdate : function(event) {
      if (Scribbler.drawing) {
        // Calculate the position of the mouse over an element. To do this, subtract
        // the position of the element the mouse is over from the mouse position. The
        // element's position can be determined from its boxObject.
        // We are using the <box> container as a XUL wrapper 
        // for the HTML <canvas>
        var offsetX = (event.clientX - event.target.parentNode.boxObject.x);
        var offsetY = (event.clientY - event.target.parentNode.boxObject.y);
 
        Scribbler.ctx.lineTo(offsetX, offsetY);
        Scribbler.ctx.stroke();
      }
    },
 
    doDrawClear : function() {
      this.ctx.fillStyle = "#fff";
      this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    }
  };

Scribbler_init();

清單 5 中的代碼創建了一個 Scribbler 對象。這個對象監聽畫布控件內的 3 個事件:mousedownmousemove 和 mouseup。這些事件在用戶按下鼠標、移動鼠標然後鬆開鼠標時觸發。這個代碼僅在這些事件期間捕捉鼠標的位置並確定相對的位置,然後在這些點之間繪製線條。瞭解簽名控件的工作原理之後,就可以測試這個應用程序了。

運行應用程序

您可以使用 XULRunner 或 Firefox 啓動這個應用程序。圖 5 是博客編輯器 UI 的屏幕截圖。

圖 5. 博客編輯器 UI
博客編輯器 UI 的屏幕截圖

您或許在 清單 4 中注意到,有一個叫做 preview 的 HTML div。這是一個用來預覽博客條目的 HTML 區域。它讓用戶進入正常的 HTML,然後單擊 preview 按鈕查看外觀。但是如何將編輯器中的 HTML 轉換成在 preview 區域上顯示的 HTML。回頭看看 XUL 代碼,您將看到一個preview() 函數,它在用戶單擊 Preview 按鈕時被調用。清單 6 展示了在 blog.js 文件中的 preview() 函數。

清單 6. preview() 函數
function preview(){
    var preview = document.getElementById("preview");
    preview.innerHTML = document.getElementById("entry").value;
    var sigImg = document.createElement("img");
    sigImg.src = document.getElementById("canvas").toDataURL();
    preview.appendChild(sigImg);
}

這對於做過很多 HTML/JavaScript 處理的人來說,應該很熟悉,尤其是做過 Ajax 開發的人。這正是我們習慣編寫的 JavaScript 類型:使用元素的 ID 來獲得元素,然後使用 HTML 元素的 innerHTML 屬性將其轉儲到 HTML 中。您還需要注意如何從用戶的簽名中獲取數據,然後將其轉換成一個數據 URL。這使您可以將簽名顯示爲圖像。該數據 URL 是一張採用 64 位編碼的 PNG 格式的圖像。甚至可以將這些數據保存到本地文件中。畫布元素還有很多其他功能,並且可以在 XUL 應用程序中任意使用。

關於名稱空間

您或許注意到,清單 4 聲明瞭 2 個名稱空間。一個是 xul 名稱空間,在 XUL 文件中創建每個 UI 控件時使用。同時,還有一個指向 HTML 模式的默認名稱空間。這跟大多數 XUL 文件的設置方式是相反的。通常,XUL 名稱空間是默認的,並且任何 HTML 元素都需要添加前綴。但是在這個例子中,我們想讓用戶將 HTML 輸入到博客編輯器中。也可以解析預覽窗格的內容,然後添加合適的 html 前綴(或者想要使用的其他前綴)。在這些內容被轉儲前,將此前綴作爲標記的一部分。

修飾應用程序外觀

您的應用程序看起來很單調,可以很輕鬆地爲其添加更好的外觀。所需的只是一個小小的 CSS,就像在 Web 頁面上使用一樣。可以將類和/或 ID 添加到每個部件上。可以爲這些類編寫 CSS 選擇器,或者用特定部件的 ID 爲其編寫 CSS 選擇器,就像創建 Web 頁面時的操作一樣。

保存博客條目

現在把博客條目保存到本地文件系統中。XUL 允許通過 JavaScript 訪問本地 I/O 操作。如果熟悉 JavaScript,就會知道它沒有內建這些功能。這正是 XPCOM 發揮作用的地方。請看一下清單 7。

清單 7. 在 JavaScript 中啓用本地 I/O
function save() {
    try {
        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
    } catch (e) {
        alert("Permission to save file was denied.");
    }
    var file = Components.classes["@mozilla.org/file/local;1"]
        .createInstance(Components.interfaces.nsILocalFile);
    file.initWithPath( savefile );
    if ( file.exists() == false ) {
        alert( "Creating file... " );
        file.create( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 420 );
    }
    var outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
        .createInstance( Components.interfaces.nsIFileOutputStream );
    /* Open flags 
    #define PR_RDONLY       0x01
    #define PR_WRONLY       0x02
    #define PR_RDWR         0x04
    #define PR_CREATE_FILE  0x08
    #define PR_APPEND      0x10
    #define PR_TRUNCATE     0x20
    #define PR_SYNC         0x40
    #define PR_EXCL         0x80
    */
    /*
    ** File modes ....
    **
    ** CAVEAT: 'mode' is currently only applicable on UNIX platforms.
    ** The 'mode' argument may be ignored by PR_Open on other platforms.
    **
    **   00400   Read by owner.
    **   00200   Write by owner.
    **   00100   Execute (search if a directory) by owner.
    **   00040   Read by group.
    **   00020   Write by group.
    **   00010   Execute by group.
    **   00004   Read by others.
    **   00002   Write by others
    **   00001   Execute by others.
    **
    */
    outputStream.init( file, 0x04 | 0x08 | 0x20, 420, 0 );
    //var output = document.getElementById('blog').value;
    var output = serialize();
    var result = outputStream.write( output, output.length );
    outputStream.close();
}

需要做的第一件事是啓用 XPConnect。這允許使用 XPConnect 來處理 XPCOM 組件。在這個示例中,使用的是 Mozilla 的 org.file.local類。然後能夠調用這個對象上的方法,就像對象是在本地運行一樣。您或許還注意到這裏調用的 serialize() 方法,它將輸入的數據序列化成一個 JSON 串,如清單 8 所示。

清單 8. 序列化數據
function serialize(){
    var name = document.getElementById("name").value;
    var entry = document.getElementById("entry").value;
    var tags = document.getElementById("tags").value;
    var pubDate = document.getElementById("pubDate").value;
    var sigData = 
        document.getElementById("canvas").toDataURL();
    var obj = { "name" : name, "entry" : entry, "tags" : tags, 
        "pubDate":pubDate, "sigData":sigData};
    var str = obj.toJSONString();
    return str;
}

此外,您使用了普通的 JavaScript DOM 功能來獲得所創建的表單外的數據。然後創建一個 JavaScript 對象,封裝保存博客條目的屬性。使用來自 json.org 的 JSON 庫,可以將 JavaScript 對象轉換爲一個字符串。然後將這個字符串寫入到文件中。那麼這個文件是什麼呢?清單 9 顯示了一些代碼,這些代碼決定將要保存什麼文件。

清單 9. 決定保存文件的代碼
var savefile = "blogentry.txt";

try {
     netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
     alert("Permission to save file was denied.");
}
// get the path to the user's home (profile) directory
const DIR_SERVICE = new Components.Constructor("@mozilla.org/file/
                                        directory_service;1","nsIProperties");
try { 
     path=(new DIR_SERVICE()).get("ProfD", Components.interfaces.nsIFile).path;
} catch (e) {
     alert("error");
}
// determine the file-separator
if (path.search(/\\/) != -1) {
     path = path + "\\";
} else {
     path = path + "/";
}
savefile = path+savefile;

所有這些代碼所做的就是確定用戶的主目錄。因此應用程序保存的任何數據都將存儲在 ~/blogentry.txt 中。另外,可以使用 XPCOM 訪問一些豐富的功能,這些功能是 XUL 框架的一部分。這些代碼也會做一些 OS 嗅探,以避免使用錯誤路徑保存數據而導致的問題。

因此可以將數據寫入磁盤,但是如何從磁盤讀取數據呢?您或許在 圖 1 注意到,在啓動時調用了一個 JavaScript 函數 read()。清單 10 顯示了此函數的代碼。

清單 10. read() 函數
function read() {
     try {
          netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
     } catch (e) {
          alert("Permission to read file was denied.");
     }
     var file = Components.classes["@mozilla.org/file/local;1"]
          .createInstance(Components.interfaces.nsILocalFile);
     file.initWithPath( savefile );
     if ( file.exists() == false ) {
          alert("File does not exist");
     }
     var is = Components.classes["@mozilla.org/network/file-input-stream;1"]
          .createInstance( Components.interfaces.nsIFileInputStream );
     is.init( file,0x01, 00004, null);
     var sis = Components.classes["@mozilla.org/scriptableinputstream;1"]
          .createInstance( Components.interfaces.nsIScriptableInputStream );
     sis.init( is );
     var output = sis.read( sis.available() );
     deserialize(output);
}

再一次藉助 XPConnect 使用通過 XPCOM 部署的本地文件庫。在這個例子中,使用來自此組件的相應的讀取 API,以讀取寫入 清單 7 的文件。這時,也會調用 deserialize() 方法,如清單 11 所示。

清單 11. deserialize() 函數
function deserialize(input){
    var obj = input.parseJSON();
    document.getElementById("name").value = obj.name;
    document.getElementById("entry").value = obj.entry;
    document.getElementById("tags").value = obj.tags;
    document.getElementById("pubDate").value = obj.pubDate;
}

此函數再一次使用了 JSON 庫。此時它獲取了從本地文件讀取的串,並將其轉換到一個 JavaScript 對象中。然後可以用這個對象的屬性來設置 UI 控件中的值。惟一的例外是畫布簽名。您將它保存爲一個數據 URL,但該格式不能在編輯時重載。您可以用圖像標記顯示它,就像在 Preview 中一樣。

發佈條目

您的應用程序可以從本地磁盤讀取和寫入條目,而且可以在博客條目中預覽 HTML 佈局。下一個邏輯步驟是將它與 Web 服務連接,以在線發佈博客條目。爲此,使用 XPConnect 和 XPCOM 來訪問包含在 XUL 中的連網 API。如果使用 XMLHttpRequest 將所有內容寫入到瀏覽器中時,也可以執行上面的方法。這在 XUL 中所有的 JavaScript 函數中都是可行的,就像運行在瀏覽器中的所有 JavaScript 文件一樣。清單 12 顯示了實現此功能的一些代碼。

清單 12. 使用 XMLHttpRequest()
var xhr = new XMLHttpRequest();
function publish(){
     var url = "http://some.blogService.com/sendBlog"; // replace this obviously
     var serializedEntry = serialize();
     xhr.onreadystatechange = processResponse;
     xhr.open("POST", url, true);
      xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
     // set-up authentication headers and/or parameters
     // then send the request
     xhr.send(serializedEntry);
}

function processResponse(){
     // check for readyState == 'loaded'
     if (xhr.readyState == 4){
          if (xhr.status == 200){
               alert("Your blog entry was published!");
          } else {
               alert("Sorry there was an error");
          }
     }
}

在 清單 12 中需要注意的關鍵是,其中的代碼跟實現從 Web 頁面調用 Ajax 而編寫的代碼類型相同。最大的區別在於,不用擔心 Internet Explorer 的不同版本,或者甚至是 Firefox(或者 Safari、Opera 和其他瀏覽器)的不同版本。這在很大程度上簡化了 XMLHttpRequest 的使用。

更多的 XUL 開發

您僅僅使用 XUL 創建了一個簡單的桌面應用程序。如果您是一個 Web 應用程序開發人員,那麼 XUL 可以爲您開啓一個嶄新的世界。您還應該瞭解 XUL 開發的更多具體類型。首先是爲 Firefox 和其他基於 Mozilla 的瀏覽器編寫擴展。

Firefox 擴展開發

XUL 開發的一個最流行的類型就是編寫 Firefox 擴展。用 XUL 進行桌面開發和擴展開發有兩個主要的區別。最重要的一個是,擴展需要安裝清單文件。這個文件必須命名爲 install.rdf,並且必須放在應用程序的根目錄下。這個文件是包含有關擴展的重要元數據的存儲庫。您可能想爲擴展創建一個圖標。這可以通過創建圖標文件並將其放到 /chrome/icons/default/ 目錄中來實現。在 Windows 中,這是一個 .ico 文件,而在 Linux 中爲一個 .xpm 文件。它的名稱和主窗口的 ID 匹配。因此,如果主窗口的 ID 爲 “xulBloggerMain”,那麼就需要一個 xulBloggerMain.ico 和一個 xulBloggerMain.xpm。

XPCOM 開發

如果做過足夠多的 XUL 開發,也許想創建自己的 XPCOM 組件。爲此,需要 Gecko SDK。可以下載此文件並當作一個二進制文件,或者從頭構建。SDK 包含大量 C++ 頭文件和用於基礎 Gecko XPCOM 組件的 IDL 文件,以及用於創建 XPCOM 組件的命令行工具。要開發 XBL,將 XUL 控件綁定到您開發的 XPCOM 組件上,Spket IDE 將會非常有用


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章