XML 使用者介面語言(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
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 應用程式的目錄結構
圖 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
另一個選項是 Spket。這可以作為獨立的 IDE 獲得,也可以作為 Eclipse 外掛獲得(參見 參考資料)。這不是特定於 XUL 的 IDE,但它提供了幾個對 XUL 開發人員非常有用的特性。Spket 提供 XUL 和 XBL 控制元件,以及 XUL 和 JavaScript 的詳細程式碼。圖 4 展示了 Spket 的螢幕截圖。
圖 4. 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 個事件:mousedown
、mousemove
和 mouseup
。這些事件在使用者按下滑鼠、移動滑鼠然後鬆開滑鼠時觸發。這個程式碼僅在這些事件期間捕捉滑鼠的位置並確定相對的位置,然後在這些點之間繪製線條。瞭解簽名控制元件的工作原理之後,就可以測試這個應用程式了。
執行應用程式
您可以使用 XULRunner 或 Firefox 啟動這個應用程式。圖 5 是部落格編輯器 UI 的螢幕截圖。
圖 5. 部落格編輯器 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(
)
相關推薦
XML 使用者介面語言(XUL)開發簡介
開始之前 本教程適用於對桌面開發感興趣又不想學習太多新技術的有經驗的 Web 開發人員。XUL(發音與 cool 類似)使運用 Web 開發技能構建桌面應用程式變得很容易。它提供了豐富的 UI 部件集,這些工具使用的是所有 Web 開發人員都很熟悉的語法。使用 XUL,可以直接與 HTML 混合使用並可
C程式設計語言(六)UNIX系統介面
系統介面和標準的C庫,是不同的兩個範疇。 標準C庫,各個平臺的C編譯器都應該支援,具有跨平臺的性質。 系統介面則是系統相關的,UNIX的系統介面,在Win上就肯定不能用。 那麼兩者是不是還有聯絡呢,其實是的。標準C庫,跟系統相關的部分,不也是通過系統呼叫/系統介面實現的麼。所以系
C語言面向物件程式設計:面向介面程式設計(4)
Java 中有 interface 關鍵字,C++ 中有抽象類或純虛類可以與 interface 比擬,C 語言中也可以實現類似的特性。 在面試 Java 程式設計師時我經常問的一個問題是:介面和抽象類有什麼區別。 &n
智慧合約基礎語言(一)——Solidity概述和開發工具的使用
一、目錄 ☛Solidity的概述 ☛Solidity開發工具remix的使用 ☛Solidity本地開發工具remix安裝 二、Solidity的概述 Solidity 是一門為實現智慧合約而建立的高階程式語言。這門語言受到了 C++,Python
選用python自動化指令碼開發語言(一)
學習點:python和lua都是網遊中游戲最常用的二種指令碼語言。這裡講述的是python。 python程式碼更優雅和美觀,雖然損耗的是一點執行效率。這裡講1個 staf 的概念。staf 是IBM的自動框架。 這裡先講關於這部分的控制端第一層設計,功能對映到物件。 T
DirectUI介面程式設計(三)從XML檔案中載入介面
Duilib支援xml介面佈局,使得介面設計與邏輯處理相分離,本節介紹如何從xml檔案中載入介面元素。 我們需要以下幾個步驟: 建立並初始化CPaintManagerUI物件。 建立CDialogBuilder物件,呼叫CDialogBuilder物件的C
Spring Security技術棧開發企業級認證與授權(九)開發圖形驗證碼介面
在設計登入模組的時候,圖形驗證碼基本上都是標配,本篇部落格重點介紹開發可重用的圖形驗證碼介面,該介面支援使用者自定義配置,比如驗證碼的長度、驗證碼圖形的寬度和高度等資訊。 本文的目標是開發一個圖形驗證碼介面,該驗證碼支援使用者自定義長度,以及生成圖片後
PHP開發APP介面全過程(一)
1、學習要點: 伺服器端 –> 資料庫|快取 –>呼叫介面 –>客戶端 2、APP介面介紹:(PHP開發APP介面) PHP面向物件的介面:抽象類,interface定義 ==>interface.php
微信公眾平臺開發之相同關鍵詞不同回覆-php語言(五)
1.微信公眾平臺開發必備的一個基礎功能是關鍵詞回覆。但有時候我們的微信公眾號的需要關鍵詞回覆的模組比較多時,例如“預設情況下的關鍵詞回覆,查詢天氣時的關鍵詞回覆,或者成語接龍等文字遊戲的關鍵詞回覆”,模組多時不可避免的會出現關鍵詞衝突的問題。 2.在每天蒐集公
iOS開發mock介面資料(三)之實戰教程
接上篇文章,我對一些匹配的方法進行了介紹,這篇文章主要為大家說說實際操作的步驟。 plist檔案如何配置 plist檔案的配置對於APIMock的至關重要,直接關係到能否運用AOP程式設計思想實現JKAPIMock所要達到的目標。如下圖:
API開發之介面安全(三)----sign有效時間
之前生成的sign和校驗sign我們已經完全掌握了、但是僅僅憑藉這樣的sign是無法滿足我們的需求的,如果一個黑客通過抓包抓到你的資料 他可以去修改你的header為這樣的 body為那樣的 也是可以通過sign校驗的 那麼我們怎麼解決呢 下面將詳細的 為大家解說 首先想到這樣的一個問題 我麼首先應當考慮到
打破國外壟斷,開發中國人自己的程式語言(2):使用監聽器實現計算器
上一篇:實現可以解析表示式的計算器 本文已經同步到公眾號「極客起源」,輸入379404開始學習! 本文是《打破國外壟斷,開發中國人自己的程式語言》系列文章的第2篇。本系列文章的主要目的是教大家學會如何從零開始設計一種程式語言(marvel語言),並使用marvel語言開發一些真實的專案,如移動A
[iOS]關於 App 混合(Hybrid)開發的優化,包括H5、Weex等(本篇博客主要針對 iOS 應用講解,但該思想同樣適用於Android)
color 數據 後臺 lib 新版 生成 下載地址 代碼 版本 我們知道混合開發,可以節省很多成本(時間成本,經濟成本等等),所以有很多公司比較鐘愛這種開發形式,今天所講的優化方式,也是我在我們公司的應用中實際用了的,而且我寫的這個優化的 SDK 已經開源到 gith
(我是初學者)第一次項目開發(二)開發中遇到的問題和註意事項
持久層 數據庫 認識 碼代碼 操作 出錯 排序 文檔 項目 這周正式開始做項目練習,這才發現實際去做的時候會遇到和出現很多的問題 在這裏說一說我的體會,請指正 首先,實體類 1、實體類中有哪些屬性,類型是什麽,並根據屬性建立sql的相應表格, 2、哪些屬性需要在寫在實體
那些年,我追過的繪圖語言(續)
微信 ont 字符 send ogr html wrap ack 時空 https://zhuanlan.zhihu.com/p/19901245 **************************** 那些年,我追過的繪圖語言(續) 陳天 3 年前 自從上一篇文
HBase概念學習(八)開發一個類twitter系統之表設計
至少 創建用戶 列表 ase wke long 少包 mali 。。 這邊文章先將可能的需求分析一下,設計出HBase表,下一步再開始編寫client代碼。 TwiBase系統 1、背景 為了加深HBase基本概念的學習,參考HBase實戰這本書實際動手做了這個樣
從零開始學習音視頻編程技術(三) 開發環境搭建(Qt4.86手動設置環境,主要就是設置g++和qmake,比較透徹,附下載鏈接)
路徑 details 分享 baidu 末尾 是我 其中 找到 source 1.先下載安裝Qt 我們使用的版本是4.8。 可以自行百度下載也可以從下面的網盤地址下載: Qt庫和編譯器下載: 鏈接:http://pan.baidu.com/s/1hrUxLIG 密碼
握手API網關(7)開發指南-API參考
custom psd title control test 機制 找不到 con 列表 一、簡介 用戶可以使用開發指南介紹的 API 對 API 網關服務進行相關操作。 術語表 術語全稱中文說明 Region 地域 用戶開放API,需選擇API在API網關的部署地
Qt5的插件機制(6)--開發Qt插件時幾個重要的宏
nor article pre strcmp object ant a plugin 通過 public 怎樣開發Qt插件,能夠在Qt Assistant 中搜索"Qt Plugins"或"How to Create Qt Plug
[SOE] ArcGIS Server對象擴展(SOE)開發註意事項
自己的 數據 命令 src 無法 內容 函數庫 適用於 適合 ArcGIS Server對象擴展(SOE)開發註意事項 1.SOE介紹 在ArcGIS 10.1中ArcGIS Server不在支持DCOM方式的連接,這也就意味著我們不能通過本地方式的連接