舊文件

此處文件僅供參考,請自行考量時效性與適用程度,其他庫藏文件請參考文件頁面
我們亟需您的協助,進行共筆系統搬移、及文件整理工作,詳情請查閱參與我們

套件開發指南 - Googlebar Lite

出自 MozTW Wiki

原文: Creating a Firefox Toolbar Extension (Firefox 1.5)[已授權]

作者 Born Geek

內容大綱

前言 (Instruction)

這份指南將說明如何建立 Firefox 的工具列套件(支援 1.5 或更新的版本)。

這份文件提供套件如何開發的概要、必要的工具、以及建立工具列的細節。套件開發是不難的,儘管必須具備某些基礎的程式設計知識。

有三種技術是我建議你必須稍微熟悉的:XML、JavaScript、CSS。這三個技術學習起來都不難,而且網路上有許多不錯的教學。

Firefox 1.5 版在套件開發上有很大的改善,這個版本比先前的版本更容易建立套件。 這份指南利用了改善的部份,必要時,我會指出變動的部份。如果你發現錯誤的地方,或是有任何建議,請聯絡作者

第一章:準備開始 (Getting Started)

開始之前 (Before We Start)

在我們開始製作第一份工具列套件之前,有一些非常有用的東西是你必須要先知道的。

下載指南 (Tutorial Downloads)

在這份指南的最後,我們將建立一個 Googlebar Lite 的簡化版本。為了對過程的學習有所幫助,你可以下載這個工具列的開發版本。兩份可得到的版本:

注意到這份 xpi 檔案也包含了原始碼。技術上而言,你只要下載 xpi 的檔案,並用 zip 的解壓縮程式解開 xpi 檔,以及其中的 jar 檔。第二份檔案只是為了方便而已。

有用的參考文獻 (Useful References)

我強烈建議你將下列的網址加入書籤,在我學習套件開發的過程,這些網頁對我非常的有幫助,我相信對你來說也是。(前四項為原作者提供)

必需先學習的 (Learning the Prerequisites)

如我之前所提到的,Firefox 套件開發需要先知道一點關於 XML、JavaScript、及CSS 的技術。這三個主題都是相當容易了解的,我也會提供了一些關於這三項技術的說明。

你將會需要的工具 (Tools You Will Need)

為了設計套件,你需要幾個工具軟體,這些軟體都是免費可取得的。我們要設計的幾個檔案都是標準文字檔。因此,你需要一個不錯的文字編輯器。我強烈反對使用類似 Microsoft Word 的程式。網路上有一些傑出的免費程式設計文字編輯器,這些編輯器對你有非常大的幫助,例如自動縮排、強調語法等等。幾個受歡迎的編輯器包含 Crimson EditorTextPad、及 JCreatorEmEditor

第二個你會用到的工具是 zip 檔的壓縮軟體。雖然有其他需多有用的工具,像是 7-Zip、及 WinRAR,但我個人是使用 WinZip。當封裝套件時,我們會用到這個工具。如果你打算做很多套件的開發,我建議你找有命令列介面的壓縮工具。使用命令列可以輕易地將封裝過程自動化,也省下你大量的時間。

檔案結構佈局 (File Structure Layout)

套件開發需要特定的內部結構,所以我們必須確定這一步是正確的。否則,將不會發生作用。首先,為我們的套件建立最上層的目錄。在這份指南,我們會使用 TutToolbar 當作目錄名稱(避免使用空白文字)。在這個新建立好的 TutToolbar 目錄裡,我們需要再建立第二個目錄。這個目錄命名為 chrome (使用小寫) 。既然我們這麼喜歡建立目錄,那就再來建立第三個吧!這次在 chrome 目錄裡建立一個名稱為 content 的目錄(使用小寫)。這裡是我們的目錄結構看起來的樣子:

+- TutToolbar/
   +- chrome/
      +- content/

或者是

TutToolbar/
TutToolbar/chrome/
TutToolbar/chrome/content/

第二章:建立架構(Creating the Framework)

套件的架構是用來告訴 Firefox 套件是如何被延伸的:檔案的結構、被誰建立、或是套件的全球唯一代號(GUID)。在 Firefox 1.5 這個版本,套件開發在這個部份有很大的變化。原本在 1.0.x 版是很沈重的技巧,在 1.5 版已經變得更整潔、更簡單。

安裝清單 (Installer Manifest)

這份清單是用來提供 Firefox 關於套件的細節。有一些重要的項目放置在這個檔案,所以我們必須確定這部份是正確的。在你的最上層目錄裡,建立一個檔案 install.rdf 。當你建立好這個檔案,你會看到這樣的結構:

+- TutToolbar/
   +- install.rdf
   +- chrome/
      +- content/

在我們開工前,讓我們看看這個範例。所有我們必須編輯的部份已經被突顯出來了,沒有被突顯的部份是不可以修改的。接著,我們來看這份檔案的內容,我將會解釋每個部份的細節,而且可以開始編輯屬於自己的套件。

<?xml version="1.0"?>

<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">

<Description about="urn:mozilla:install-manifest">

<!-- Required Items -->
<em:id>yourextension@yoursite.com</em:id>
<em:name>Your Extension's Name</em:name>
<em:version>1.0</em:version>

<em:targetApplication>
    <Description>
        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
        <em:minVersion>1.5</em:minVersion>
        <em:maxVersion>1.5.0.*</em:maxVersion>
    </Description>
</em:targetApplication>

<!-- Optional Items -->
<em:creator>Your Name</em:creator>
<em:description>A description of the extension</em:description>
<em:homepageURL>http://www.yoursite.com/</em:homepageURL>

</Description>
</RDF>

在最上面第一行表示這是一份 XML 格式的檔案。第二行,以 <RDF> 開頭的,是這份文件的基本元素(root element)。它的責任是辨別這部份是 RDF (Resource Description Framework) 格式。下一個標籤(Tag) <Description> 同樣地是用來辨別為安裝清單。現在,乏味的東西都結束了,讓我們看看第一部份必須要編輯的:

<em:id>yourextension@yoursite.com</em:id>
<em:name>Your Extension's Name</em:name>
<em:version>1.0</em:version>

第一個我們需要擔心的是套件的識別號碼。在 Firefox 1.5 之前的版本,必須使用全球唯一代號(globally unique identifier),即 GUID。儘管 GUID 還是被支援的,新的格式卻更容易使用。僅僅需要使用你的套件名稱,@ 符號,再加上你的網站的最上層網址。在這份指南中,我們為這個工具列使用這個值 tuttoolbar@borngeek.com

接著是套件的名稱(這會顯示在套件管理員裡)。在我們的範例裡,使用 Toolbar Tutorial 為這個套件的名稱。確定這個名稱不包含版本編號,因為版本編號有它自己專屬的標籤。我們要編輯的這個標籤就在下一行。既然這是我們試圖要做的第一個工具列套件,讓這個值為 1.0 。要注意到,在真實的情況裡,當你發表新版本的套件時,必須更新這個值,描述解譯程式(Scripter)在自動更新過程才不會發生問題。在我的套件裡,我利用了描述語法 (以 Perl 寫成)。在這份指南的第七章,我將會告訴你我如何使用。

下一個區塊也是這個安裝清單重要的一部分,接著來看看這部份:

<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>1.5</em:minVersion>
<em:maxVersion>1.5.0.*</em:maxVersion>
</Description>
</em:targetApplication>

這個區塊是用來指示套件要用在哪個程式。在這個例子,我們要開發 Firefox 的套件。因此,<em:id> 這個元素指定了 Firefox 的 GUID 。你不應該改變這個值,否則,會讓你的套件無法被正確地安裝。

唯一兩個我們需要變動的,是這兩個元素:<em:minVersion><em:maxVersion> 。這兩個元素指明套件適合用在 Firefox 的哪個版本 ( minVersion 是最低支援版本,而 maxVersion 是最高支援版本) 。 在我們的範例,我們會使用 1.5 (minVersion) 與 1.5.0.* (maxVersion)。因為我們利用 Firefox 1.5 的開發環境,所以不可以把 minVersion 設定的比 1.5 還小。

注意到,你所使用的版本編號必須遵守標準協定。舉個例子,「1.5 Release Candidate 1」是不行的。目前 Firefox 的版本結構是相當嚴謹的,在 Mozilla Developer Center 的文章 Toolkit Version Format 有詳細的描述。我建議你讀這篇文章,以了解怎樣的字串是被允許的。

在安裝清單的最後,是用來描述套件的資料定義,或稱中繼資料(meta-data):

<!-- Optional Items -->
<em:creator>Your Name</em:creator>
<em:description>A description of the extension</em:description>
<em:homepageURL>http://www.yoursite.com/</em:homepageURL>

就如註釋一樣,這些元素是非必要的。<em:creator> 允許套件作者指明自己的名字,這樣別人就知道是誰製作這個套件。下一個,<em:description> 允許我們對我們的套件做一些說明,這個說明會顯示在套件管理員中的套件名稱底下。最後,<em:homepageURL> 允許我們指明別人可以在哪裡找到我們的套件。

注意到,這些不是唯一的資料定義,同時也有許多其他可選用的項目。舉個例子,有個元素可以在套件管理員中使用我們自己的圖示(icon)。 另一個元素允許我們指定自訂選項的位置或是「關於」的對話視窗。全部可用的元素(也叫做屬性 properties ),可以看看在Mozilla Developer Center 中的文章 Installer Manifests 。最後一點要注意的,所有的元素是不需要按照順序的。也就是說,你可以將它們放在檔案中任何地方,僅僅要注意的是,將它們放置在 <Description></Description> 之間。

現在,我們了解了安裝清單,讓我們來看看最後的樣子,這個版本將被使用在這份指南中。你可以直接將下面的部份,複製在 install.rdf 中。

<?xml version="1.0"?>

<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">

<Description about="urn:mozilla:install-manifest">

 <!-- Required Items -->
 <em:id>tuttoolbar@borngeek.com</em:id>
 <em:name>Tutorial Toolbar</em:name>
 <em:version>1.0</em:version>

 <em:targetApplication>
     <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>1.5</em:minVersion>
         <em:maxVersion>1.5.0.*</em:maxVersion>
     </Description>
 </em:targetApplication>

 <!-- Optional Items -->
 <em:creator>Jonah Bishop</em:creator>
 <em:description>An example toolbar extension.</em:description>
 <em:homepageURL>http://www.borngeek.com/firefox/</em:homepageURL>

</Description>
</RDF>

Chrome 清單 (Chrome Manifest)

Chrome 清單是 Firefox 1.5 才開始有的。在之前,所有的資料被設定在兩個地方:安裝清單(installer manifest)、及 contents.rdf 。而現在,新的格式顯得更簡單。再次在最上層目錄建立另外一個檔案 chrome.manifest 。下面是檔案結構看起來的樣子:

+- TutToolbar/
   +- install.rdf
   +- chrome.manifest
   +- chrome/
      +- content/

Chrome 清單是用來告訴 Firefox ,我們的套件提供的封裝(package)與覆載(overlay)。在我們開始建立之前,先看個範例:

content myextension chrome/content/
overlay chrome://browser/content/browser.xul chrome://myextension/content/overlay.xul
locale myextension en-US chrome/locale/en-US/
skin myextension classic/1.0 chrome/skin/

與早先使用的 contents.rdf (通常超過20行) 比較,現在的方式變得非常簡單!第一行使用你指定的封裝名稱來登錄,並指明放置在 content 目錄。這將允許 chrome 資源識別字串(Uniform Resource Identifier,簡稱 URI) ,如 chrome://myextension/content/,在我們的套件層級中指向適當的地方。注意:封裝名稱只能以小寫表示,其他混和大小寫,或是全部大寫的名稱都是不允許的。

注意到,content 目錄的位置是相對於套件的根目錄。在這份指南,我們使用 tuttoolbar 當作封裝名稱。

第二行登錄 chrome://browser/content/browser.xul 的覆載(overlay),這允許你新增或修改 Firefox 主視窗的使用者介面(user interface)。

在上面的範例,chrome://myextension/content/overlay.xul 指定了覆載(overlay)的XUL 檔案。

換句話說,位於套件中 content 目錄的 overlay.xul 檔案,是我們將要新增的使用者介面(工具列)。在這個部份,我們要使用的值為 chrome://tuttoolbar/content/tuttoolbar.xul 。 下一行說明地區化如何建立(how a locale can be created)。在這份指南中,我們不設計地區化(雖然過程很簡單),這將在未來再做討論。

最後一行設定了面板(skin),我們將使用這個技巧來美化我們的工具列。現在,先略過它,我們將在第五章再回來討論這點。 一切都很簡單,不是嘛?下面是我們將會使用的 chrome 清單,這只是半成品,之後我們將會加入面板的資訊。再一次,將下面的程式碼複製到我們剛剛建立的 chrome.manifest

content tuttoolbar chrome/content/
overlay chrome://browser/content/browser.xul chrome://tuttoolbar/content/tuttoolbar.xul

現在架構已經可以了,接著讓我們來做些有趣的事吧!

第三章:建構工具列 (Structuring the Toolbar)

Firefox 套件的使用者介面部份是使用 XUL 技術 (音:zool),這是一種用來設計使用者介面的標示語言。XUL 可以想成是一種 XML 的調味料(意旨有附加功能的), 它不過是使用預設元素的 XML (也叫做 widgets)。使用 XUL 的好處,是因為它使用了動態覆載(dynamic overlays)的技術。動態覆載技術可以讓開發者不需要改變原本介面的程式碼的情況下,去修改視窗的使用者介面。在不需更動原始程式碼的情況下,可以讓我們專注於設計我們的套件,而不用擔心需要重複開發的問題。在這個章節,我們要來看看設計工具列的 XUL 必要標示。記住,XUL 僅僅是用來建構工具列而已。為了讓工具列可以運作,我們必須使用 JavaScript ,這部份將在第六章提到。

content 目錄裡,建立一個檔名為 tuttoolbar.xul 的檔案。在你建立好之後,目錄的結構會是這樣:

+- TutToolbar/
   +- install.rdf
   +- chrome.manifest
   +- chrome/
      +- content/
         +- tuttoolbar.xul

現在,我們已經建立好這個檔案,可以逐行地開始討論內容。隨著進行的腳步,我會將檔案的所有內容,一步一步地介紹。這會讓過程看起來更簡單,在這章節的最後,這個檔案會相當健全。

因為 XUL 只是一種 XML 的調味料,所以第一行必須是使用 XML 的宣告:

<?xml version="1.0"?>

現在,宣告這是一個 XML 的檔案之後,我們就可以開始藉由 overlay 元素設計覆載了。這個元素將會是這整份文件的根元素。換句話說,所有其他的元素必須在 <overlay> 與 </overlay> 之間。這是 overlay 看起來的樣子:

<overlay id="TutTB-Overlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
</overlay>

這個元素有兩個屬性:idxmlns 。這跟 HTML 類似,id 的值必須是唯一的。更重要的是,這也必須是整個瀏覽器唯一的值。這裡有一個可以讓你確定所使用的 ID 是唯一的方法。例如,上面的範例。你可以注意到我使用了 TutTB 開頭的字眼來定義 id 屬性的值。使用符合你的套件名稱開頭的字眼,這會使 ID 是唯一的可能性提高。並且注意到,實際的值是不需要包含 overlay 的字眼,我這麼做是方便我追蹤所使用的名稱是做什麼用的。

第二個屬性:xmlns,用來指定使用於覆載的命名空間。既然這是 XUL 文件,我們必須定義 XUL 的命名空間,這是你會一直用到的值。

工具箱與工具列 (The Toolbox and Toolbar)

所有的工具列應該被集中在工具箱中。我們使用 toolbox 元素來指定一個工具箱(記住,這個元素必須被放置在 overlay 之間。):

<toolbox id="navigator-toolbox">
</toolbox>

注意到,這個 id 屬性有一個預設值:navigator-toolbox 。 這個特定的值表示為 Firefox 視窗中主要的工具箱元素,跟瀏覽工具列、選單列、網址列,或是其他類似的控制選項一樣。藉由指定這個特別的工具列,我們可以確定這個工具列會與其他的工具列靠攏在一起。我建議你總是讓你的工具列放在這個特定的工具箱中,這不只讓你有看起來一致的工具列,也可以讓你在 「檢視>工具列」 中,快速地將你的工具列隱藏或是顯示。

讓我們將注意力放回到 toolbar 元素,我們會將這個元素放在 toolbox 之中。看看這個範例:

<toolbar id="TutTB-Toolbar" toolbarname="Tutorial Toolbar" accesskey="T"
         class="chromeclass-toolbar" context="toolbar-context-menu" 
         hidden="false" persist="hidden">
</toolbar>

讓我們來看看這個元素的屬性:

  • toolbarname - 這個元素指定套件的名稱(這會是你在「檢視>工具列」看到的文字)。
  • accesskey - 給這個工具列指定字母,用來當作快速鍵使用。在這個工具列指南中,我們使用大寫的 T 。雖然這是非必要的屬性,還是強烈建議你使用它,這能讓使用者只要用快速鍵就能開啟或關閉你的工具列。
  • class - 為工具列指定特定的樣式類別。預設值 chromeclass-toolbar 是用來規範工具列顯示模式的類別,同樣地,這是建議使用的非必要屬性。
  • context - 指定當在工具列上按右鍵時,所顯示的工具列選單(context menu)。你可以提供你的選單 ID 值,或是使用 toolbar-context-menu 來將選單放置在「檢視 > 工具列」。
  • hidden - 指定工具列是否隱藏。預設的情況下,我們要讓使用者看見我們的工具列,所以設定這個值為 false (相反為 ture )。
  • persist - This attribute is a space separated list of attributes that should persist across browser sessions. 在上面的範例,我使用 hidden ,用來告訴 Firefox 在 sessions 記住工具列的隱藏狀態。注意到,如果你的工具列沒有使用 id 屬性,則 persist 屬性不會發生作用!所以要確定你有為你的工具列指定 id 元素。

你可以在 XUL Planet toolbar element's attributes 看到完整的參考文獻。

現在,讓我們來看看建立好的覆載檔案[View XUL Overlay Revision 1]:

<?xml version="1.0"?>
<overlay id="TutTB-Overlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

    <toolbox id="navigator-toolbox">

        <toolbar id="TutTB-Toolbar" toolbarname="Tutorial Toolbar" accesskey="T"
                 class="chromeclass-toolbar" context="toolbar-context-menu"
                 hidden="false" persist="hidden">
        </toolbar>

    </toolbox>

</overlay>

工具列按鈕 (Toolbar Buttons)

在 Firefox 中,工具列按鈕有三種:一般、選單、按鈕選單。這些都是用 toolbarbutton 來建立的,讓我們來分別看看。記住,這些元素必須放在 toolbar 元素之中。

一般按鈕 (Normal Buttons)

這是用來建立一般的按鈕:

<toolbarbutton id="TutTB-Web-Button" tooltiptext="Search the Web"
               label="Web Search" oncommand="TutTB_Search(event, 'web')" />

完成之後,來看看這個元素的屬性:

  • tooltiptext - 當滑鼠移動到按鈕上時出現的說明。
  • label - 工具列按鈕顯示的文字。
  • oncommand - 當 oncommand 事件觸發時(按下按鈕),你所指定的程式碼。在這個範例,使用 TutTB_Search()函式,我們將在第六章詳細說明。

你可以在 XUL Planet toolbarbutton element's attributes 找到完整的參考文獻。

選單按鈕 (Menu Buttons)

當點擊按鈕選單時,會出現下拉式選單。 儘管這個按鈕標記類似一般按鈕標記,我們還是必須加入 menupopup 元素,讓選單以我們想要的樣子出現。為了簡潔點,下面的範例只包含兩個選單項目。

<toolbarbutton id="TutTB-MainMenu" type="menu"
               tooltiptext="Tutorial Toolbar Main Menu">
  <menupopup>
    <menuitem label="Google Home Page" accesskey="G"
              tooltiptext="Navigate to Google"
              oncommand="TutTB_LoadURL('http://www.google.com/')" />

    <menuseparator />

    <menuitem label="Born Geek Website" accesskey="B"
              tooltiptext="Navigate to Born Geek"
              oncommand="TutTB_LoadURL('http://www.borngeek.com/')" />
  </menupopup>
</toolbarbutton>

toolbarbutton 有兩個要注意的變化。第一,type 屬性指定了 menu 的值,說明了這是個選單按鈕,而不是一般按鈕。第二,你會注意到這裡面沒有 oncommand 屬性。因為選單按鈕的唯一目的是為了顯示跳出式選單,並不需要執行任何程式碼,而顯示選單的動作會由 Firefox 自動完成。

也請注意到 menupopupmenuitemmenuseparator 這三個元素。menupopup 所有選單項目的容器,用來建立與顯示選單。在這個範例裡,這個元素沒有屬性。同樣地,menuseparator 也很簡單,在下拉式選單中放置水平分隔線,用來分隔不同的選單。

menuitem 稍微有點複雜。在上面的範例,我們指定的屬性:

  • label - 指定選單項目的文字。
  • tooltiptext - 這個屬性就像是我們在 toolbarbutton 看到的,但是有一點要警告的。由於 Firefox 的 bug (bug #147670),這個屬性是必要的。如果你決定從 menuitem 中刪掉這個屬性,當使用者將滑鼠移動到選單項目時,會看不到提示說明。選單不會顯示工具提示,這真是個令人不滿意的「特色」!

你可以在 XUL Planet menuitem element's attributes 找到完整的參考文獻。

按鈕選單 (Button-Menu Buttons)

第三也是最後的按鈕選單是三種之中最複雜的。實際上,結合了前兩種按鈕,提供可點擊按鈕的下拉式選單。

「上一頁」與「下一頁」瀏覽選單是這個按鈕選單的範例,這是需要建立的標記:

<toolbarbutton id="TutTB-Combined-Button" label="Search"
               type="menu-button" tooltiptext="Combined Search"
               oncommand="TutTB_Search(event, 'web')">
  <menupopup>
    <menuitem id="TutTB-Combined-Web" label="Web Search"
              class="menuitem-iconic" tooltiptext="Search the Web"
              oncommand="TutTB_Search(event, 'web'); event.preventBubble();" />

    <menuitem id="TutTB-Combined-Image" label="Image Search"
              class="menuitem-iconic" tooltiptext="Search Images"
              oncommand="TutTB_Search(event, 'image'); event.preventBubble();" />
  </menupopup>
</toolbarbutton>

有兩個值得注意的變化:在 toolbarbuttontype 屬性指定了值 menu-button ,而且有另外一行程式碼 oncommand ,我會在第六章解釋這額外的程式碼。接著注意到,這裡有跟「一般按鈕」一樣的 toolbarbuttononcommand 屬性,以及在「選單按鈕」裡的巢狀元素 menupopupmenuitem

現在,我們討論完不同的按鈕,來看看程式碼的樣子[View XUL Overlay Revision 2]:

<?xml version="1.0"?>  
<overlay id="TutTB-Overlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
                 
    <toolbox id="navigator-toolbox">

        <toolbar id="TutTB-Toolbar" toolbarname="Tutorial Toolbar" accesskey="T"
                 class="chromeclass-toolbar" context="toolbar-context-menu"
                 hidden="false" persist="hidden">
                                 
            <toolbarbutton id="TutTB-MainMenu" type="menu"
                           tooltiptext="Tutorial Toolbar Main Menu">
                  <menupopup>
                      <menuitem label="Google Home Page" accesskey="G"
                                tooltiptext="Navigate to Google"
                                oncommand="TutTB_LoadURL('http://www.google.com/')" />

                      <menuseparator />

                      <menuitem label="Born Geek Website" accesskey="B"
                                tooltiptext="Navigate to Born Geek"
                                oncommand="TutTB_LoadURL('http://www.borngeek.com/')" />
                  </menupopup>
            </toolbarbutton>

            <toolbarbutton id="TutTB-Combined-Button" label="Search"
                           type="menu-button" tooltiptext="Combined Search"
                           oncommand="TutTB_Search(event, 'web')">
                  <menupopup>
                      <menuitem id="TutTB-Combined-Web" label="Web Search"
                                class="menuitem-iconic" tooltiptext="Search the Web"
                                oncommand="TutTB_Search(event, 'web'); event.preventBubble();" />

                      <menuitem id="TutTB-Combined-Image" label="Image Search"
                                class="menuitem-iconic" tooltiptext="Search Images"
                                oncommand="TutTB_Search(event, 'image'); event.preventBubble();" />
                  </menupopup>
              </toolbarbutton>

              <toolbarbutton id="TutTB-Web-Button" tooltiptext="Search the Web"
                             label="Web Search" oncommand="TutTB_Search(event, 'web')" />
        </toolbar>
    </toolbox>
</overlay>

下拉式編輯列 (Drop-Down Edit Box)

下一個我們要在工具列加入的是下拉式編輯列。這是用 menulist 來建立的,讓我們來看看程式碼:

<toolbaritem id="TutTB-SearchTerms-TBItem" persist="width">
    <menulist id="TutTB-SearchTerms" editable="true" flex="1"
              minwidth="100" width="250"
              onkeypress="TutTB_KeyHandler(event);">
        <menupopup id="TutTB-SearchTermsMenu" onpopupshowing="TutTB_Populate()" />
    </menulist>
</toolbaritem>

注意到,我們把 menulist 放在 toolbaritem 之中。任何一個在 toolbar 的項目都不是工具列按鈕,在 toolbaritem 裡頭的才是。也請注意到,我們為 toolbaritem 指定了 id ,且設定了寬度(藉由利用了 persist 屬性)。

menulist 才是真正用來建立下拉式編輯列的。有幾個在這個元素出現的新屬性:

  • editable - 當設定為 true 時,使用者可以在編輯列中打字。
  • flex - 使這個元素是可變動的。這是個整數值,相對於其他可變動工具列元素,指定其大小。設定值 2 代表著設定值 1 的兩倍寬,0 則是不能變動的固定寬度。
  • minwidth - 最小寬度,單位為像素。
  • width - 初始寬度,單位為像素。
  • onkeypress - 當使用者輸入文字時,用來執行的程式碼。

你可以在 XUL Planet menulist element's attributes 看到完整的參考文獻。

跟我們之前看到的「選單按鈕」一樣,在 menulist 裡的 menupopup 包含了 menuitem 元素。onpopupshowing 這個事件會在編輯列的選單項目顯示前觸發,其中指定一個能動態寫入選單項目的函式,這個函式我們稍後會提及。你也可以加入靜態的選單,過程跟「選單按鈕」是一樣的。

現在,這個套件已經變得更具體一點了,讓我們來看看程式碼[View XUL Overlay Revision 3]:

<?xml version="1.0"?>
<overlay id="TutTB-Overlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
         
    <toolbox id="navigator-toolbox">
    
        <toolbar id="TutTB-Toolbar" toolbarname="Tutorial Toolbar" accesskey="T"
                 class="chromeclass-toolbar" context="toolbar-context-menu" 
                 hidden="false" persist="hidden">
                 
            <toolbarbutton id="TutTB-MainMenu" type="menu"
                           tooltiptext="Tutorial Toolbar Main Menu">
                <menupopup>
                    <menuitem label="Google Home Page" accesskey="G"
                              tooltiptext="Navigate to Google"
                              oncommand="TutTB_LoadURL('http://www.google.com/')" />
                    
                    <menuseparator />
                    
                    <menuitem label="Born Geek Website" accesskey="B"
                              tooltiptext="Navigate to Born Geek"
                              oncommand="TutTB_LoadURL('http://www.borngeek.com/')" />
                </menupopup>
            </toolbarbutton>
            
            <toolbaritem id="TutTB-SearchTerms-TBItem" persist="width">
                <menulist id="TutTB-SearchTerms" editable="true" flex="1"
                          minwidth="100" width="250"
                          onkeypress="TutTB_KeyHandler(event);">
                    <menupopup id="TutTB-SearchTermsMenu" onpopupshowing="TutTB_Populate()" />
                </menulist>
            </toolbaritem>
            
            <toolbarbutton id="TutTB-Combined-Button" label="Search"
                           type="menu-button" tooltiptext="Combined Search"
                           oncommand="TutTB_Search(event, 'web')">
                <menupopup>
                    <menuitem id="TutTB-Combined-Web" label="Web Search"
                              class="menuitem-iconic" tooltiptext="Search the Web"
                              oncommand="TutTB_Search(event, 'web'); event.preventBubble();" />
                
                    <menuitem id="TutTB-Combined-Image" label="Image Search"
                              class="menuitem-iconic" tooltiptext="Search Images"
                              oncommand="TutTB_Search(event, 'image'); event.preventBubble();" />
                </menupopup>
            </toolbarbutton>
            
            <toolbarbutton id="TutTB-Web-Button" tooltiptext="Search the Web"
                           label="Web Search" oncommand="TutTB_Search(event, 'web')" />
        </toolbar>
    </toolbox>
</overlay>

可調整的移駐標記 (Resizing Gripper)

還記得之前包含在 menulisttoolbaritem 元素所提到的 persist 屬性嗎?設定這個屬性以讓我們在瀏覽器使用期間,可以儲存搜尋列的寬度。為了讓使用者可以改變寬度,我們要提供 移駐標記 。用來做這件事的元素是 splitter ,讓我們來看看程式碼:

<splitter id="TutTB-ResizeSplitter" state="open" collapse="none"
          resizebefore="closest" resizeafter="farthest"
          tooltiptext="Resize the Search Box">
    <vbox id="TutTB-ResizeBar" />
</splitter>

這是 splitter 的屬性:

  • state - 指定切割標記是否摺疊(隱藏)內容, open 值指定不管是否有內容皆顯示。
  • collapse - 決定哪邊的 splitter 被隱藏。我們使用 none ,不讓任何一邊的 splitter 被隱藏。
  • resizebefore - 當 splitter 改變時,指定 splitter 左邊的哪個元素必須被重新調整。當 splitter 移動時,使用 closest 來重新調整最靠近 splitter 左邊的編輯列。
  • resizeafter - 當 splitter 改變時,指定 splitter 右邊的哪個元素必須被重新調整。在這個範例裡,我們使用 farthest 來重新調整最右邊的彈性空間。

vbox 是用來設定樣式的,我們會在第五章討論面板時,提到這點。 你可以在 XUL Planet splitter element's attributes 看到更完整的參考文獻。

讓我們來看看包含 splittertoolbaritem 的覆載程式碼[View XUL Overlay Revision 4]:

<?xml version="1.0"?>
<overlay id="TutTB-Overlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
         
    <toolbox id="navigator-toolbox">
    
        <toolbar id="TutTB-Toolbar" toolbarname="Tutorial Toolbar" accesskey="T"
                 class="chromeclass-toolbar" context="toolbar-context-menu" 
                 hidden="false" persist="hidden">
                 
            <toolbarbutton id="TutTB-MainMenu" type="menu"
                           tooltiptext="Tutorial Toolbar Main Menu">
                <menupopup>
                    <menuitem label="Google Home Page" accesskey="G"
                              tooltiptext="Navigate to Google"
                              oncommand="TutTB_LoadURL('http://www.google.com/')" />
                    
                    <menuseparator />
                    
                    <menuitem label="Born Geek Website" accesskey="B"
                              tooltiptext="Navigate to Born Geek"
                              oncommand="TutTB_LoadURL('http://www.borngeek.com/')" />
                </menupopup>
            </toolbarbutton>
            
            <toolbaritem id="TutTB-SearchTerms-TBItem" persist="width">
                <menulist id="TutTB-SearchTerms" editable="true" flex="1"
                          minwidth="100" width="250"
                          onkeypress="TutTB_KeyHandler(event);">
                    <menupopup id="TutTB-SearchTermsMenu" onpopupshowing="TutTB_Populate()" />
                </menulist>
            </toolbaritem>
            
            <splitter id="TutTB-ResizeSplitter" state="open" collapse="none"
                      resizebefore="closest" resizeafter="farthest"
                      tooltiptext="Resize the Search Box">
                <vbox id="TutTB-ResizeBar" />
            </splitter>
            
            <toolbaritem flex="0">
            
                <toolbarbutton id="TutTB-Combined-Button" label="Search"
                               type="menu-button" tooltiptext="Combined Search"
                               oncommand="TutTB_Search(event, 'web')">
                    <menupopup>
                        <menuitem id="TutTB-Combined-Web" label="Web Search"
                                  class="menuitem-iconic" tooltiptext="Search the Web"
                                  oncommand="TutTB_Search(event, 'web'); event.preventBubble();" />
                    
                        <menuitem id="TutTB-Combined-Image" label="Image Search"
                                  class="menuitem-iconic" tooltiptext="Search Images"
                                  oncommand="TutTB_Search(event, 'image'); event.preventBubble();" />
                    </menupopup>
                </toolbarbutton>
                
                <toolbarbutton id="TutTB-Web-Button" tooltiptext="Search the Web"
                               label="Web Search" oncommand="TutTB_Search(event, 'web')" />
            </toolbaritem>
        </toolbar>
    </toolbox>
</overlay>

為了避免移駐標記討厭的細微顯示問題,我們還需要建立額外的 toolbaritem ,這與我們剛剛在「選單按鈕」中的搜尋按鈕所做的一樣。 再一次確定 flex 屬性的值為 0,這將會避免,當我們的選單按鈕被拖曳到左邊時被移除。

接著,讓我們再來增加兩個項目。首先,將 toolbarseparator 放置在最後兩個按鈕中,此目的是為了裝飾。再來,將 toolbarspring 放置在最後的 toolbaritem 元素之後。This spring will allow us to drag the resizer all the way to the right, so that we can see the full resizing effect in action. 這兩個標示是很簡單的,像是這樣:

<toolbarseparator />

<toolbarspring />

讓我們來看看完整的覆載範例[View XUL Overlay Revision 5]:

<?xml version="1.0"?>
<overlay id="TutTB-Overlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

    <toolbox id="navigator-toolbox">

        <toolbar id="TutTB-Toolbar" toolbarname="Tutorial Toolbar" accesskey="T"
                 class="chromeclass-toolbar" context="toolbar-context-menu"
                 hidden="false" persist="hidden">

            <toolbaritem flex="0">
                <toolbarbutton id="TutTB-MainMenu" type="menu"
                               tooltiptext="Tutorial Toolbar Main Menu">
                    <menupopup>
                        <menuitem label="Google Home Page" accesskey="G"
                                  tooltiptext="Navigate to Google"
                                  oncommand="TutTB_LoadURL('http://www.google.com/')" />

                        <menuseparator />

                        <menuitem label="Born Geek Website" accesskey="B"
                                  tooltiptext="Navigate to Born Geek"
                                  oncommand="TutTB_LoadURL('http://www.borngeek.com/')" />
                    </menupopup>
                </toolbarbutton>
			</toolbaritem>

			<toolbaritem id="TutTB-SearchTerms-TBItem" persist="width">
				<menulist id="TutTB-SearchTerms" editable="true" flex="1"
                          minwidth="100" width="250"
                          onkeypress="TutTB_KeyHandler(event);">
					   <menupopup id="TutTB-SearchTermsMenu" onpopupshowing="TutTB_Populate()" />
				</menulist>
			</toolbaritem>

			<splitter id="TutTB-ResizeSplitter" state="open" collapse="none"
                      resizebefore="closest" resizeafter="farthest"
                      tooltiptext="Resize the Search Box">
				<vbox id="TutTB-ResizeBar" />
			</splitter>

			   <toolbaritem flex="0">

			<toolbarbutton id="TutTB-Combined-Button" label="Search"
                           type="menu-button" tooltiptext="Combined Search"
                           oncommand="TutTB_Search(event, 'web')">
				<menupopup>
					<menuitem id="TutTB-Combined-Web" label="Web Search"
                              class="menuitem-iconic" tooltiptext="Search the Web"
                              oncommand="TutTB_Search(event, 'web'); event.preventBubble();" />

					   <menuitem id="TutTB-Combined-Image" label="Image Search"
                              class="menuitem-iconic" tooltiptext="Search Images"
                              oncommand="TutTB_Search(event, 'image'); event.preventBubble();" />
				</menupopup>
			</toolbarbutton>

			<toolbarseparator />

			<toolbarbutton id="TutTB-Web-Button" tooltiptext="Search the Web"
                           label="Web Search" oncommand="TutTB_Search(event, 'web')" />
			</toolbaritem>
		
		 <toolbarspring />

		</toolbar>
	</toolbox>
</overlay>  

第四章:動態開發 (Dynamic Development)

(譯者:如果不想用此開發環境,可以略過此章,並用第七章的方式測試套件。)

在 Firefox 1.5 中,動態套件開發是新的技術,對開發者而言,這是最有用的附加功能。這項技術可以讓你在開發套件時,即時地看到結果。當你要測試 XUL 覆載或是 JavaScript 程式碼時,不用每次再重新封裝套件。這不只節省了你的時間,也讓你能更快速地除錯。

那麼,這項特色是如何運作的呢?這就要感謝我們在第二章建立的 chrome 清單了。直到現在為止,我都還沒跟你說 chrome 清單試作甚麼用途。在這個指南中,我們一開始建立的版本就是特別為動態開發而設計的。換言之,我們不必重新封裝那份版本的套件,但還是有些改變需要完成,才可以封裝。有幾個用來「啟動」套件動態開發的步驟,在那之前,我要先給你幾個建議。

注意事項 (A Word of Warning)

套件開發有時會有點危險,尤其是設計 XUL 覆載檔案時。我強烈建議你要避免開發套件跟平常瀏覽網頁共用一個設定檔(profile)。換言之,你必須為你的開發環境建立一個新的設定檔。這可以避免你失去一些關鍵的資料,如:儲存的密碼、cookies、書籤或是其他你不想遺失的東西。

我希望能儘快 Born Geek 在發表關於如何建立與使用設定檔的教學。在那之前,你可以先看看在 Mozilla.org 的設定檔文件。建立與使用設定檔是很簡單的,這會避免讓你頭痛的情況發生。

如何動態開發 (How to Develop Dynamically)

最主要的工作,我們已經在 chrome 清單完成(即使你還不了解我們那時做了什麼)。現在,我們需要的,是一個將 Firefox 指向硬碟上套件位置的檔案,我們將藉由 pointer 檔案來完成。

首先,在你的電腦的隨便一個地方,以我們在安裝清單(install manifest)中所使用的 GUID 名稱,建立一個文字檔。在這份指南中,我們使用 tuttoolbar@borngeek.com 作為檔名。不幸的是,在微軟視窗環境裡,「.com」的檔案是被用來當作執行檔,而我們需要的是文字檔。你可以把「.com」拿掉,但也記得去修改在安裝清單的 GUID,確保兩個名稱是一樣的。

在這個檔案裡,我們將要輸入一行文字:套件儲存的絕對位置,也就是存放 install.rdfchrome.manifest 檔案的目錄。在我的例子,儲存路徑如下:

C:\Born Geek\TutToolbar

你必須使用你自己設定的路徑,除非你的路徑跟我的一樣。輸入完路徑後,儲存檔案。

現在,我們必須將這個檔案移動到跟開發環境的設定檔(profile)同一個目錄(如:C:\Documents and Settings\Administrator\Application Data\Mozilla\Firefox\Profiles)。我剛剛提到的設定檔文件會討論到設定檔目錄的位置。當你找到設定檔目錄時,將我們剛剛建立的 pointer 檔案移動到該目錄的 extensions 目錄下。這是我的例子:

+- tl5wlpz3.Nightly/
   +- bookmarkbackups/
   +- chrome/
   +- extensions/
      +- tuttoolbar@borngeek.com
      +- (... other files and folders ...)

你可以看到我的設定檔最上層目錄,我使用以「Nightly」為名的設定檔,來作為我的套件開發環境。一旦你把 pointer 檔案放在適當的位置,開啟 Firefox,確定你使用的是套件開發的設定檔(再次提醒,剛剛提到的設定檔文件會告訴你如何這樣做)。 到目前為止,如果你每樣都做的正確,你會看到沒有面板的灰色工具列。

開發週期 (The Development Cycle)

現在,我們已經啟動了動態開發環境,那要如何去利用呢?過程其實是非常簡單的:

  1. 編輯你的套件檔案
  2. 重新開啟已更改檔案所套用的視窗,或是使用 Extension Developer's Extension 的重新整理(Reload Chrome)。

有兩件事是你要知道的:

  • 如果你改變了 chrome.manifest 檔案,你必須重新開啟 Firefox (這個檔案只有在程式啟動時會發生作用)。
  • 如果你改變了 install.rdf 檔案,你必須改變 pointer 檔案所指向目錄的修改時間。在 Linux 中,你只要使用 touch 指令。而在 Windows 裡就有點難度,除非你安裝了可以使用 touch 指令的 Cygwin 工具。唯一要做的,就是在套件最上層目錄建立一個新目錄,然後刪除新目錄以改變目錄的「上一次修改」時間。

你可以看到,我們有了方便的開發與除錯工具。這勝過於,在 1.5 版之前的標準過程,就是每次在套件開發過程都要封裝檔案。

現在,讓我們把這個醜陋的工具列變得更漂亮一點吧!

第五章:面板製作 (Skinning the Toolbar)

面板是由樣式標準與圖片所構成的。記住,儘管設計面板是非必要的,但是它能提升套件的品質。畢竟使用者對套件的第一印象是它的外觀,而不是它的功能。然而,如果你覺得使用文字標籤讓你比較自在,或是不需要建立外觀,你可以略過這個章節。

更新檔案結構 (Updating the File Structure)

設計面板的第一步就是建立一個目錄,用來存放關於面板的檔案。在 chrome 目錄下建立一個 skin 目錄。檔案結構會像這樣:

+- TutToolbar/
   +- install.rdf
   +- chrome.manifest
   +- chrome/
      +- content/
         +- tuttoolbar.xul
      +- skin/

現在我們有存放面板檔案的地方了,之後必須要在 chrome 裡註冊這個位置。

更新 Chrome 清單 (Updating the Chrome Manifest)

還記得我們在第二章建立的 chrome 清單嗎?我們必須在這個檔案加入一行程式碼,用來註冊我們的面板。新加入的位置(第三行):

content tuttoolbar chrome/content/
overlay chrome://browser/content/browser.xul chrome://tuttoolbar/content/tuttoolbar.xul
skin tuttoolbar classic/1.0 chrome/skin/

新加入的這一行相當簡單,而且只有四個部份。第一部份,skin 說明這一行要註冊面板的資訊。接著是套件的名稱,這裡使用 tuttoolbar 當作我們的套件名稱。第三部份,說明我們將會擴展的已安裝套件名稱,「classic/1.0」是你會一直用到的值。最後,也是最重要的部份,就是包含面板檔案的目錄路徑。注意到這個路徑後面的斜線(/),這個斜線是必要的,確定你有輸入。

建立圖片檔案 (Creating the Image Files)

我們會讓每個工具列按鈕使用各自的圖像。注意到,這不是最有效率或是精緻的作法,但是可以讓人更容易了解。更好的方式是使用圖像樣式表,但是那超出這份指南的範圍。

我們需要五個圖像供工具列的組合選單使用:主要選單、綜合搜尋按鈕、網頁搜尋按鈕、圖片搜尋選單以及可調整的移駐標記。在下面的列表中,有五個我用在這個套件的圖像。注意, 這些圖像都是透明的 PNG 格式檔案,如果你使用 IE 或是其他瀏覽器,可能不能正確地顯示。

主要選單圖示 Main Menu Icon (main.png): http://wiki.moztw.org/uimages/b/b7/Borngeek_main.png

綜合搜尋圖示 Combined Search Icon (combined.png): http://wiki.moztw.org/uimages/3/36/Borngeek_combined.png

網頁搜尋圖示 Web Search Icon (web.png): http://wiki.moztw.org/uimages/9/9c/Borngeek_web.png

圖片搜尋圖示 Images Search Icon (images.png): http://wiki.moztw.org/uimages/6/61/Borngeek_images.png

可調整移駐標記圖示 Resizing Gripper Icon (gripper.png): http://wiki.moztw.org/uimages/5/50/Borngeek_gripper.png

將這些圖像儲存在我們剛剛建立的 skin 目錄裡,檔案結構會像這樣:

+- TutToolbar/
   +- install.rdf
   +- chrome.manifest
   +- chrome/
      +- content/
         +- tuttoolbar.xul
      +- skin/
         +- combined.png
         +- gripper.png
         +- images.png
         +- main.png
         +- web.png

藉由串接樣式表套用圖片 (Applying the Images with CSS)

現在,有這些圖像可以使用,面板目錄也已經註冊,我們可以開始把圖像套用在工具列按鈕上了。我們將會藉由 CSS 來完成。就如我在指南一開始提到的,如果你對 CSS 不熟悉,可以參考 W3Schools 的 excellent CSS tutorial

讓我們開始建立 CSS 檔案吧!在 skin 目錄下,建立一個檔名為 tuttoolbar.css 的檔案,把這個檔案跟圖像放在一起。檔案結構如下:

+- TutToolbar/
   +- install.rdf
   +- chrome.manifest
   +- chrome/
      +- content/
         +- tuttoolbar.xul
      +- skin/
         +- combined.png
         +- gripper.png
         +- images.png
         +- main.png
         +- web.png
         +- tuttoolbar.css

在說明細節前,先讓我們來看看樣式表的內容:

#TutTB-MainMenu {
    list-style-image: url("chrome://tuttoolbar/skin/main.png");
}

#TutTB-Combined-Button {
    list-style-image: url("chrome://tuttoolbar/skin/combined.png");
}

#TutTB-Combined-Button > .toolbarbutton-menubutton-button {
    -moz-box-orient: horizontal;
}

#TutTB-Web-Button,
#TutTB-Combined-Web {
    list-style-image: url("chrome://tuttoolbar/skin/web.png");
}

#TutTB-Combined-Image {
    list-style-image: url("chrome://tuttoolbar/skin/images.png");
}

#TutTB-ResizeBar {
    background-image: url("chrome://tuttoolbar/skin/gripper.png");
    min-height: 22px;
    min-width: 3px;
}

#TutTB-ResizeSplitter {
    background: transparent;
    border: none !important;
}

第一項規則是指定該圖像給「主要選單」按鈕使用。回顧一下第三章,我們給了該按鈕一個 ID 值:TutTB-MainMenu,我們用該值來設定按鈕顯示的圖像。在前面的井號(#)是 ID 選取器(ID selector),也就是用來指定 CSS 元素規則的特定識別值。在工具列按鈕裡,我們使用 CSS 屬性的 list-style-image 來指定圖像。注意到我們使用的是 chrome 路徑,也就是套件檔案結構中的圖像路徑,常用的格式如下:

chrome://<packagename>/skin/<image_file_name>

在我們的範例裡,封裝名稱(package name)是 tuttoolbar,而主要選單使用的圖像名稱為 main.png。你可以看到,所有的工具列按鈕使用類似的規則,在綜合搜尋按鈕的選單項目也是一樣。There's one caveat to handling menu item icons, however. 回想一下,在綜合搜尋選單中,我們為選單項目使用了一個特別的類別值:menuitem-iconic,這是 Firefox 內建的。這個值「啟動」圖示支援使用它的每個選單項目。

想必你也注意到了這個綜合搜尋按鈕的特定規則:

#TutTB-Combined-Button > .toolbarbutton-menubutton-button {
    -moz-box-orient: horizontal;
}

這個特別的 CSS 屬性在這裡使用 -moz-box-orient,這指定按鈕的標籤該如何顯示:水平或是垂直顯示。工具列按鈕標籤的預設值是垂直顯示,也就是顯示在圖示下方。在這個範例,不使用這樣的方式,所以我們改為水平顯示,以使標籤顯示在圖示之後。

在處理可調整的移駐標記(resizing gripper)時,有兩條樣式表的規則。還記得在第四章裡,我們在分離的元素(splitter element)使用的 vbox 元素嗎?我們為移駐標記使用了 vbox 來顯示背景圖像。這次,我們使用 background-image 屬性。我們必須藉由 min-heightmin-width 屬性,提供最小高度與最小寬度,以讓移駐標記顯示出來。vboxhbox 屬性的預設值可能是依內容大小判定。由於 vbox 是空的,預設值就是 0,這也避免我們會看到背景圖像。

我們也為分離的元素提供一條規則。 我們讓背景是透明的,而且不使用邊框(分離元素的預設值是會使用邊框的)。現在,CSS 檔案已經完成,我們要設定覆載(overlay)以使用它。

使用樣式表 (Using the Style Sheet)

只有一行程式碼是需要被加入在 XUL 覆載的,這用來啟動我們剛剛建立的樣式表。這必須放置在 XML 宣告指令的下方,以及覆載(overlay)元素之前。下面,我列出覆載檔案的前幾行需要更動的地方:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://tuttoolbar/skin/tuttoolbar.css"
                 type="text/css"?>

<overlay id="TutTB-Overlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

(... rest of XUL overlay file ...)

就像我們在樣式表中看到的圖像一樣,chrome 路徑被用來指定位置。而 type 屬性只是指明這是 CSS 的檔案。現在讓我們看看覆載檔案在加入這一行後的完整內容[View XUL Overlay Revision 6]:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://tuttoolbar/skin/tuttoolbar.css" type="text/css"?>

<overlay id="TutTB-Overlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
         
    <toolbox id="navigator-toolbox">
    
        <toolbar id="TutTB-Toolbar" toolbarname="Tutorial Toolbar" accesskey="T"
                 class="chromeclass-toolbar" context="toolbar-context-menu" 
                 hidden="false" persist="hidden">
                 
            <toolbaritem flex="0">
                <toolbarbutton id="TutTB-MainMenu" type="menu"
                               tooltiptext="Tutorial Toolbar Main Menu">
                    <menupopup>
                        <menuitem label="Google Home Page" accesskey="G"
                                  tooltiptext="Navigate to Google"
                                  oncommand="TutTB_LoadURL('http://www.google.com/')" />
                        
                        <menuseparator />
                        
                        <menuitem label="Born Geek Website" accesskey="B"
                                  tooltiptext="Navigate to Born Geek"
                                  oncommand="TutTB_LoadURL('http://www.borngeek.com/')" />
                    </menupopup>
                </toolbarbutton>
            </toolbaritem>
            
            <toolbaritem id="TutTB-SearchTerms-TBItem" persist="width">
                <menulist id="TutTB-SearchTerms" editable="true" flex="1"
                          minwidth="100" width="250"
                          onkeypress="TutTB_KeyHandler(event);">
                    <menupopup id="TutTB-SearchTermsMenu" onpopupshowing="TutTB_Populate()" />
                </menulist>
            </toolbaritem>
            
            <splitter id="TutTB-ResizeSplitter" state="open" collapse="none"
                      resizebefore="closest" resizeafter="farthest"
                      tooltiptext="Resize the Search Box">
                <vbox id="TutTB-ResizeBar" />
            </splitter>
            
            <toolbaritem flex="0">
            
                <toolbarbutton id="TutTB-Combined-Button" label="Search"
                               type="menu-button" tooltiptext="Combined Search"
                               oncommand="TutTB_Search(event, 'web')">
                    <menupopup>
                        <menuitem id="TutTB-Combined-Web" label="Web Search"
                                  class="menuitem-iconic" tooltiptext="Search the Web"
                                  oncommand="TutTB_Search(event, 'web'); event.preventBubble();" />
                    
                        <menuitem id="TutTB-Combined-Image" label="Image Search"
                                  class="menuitem-iconic" tooltiptext="Search Images"
                                  oncommand="TutTB_Search(event, 'image'); event.preventBubble();" />
                    </menupopup>
                </toolbarbutton>
                
                <toolbarseparator />
                
                <toolbarbutton id="TutTB-Web-Button" tooltiptext="Search the Web"
                               label="Web Search" oncommand="TutTB_Search(event, 'web')" />
            </toolbaritem>
            
            <toolbarspring />

        </toolbar>
    </toolbox>
</overlay>

別忘記要測試!用動態開發的 Firefox 副本開啟,然後看看套件的樣子。接下來,我們要為工具列注入生命。

第六章:主程式 (Scripting the Toolbar)

套件通常是用 JavaScript 執行的,這是一種簡單易學的程式語言。你將會發現,我們的工具列套件的程式碼是非常簡單的。我們將會大量使用文件物件模型(DOM),這可以讓我們處理單獨的元素。

在我們開始前,你要先知道一個重點。id 屬性必須是整個瀏覽器唯一的,而 JavaScript 的變數及函式也是。在瀏覽器覆載中的JavaScript 是全域性的,因此才會有這個限制。我們將會使用跟 XUL 元素一樣的技巧:所有的變數及函式的名稱,將會有以我們套件為名起始名稱。我們使用 TutTB_ 當作起始名稱,所以我們的搜尋函式會是 TutTB_Search()

現在,讓我們建立 JavaScript 檔案。在 content 目錄中,建立一個 tuttoolbar.js 的文字檔。目錄結構如下:

+- TutToolbar/
   +- install.rdf
   +- chrome.manifest
   +- chrome/
      +- content/
         +- tuttoolbar.xul
         +- tuttoolbar.js
      +- skin/
         +- combined.png
         +- gripper.png
         +- images.png
         +- main.png
         +- web.png
         +- tuttoolbar.css

在建立任何程式碼前,先告訴 XUL 檔案該如何使用 JavaScript 檔案。

連結 XUL 與 JavaScript (Tying XUL to JavaScript)

我們的 XUL 檔案 tuttoolbar.xul 需要被告知相對應的 JavaScript 檔,我們可以使用下面的描述:

<script type="application/x-javascript"
        src="chrome://tuttoolbar/content/tuttoolbar.js" />

這必須放置在 overlay 元素之中。type 屬性是指定使用 JavaScript,src 屬性是指定剛剛建立的 JavaScript 檔。內容如下[View XUL Overlay Revision 7]:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://tuttoolbar/skin/tuttoolbar.css" type="text/css"?>

<overlay id="TutTB-Overlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
         
    <script type="application/x-javascript"
            src="chrome://tuttoolbar/content/tuttoolbar.js" />
            
    <toolbox id="navigator-toolbox">
    
        <toolbar id="TutTB-Toolbar" toolbarname="Tutorial Toolbar" accesskey="T"
                 class="chromeclass-toolbar" context="toolbar-context-menu" 
                 hidden="false" persist="hidden">
                 
            <toolbaritem flex="0">
                <toolbarbutton id="TutTB-MainMenu" type="menu"
                               tooltiptext="Tutorial Toolbar Main Menu">
                    <menupopup>
                        <menuitem label="Google Home Page" accesskey="G"
                                  tooltiptext="Navigate to Google"
                                  oncommand="TutTB_LoadURL('http://www.google.com/')" />
                        
                        <menuseparator />
                        
                        <menuitem label="Born Geek Website" accesskey="B"
                                  tooltiptext="Navigate to Born Geek"
                                  oncommand="TutTB_LoadURL('http://www.borngeek.com/')" />
                    </menupopup>
                </toolbarbutton>
            </toolbaritem>
            
            <toolbaritem id="TutTB-SearchTerms-TBItem" persist="width">
                <menulist id="TutTB-SearchTerms" editable="true" flex="1"
                          minwidth="100" width="250"
                          onkeypress="TutTB_KeyHandler(event);">
                    <menupopup id="TutTB-SearchTermsMenu" onpopupshowing="TutTB_Populate()" />
                </menulist>
            </toolbaritem>
            
            <splitter id="TutTB-ResizeSplitter" state="open" collapse="none"
                      resizebefore="closest" resizeafter="farthest"
                      tooltiptext="Resize the Search Box">
                <vbox id="TutTB-ResizeBar" />
            </splitter>
            
            <toolbaritem flex="0">
            
                <toolbarbutton id="TutTB-Combined-Button" label="Search"
                               type="menu-button" tooltiptext="Combined Search"
                               oncommand="TutTB_Search(event, 'web')">
                    <menupopup>
                        <menuitem id="TutTB-Combined-Web" label="Web Search"
                                  class="menuitem-iconic" tooltiptext="Search the Web"
                                  oncommand="TutTB_Search(event, 'web'); event.preventBubble();" />
                    
                        <menuitem id="TutTB-Combined-Image" label="Image Search"
                                  class="menuitem-iconic" tooltiptext="Search Images"
                                  oncommand="TutTB_Search(event, 'image'); event.preventBubble();" />
                    </menupopup>
                </toolbarbutton>
                
                <toolbarseparator />
                
                <toolbarbutton id="TutTB-Web-Button" tooltiptext="Search the Web"
                               label="Web Search" oncommand="TutTB_Search(event, 'web')" />
            </toolbaritem>
            
            <toolbarspring />

        </toolbar>
    </toolbox>
</overlay>

為按鈕加上功能 (Adding Functionality to the Buttons)

還記得我們為工具列按鈕提供的 oncommand 屬性嗎?這個屬性是指定程式碼執行的事件。我們也可以使用 onclick 代替 oncommand,但是 onclick 對鍵盤無效,而 oncommand 可以同時回應鍵盤與滑鼠的動作。讓我們回顧一下搜尋按鈕:

oncommand="TutTB_Search(event, 'web')"

這個值呼叫 TutTB_Search() 函式,其中包含兩個值:這個函式呼叫的事件(event),以及搜尋的網頁(web)。

程式碼如下[View JavaScript Revision 1]:

////////////////////////////////////////////////////////////////////////////////
// TutTB_Search() 函式會為我們完成搜尋。event 參數會觸發函式的呼叫,type 參數
// 為搜尋的類型。
////////////////////////////////////////////////////////////////////////////////
function TutTB_Search(event, type)
{
    // 將要瀏覽的網址
    var URL = "";

    // 辨別搜尋欄位是否為空值
    var isEmpty = false;

    // 處理搜尋欄位 ( <menulist> 元素)
    var searchTermsBox = document.getElementById("TutTB-SearchTerms");
    
    // 從搜尋欄位取出字串,整理必要的空白
    // 可以在下面看看 TutTB_TrimString() 函式運作的細節

    var searchTerms = TutTB_TrimString(searchTermsBox.value);

    if(searchTerms.length == 0) // Is the search terms box empty?
        isEmpty = true;         // If so, set the isEmpty flag to true
    else                        // If not, convert the terms to a URL-safe string
        searchTerms = TutTB_ConvertTermsToURI(searchTerms);

    // 選擇搜尋的類型
    // 如果欄位是空白的,我們會引導使用者到 Google 網頁中適當的地方。
    // 否則,搜尋輸入的文字。

    switch(type)
    {
    // 建立圖片搜尋的網址
    case "image":
        if(isEmpty) { URL = "http://images.google.com/"; }
        else        { URL = "http://images.google.com/images?q=" + searchTerms; }
        break;


    // 建立網頁搜尋的網址
    case "web":
    default:
        if(isEmpty) { URL = "http://www.google.com/"; }
        else        { URL = "http://www.google.com/search?q=" + searchTerms; }
        break;
    }
    
    // 使用 TutTB_LoadURL 函式在瀏覽視窗中載入網址
    TutTB_LoadURL(URL);
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_TrimString() 函式會修剪字串的前後空白部份,然後將所有的空白部份轉換成
// 單一個空白,然後傳回修改後字串。
////////////////////////////////////////////////////////////////////////////////
function TutTB_TrimString(string)
{
    // 如果傳入的字串無效,或是沒有東西,則傳回空值
    if (!string)
        return "";

    string = string.replace(/^\s+/, ''); // Remove leading whitespace
    string = string.replace(/\s+$/, ''); // Remove trailing whitespace

    // 將所有空白部份用單一空白代替
    string = string.replace(/\s+/g, ' ');

    return string; // 傳回改變後的值
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_ConvertTermsToURI() 函式將傳入的搜尋字串轉換為安全的網址
////////////////////////////////////////////////////////////////////////////////
function TutTB_ConvertTermsToURI(terms)
{
    // 建立陣列存放每個搜尋字串
    var termArray = new Array();

    // 用空白字元切割字串
    termArray = terms.split(" ");

    // 用來存放安全的網址
    var result = "";

    // 在字串中遞迴
    for(var i=0; i<termArray.length; i++)
    {
        // 在第一個字串後的所有的字串會用加號(+)隔開
        if(i > 0)
            result += "+";

        // 使用 Firefox 內建的 encodeURIComponent() 函式加密
        result += encodeURIComponent(termArray[i]);
    }

    return result; // 傳回結果
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_LoadURL() 函式在瀏覽器中載入特定的網址
////////////////////////////////////////////////////////////////////////////////
function TutTB_LoadURL(url)
{
    // 在網址列設定傳入的網址
    window._content.document.location = url;

    // Make sure that we get the focus
    window.content.focus();
}

開啟你的 Firefox 開發版本,在我們的工具列套件輸入搜尋的值,然後按下網頁搜尋按鈕。程式碼應該會執行,然後會出現搜尋結果。這個動態開發版本確實是相當便利的吧!

按鈕選單的注意事項 (A Special Note About Button-Menu Buttons)

在我們進入下一張章節之前,有一點關於按鈕選單所要注意的。回想一下,這類型的按鈕提供可以點擊的按鈕與彈出式選單(主要範例為往前與向後瀏覽)。也想一下,toolbarbutton 以及包含 oncommand 的 menuitem 元素。當工具列按鈕的一部分被啟動,toolbarbutton 元素的 oncommand 程式碼如你所期望的被執行。但是,當使用者啟動了menuitem 元素的其中一個時,會發生什麼事?

不只是 menuitem 元素的 oncommand 事件執行,toolbarbutton 元素的事件也被執行。所以兩個事件是同時執行的,儘管你只是想要其中一件被執行。因為 toolbarbutton 事件是最後發生的,所以實際執行的是 toolbarbutton 事件。換句話說,menuitem 事件沒有機會執行。那我們要怎樣解決這個問題呢?事實上,是有辦法的。讓我們往前看看 menuitem 元素:

<menuitem id="TutTB-Combined-Image" label="Image Search"
          class="menuitem-iconic" tooltiptext="Search Images"
          oncommand="TutTB_Search(event, 'image'); event.preventBubble();" />

藉由使用 DOM 函式 preventBubble(),我們可以避免 oncommand 被執行。為了避免夜長夢多,記住一件事,當你建立按鈕選單時,記得在 menuitem 元素的 oncommand 屬性加入 event.preventBubble()

為搜尋列加入功能 (Adding Functionality to the Search Box)

現在,我們已經讓搜尋按鈕可以使用,接下來,我們需要幫搜尋列加入一些功能。讓使用者可以在輸入字串後按下「Enter」以執行搜尋。要怎麼做呢?讓我們看看要使用的函式:

<toolbaritem id="TutTB-SearchTerms-TBItem" persist="width">
    <menulist id="TutTB-SearchTerms" editable="true" flex="1"
              minwidth="100" width="250"
              onkeypress="TutTB_KeyHandler(event);">
        <menupopup id="TutTB-SearchTermsMenu" onpopupshowing="TutTB_Populate()" />
    </menulist>
</toolbaritem>

這次,我們使用 onkeypress 代替 oncommand。這個事件會在使用者在搜尋列按下按鍵時觸發。在這個範例,我們建立一個相當簡單的 TutTB_KeyHandler()函式:

function TutTB_KeyHandler(event)
{
    if(event.keyCode == event.DOM_VK_RETURN)
        TutTB_Search(event, 'web');
}

這個函式只不過是要檢查「Enter」是否被按下。如果是,TutTB_Search()會被呼叫。否則,不做任何事。讓我們來看看加入此函式的 JavaScript 檔案[View JavaScript Revision 2]:

////////////////////////////////////////////////////////////////////////////////
// TutTB_Search() 函式會為我們完成搜尋。event 參數會觸發函式的呼叫,type 參數
// 為搜尋的類型。
////////////////////////////////////////////////////////////////////////////////
function TutTB_Search(event, type)
{
    // 將要瀏覽的網址
    var URL = "";

    // 辨別搜尋欄位是否為空值
    var isEmpty = false;

    // 處理搜尋欄位 ( <menulist> 元素)
    var searchTermsBox = document.getElementById("TutTB-SearchTerms");
    
    // 從搜尋欄位取出字串,整理必要的空白
    // 可以在下面看看 TutTB_TrimString() 函式運作的細節

    var searchTerms = TutTB_TrimString(searchTermsBox.value);

    if(searchTerms.length == 0) // 搜尋欄位是空白的嗎?
        isEmpty = true;         // 是,設定 isEmpty 為 true
    else                        // 不,轉換為安全的網址
        searchTerms = TutTB_ConvertTermsToURI(searchTerms);

    // 選擇搜尋的類型
    // 如果欄位是空白的,我們會引導使用者到 Google 網頁中適當的地方。
    // 否則,搜尋輸入的文字。

    switch(type)
    {
    // 建立圖片搜尋的網址
    case "image":
        if(isEmpty) { URL = "http://images.google.com/"; }
        else        { URL = "http://images.google.com/images?q=" + searchTerms; }
        break;


    // 建立網頁搜尋的網址
    case "web":
    default:
        if(isEmpty) { URL = "http://www.google.com/"; }
        else        { URL = "http://www.google.com/search?q=" + searchTerms; }
        break;
    }
    
    // 使用 TutTB_LoadURL 函式在瀏覽視窗中載入網址
    TutTB_LoadURL(URL);
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_TrimString() 函式會修剪字串的前後空白部份,然後將所有的空白部份轉換成
// 單一個空白,然後傳回修改後字串。
////////////////////////////////////////////////////////////////////////////////
function TutTB_TrimString(string)
{
    // 如果傳入的字串無效,或是沒有東西,則傳回空值
    if (!string)
        return "";

    string = string.replace(/^\s+/, ''); // Remove leading whitespace
    string = string.replace(/\s+$/, ''); // Remove trailing whitespace

    // 將所有空白部份用單一空白代替
    string = string.replace(/\s+/g, ' ');

    return string; // 傳回改變後的值
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_ConvertTermsToURI() 函式將傳入的搜尋字串轉換為安全的網址
////////////////////////////////////////////////////////////////////////////////
function TutTB_ConvertTermsToURI(terms)
{
    // 建立陣列存放每個搜尋字串
    var termArray = new Array();

    // 用空白字元切割字串
    termArray = terms.split(" ");

    // 用來存放安全的網址
    var result = "";

    // 在字串中遞迴
    for(var i=0; i<termArray.length; i++)
    {
        // 在第一個字串後的所有的字串會用加號(+)隔開
        if(i > 0)
            result += "+";

        // 使用 Firefox 內建的 encodeURIComponent() 函式加密
        result += encodeURIComponent(termArray[i]);
    }

    return result; // 傳回結果
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_LoadURL() 函式在瀏覽器中載入特定的網址
////////////////////////////////////////////////////////////////////////////////
function TutTB_LoadURL(url)
{
    // 在網址列設定傳入的網址
    window._content.document.location = url;

    // Make sure that we get the focus
    window.content.focus();
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_KeyHandler() 檢查「Enter」是否被輸入。是的話,就執行搜尋
////////////////////////////////////////////////////////////////////////////////
function TutTB_KeyHandler(event)
{
    // [ENTER]被按下了嗎?如果是,執行搜尋
    if(event.keyCode == event.DOM_VK_RETURN)
        TutTB_Search(event, 'web');
}

在一次,執行你的 Firefox 開發版本,在搜尋列輸入搜尋字串,然後按下「Enter」。就像是按下搜尋按鈕一樣,搜尋結果應該會出現。現在,我們的套件已經有了生命!

動態置入選單 (Dynamically Populating a Menu)

動態的將選單項目放置在選單裡是很有用的,還好這個特色是很容易完成的。在這個指南裡,我們將會在搜尋列下拉式選單動態的加入一些項目。讓我們看看用來建立工具列的部份:

<toolbaritem id="TutTB-SearchTerms-TBItem" persist="width">
    <menulist id="TutTB-SearchTerms" editable="true" flex="1"
              minwidth="100" width="250"
              onkeypress="TutTB_KeyHandler(event);">
        <menupopup id="TutTB-SearchTermsMenu" onpopupshowing="TutTB_Populate()" />
    </menulist>
</toolbaritem>

注意到,在 menupopup 元素的 onpopupshowing 屬性。我們指定 TutTB_Populate()函式在每次「彈出式選單」即將顯示時被執行,這個函式將會建立我們的動態選單項目。讓我們來看看函式的程式碼[View TutTB_Populate() Code]:

////////////////////////////////////////////////////////////////////////////////
// TutTB_Populate() 函式在工具列欄位的下拉式選單放置動態產生的選單項目。
// 雖然這不是非常實用的,但是藉由這個範例,我們可以看看動態選單置入的運作方式。
////////////////////////////////////////////////////////////////////////////////
function TutTB_Populate()
{
    // 取得彈出選單元素
    var menu = document.getElementById("TutTB-SearchTermsMenu");

    // 移除在彈出選單目前的項目
    for(var i=menu.childNodes.length - 1; i >= 0; i--)
    {
        menu.removeChild(menu.childNodes.item(i));
    }

    // 指定要在選單增加的項目數量
    var numItemsToAdd = 10;

    for(var i=0; i<numItemsToAdd; i++)
    {
        // 建立要增加的新選單項目
        var tempItem = document.createElement("menuitem");

        // 設定新選單項目的標籤
        tempItem.setAttribute("label", "Dynamic Item Number " + (i+1));

        // 在選單中新增項目
        menu.appendChild(tempItem);
    }
}

這個函式範例不會命令每個 menuitem 元素去執行選擇的指令。為了讓每個 menuitem 知道應該執行的指令,我們僅僅只要加入 setAttribute()函式來處理事件及執行程式碼。這是處理 oncommand 事件的範例:

tempItem.setAttribute("oncommand", "TutTB_SomeFunction()");

加入 TutTB_Populate()函式後的 JavaScript 檔案[View JavaScript Revision 3]:

////////////////////////////////////////////////////////////////////////////////
// TutTB_Search() 函式會為我們完成搜尋。event 參數會觸發函式的呼叫,type 參數
// 為搜尋的類型。
////////////////////////////////////////////////////////////////////////////////
function TutTB_Search(event, type)
{
    // 將要瀏覽的網址
    var URL = "";

    // 辨別搜尋欄位是否為空值
    var isEmpty = false;

    // 處理搜尋欄位 ( <menulist> 元素)
    var searchTermsBox = document.getElementById("TutTB-SearchTerms");
    
    // 從搜尋欄位取出字串,整理必要的空白
    // 可以在下面看看 TutTB_TrimString() 函式運作的細節

    var searchTerms = TutTB_TrimString(searchTermsBox.value);

    if(searchTerms.length == 0) // 搜尋欄位是空白的嗎?
        isEmpty = true;         // 是,設定 isEmpty 為 true
    else                        // 不,轉換為安全的網址
        searchTerms = TutTB_ConvertTermsToURI(searchTerms);

    // 選擇搜尋的類型
    // 如果欄位是空白的,我們會引導使用者到 Google 網頁中適當的地方。
    // 否則,搜尋輸入的文字。

    switch(type)
    {
    // 建立圖片搜尋的網址
    case "image":
        if(isEmpty) { URL = "http://images.google.com/"; }
        else        { URL = "http://images.google.com/images?q=" + searchTerms; }
        break;


    // 建立網頁搜尋的網址
    case "web":
    default:
        if(isEmpty) { URL = "http://www.google.com/"; }
        else        { URL = "http://www.google.com/search?q=" + searchTerms; }
        break;
    }
    
    // 使用 TutTB_LoadURL 函式在瀏覽視窗中載入網址
    TutTB_LoadURL(URL);
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_TrimString() 函式會修剪字串的前後空白部份,然後將所有的空白部份轉換成
// 單一個空白,然後傳回修改後字串。
////////////////////////////////////////////////////////////////////////////////
function TutTB_TrimString(string)
{
    // 如果傳入的字串無效,或是沒有東西,則傳回空值
    if (!string)
        return "";

    string = string.replace(/^\s+/, ''); // 移除前面空白
    string = string.replace(/\s+$/, ''); // 移除後面空白

    // 將所有空白部份用單一空白代替
    string = string.replace(/\s+/g, ' ');

    return string; // 傳回改變後的值
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_ConvertTermsToURI() 函式將傳入的搜尋字串轉換為安全的網址
////////////////////////////////////////////////////////////////////////////////
function TutTB_ConvertTermsToURI(terms)
{
    // 建立陣列存放每個搜尋字串
    var termArray = new Array();

    // 用空白字元切割字串
    termArray = terms.split(" ");

    // 用來存放安全的網址
    var result = "";

    // 在字串中遞迴
    for(var i=0; i<termArray.length; i++)
    {
        // 在第一個字串後的所有的字串會用加號(+)隔開
        if(i > 0)
            result += "+";

        // 使用 Firefox 內建的 encodeURIComponent() 函式加密
        result += encodeURIComponent(termArray[i]);
    }

    return result; // 傳回結果
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_LoadURL() 函式在瀏覽器中載入特定的網址
////////////////////////////////////////////////////////////////////////////////
function TutTB_LoadURL(url)
{
    // 在網址列設定傳入的網址
    window._content.document.location = url;

    // Make sure that we get the focus
    window.content.focus();
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_KeyHandler() 檢查「Enter」是否被輸入。是的話,就執行搜尋
////////////////////////////////////////////////////////////////////////////////
function TutTB_KeyHandler(event)
{
    // [ENTER]被按下了嗎?如果是,執行搜尋
    if(event.keyCode == event.DOM_VK_RETURN)
        TutTB_Search(event, 'web');
}

////////////////////////////////////////////////////////////////////////////////
// TutTB_Populate() 函式在工具列欄位的下拉式選單放置動態產生的選單項目。
// 雖然這不是非常實用的,但是藉由這個範例,我們可以看看動態選單置入的運作方式。
////////////////////////////////////////////////////////////////////////////////
function TutTB_Populate()
{
    // 取得彈出選單元素
    var menu = document.getElementById("TutTB-SearchTermsMenu");

    // 移除在彈出選單目前的項目
    for(var i=menu.childNodes.length - 1; i >= 0; i--)
    {
        menu.removeChild(menu.childNodes.item(i));
    }

    // 指定要在選單增加的項目數量
    var numItemsToAdd = 10;

    for(var i=0; i<numItemsToAdd; i++)
    {
        // 建立要增加的新選單項目
        var tempItem = document.createElement("menuitem");

        // 設定新選單項目的標籤
        tempItem.setAttribute("label", "Dynamic Item Number " + (i+1));

        // 在選單中新增項目
        menu.appendChild(tempItem);
    }
}

動態加入工具列按鈕 (Dynamically Adding Toolbar Buttons)

加入動態工具列按鈕就跟動態置入選單一樣簡單,而且還非常相似。首先,我們需要一個包含動態按鈕的容器。在這兒, toolbaritem 元素是不錯的選擇,標記如下:

<toolbaritem id="TutTB-DynButtonContainer" />

現在,容器已經可以利用了,我們可以使用它,來加入動態 toolbarbutton 元素,讓我們來看一下可以讓我們加入動態按鈕的函式範例:

function TutTB_AddDynamicButtons()
{
    // 取得我們在 XUL 描述中增加的工具列項目「容器」
    var container = document.getElementById("TutTB-DynButtonContainer");
    
    // 移除所有存在的按鈕
    for(i=container.childNodes.length; i > 0; i--) {
        container.removeChild(container.childNodes[0]);
    }

    // 增加五個動態按鈕
    for(var i=0; i<5; i++) {
        var tempButton = null;
        tempButton = document.createElement("toolbarbutton");
        tempButton.setAttribute("label", "Button " + i);
        tempButton.setAttribute("tooltiptext", "Button " + i);
        tempButton.setAttribute("oncommand", "TutTB_SomeFunction()");
        container.appendChild(tempButton);
    }
}

這個函式和置入動態選單非常相似。我們從容器中移除現有的動態按鈕,然後加入新的。

Optional Programming Exercise:試著用這個概念來增加「搜尋字串按鈕(search word buttons)」到工具列。當使用者在搜尋字串按鈕鍵入文字時,動態地增加按鈕到工具列的最後,一個按鈕一個字。這兒有幾個提示:

  1. 你需要增加 toolbaritem 容器到 XUL 描述檔的最後(right before the toolbarspring element is a good choice)。確定 ID 是唯一的。
  2. 在 menulist 元素中使用 oninput 事件,這個事件會在使用者輸入文字時觸發。
  3. 你呼叫的 JavaScript 會做下列的事:(a)在搜尋欄位中取得搜尋文字(b)移除所有先前建立的動態按鈕(c)以空白分開搜尋文字(d)為每個獨立的文字建立按鈕。

這就是我處理 Googlebar Lite 的方式。

關閉與啟動按鈕 (Disabling and Enabling Buttons)

(這部份只是告訴你怎麼用,在這個工具列範例並不會出現)。

你偶爾可能會想要關閉或是啟動工具列按鈕。舉個例子,Googlebar Lite 的高亮度標記按鈕不會在搜尋欄位沒有搜尋字串時發生作用,只能執行於有搜尋字串時。

下面的例子是如何建立一個可以控制關閉的一般工具列按鈕。再次提醒,這不是實用的範例,只是證明這個效果是可以做得到的。接著是選單項目的描述標記:

<menuitem label="Toggle Web Search Button"
          tooltiptext="Toggle Web Search Button"
          oncommand="TutTB_ToggleWebSearchButton()" />

TutTB_ToggleWebSearchButton()函式的程式碼:

function TutTB_ToggleWebSearchButton()
{
    var button = document.getElementById("TutTB-Web-Button");
    var value = button.disabled;
    if(value == true)
        button.disabled = false;
    else
        button.disabled = true;
}

首先,我們對有興趣的工具列按鈕使用 ID 值以及 getElementById()函式。只要有按鈕,我們就能使用 disabled 屬性控制該按鈕物件的現時狀態(啟動或關閉)。如果值為 true,就使用 false 取消按鈕關閉,反之亦然。Firefox 會幫我們處理按鈕的關閉,讓該按鈕不能點擊(note that the button's image may not appeared grayed out: we must rely on skinning for that)。

動態顯示與隱藏按鈕 (Dynamically Showing and Hiding Buttons)

(這部份只是告訴你怎麼用,在這個工具列範例並不會出現)。

Googlebar Lite 允許使用者選擇按鈕在工具列所看到的樣子。也就是說,工具列必須能夠動態的顯示或是隱藏可用的按鈕。這是我們要完成的 JavaScript程式碼片段:

var TB_Web = document.getElementById("TutTB-Web-Button");
TB_Web.setAttribute("hidden", !TutTB_ShowWebButton);

首先我們讓工具列按鈕使用 getElementById() 函式,然後我們呼叫 setAttribute()函式來改變 hidden 屬性的值。你會發現這個 hidden 值跟我在 TutTB_ShowWebButton 所設定的相反。這個值是個布林標籤(boolean flag),用來辨別使用者是否想要看到「網頁搜尋」按鈕。儲存在這個變數的值,是從使用者設定中讀取到的。繼續閱讀,來看看我們如何做到這點。

讀取與儲存使用者設定 (Reading and Storing User Preferences)

(這部份只是告訴你怎麼用,在這個工具列範例並不會出現)。

為了讀取所儲存的設定,我們需要存取 Firefox 設定服務介面(preferences service interface)。我們可以建立一個變數來完成:

const TutTB_PrefService =
  Components.classes["@mozilla.org/preferences-service;1"].
    getService(Components.interfaces.nsIPrefService);

一旦存取設定介面,我們就可以獲得套件設定的一部分(i.e. the location in the preferences "registry" where our extension's settings are kept):

const TutTB_Branch = TutTB_PrefService.getBranch("tuttoolbar.");

假設這個套件範例的設定起始於 tuttoolbar 文字,因此,我們設定字串 getBranch() (內建的 Fireofx 函式) 。這個函式可以讓我們存取套件的設定,最後可以獲得個別選項的值。這是我們剛剛提到「顯示網頁搜尋按鈕」選項可以取得的值:

if(TutTB_Branch.prefHasUserValue("show.button.web"))
    TutTB_ShowWebButton = TutTB_Branch.getBoolPref("show.button.web");
else
{
    TutTB_Branch.setBoolPref("show.button.web", false);
    TutTB_ShowWebButton = false;
}

首先,我們測試這個設定是否存在於設定樹狀結構裡。如果存在,就用 getBoolPref()函式讀取它的值(儲存於 TutTB_ShowWebButton 變數)。否則,我們就設定為預設值(設定的樹與全域變數)。

儲存設定是很簡單的吧!你只要提供設定的名稱,然後使用這個值:

TutTB_Branch.setBoolPref("show.button.web", TutTB_ShowWebButton);

「讀取與儲存設定」用來儲存你的套件設定是很方便的方式。大部分的套件都使用這個功能,有許多可使用的範例等著你去研究。另外,可以看看 MozillaZine knowledge base 的文章

第七章:封裝 (Packaging the Toolbar)

到目前為止,我們用動態開發來處理安裝的部份。但是使用者並不會想用這樣的方式來安裝套件,所以我們必須封裝檔案成一個安裝檔。我們將要建立兩個檔案:XPI 及 JAR。別被這樣的檔案名稱給騙了,他們只不過是用 zip 壓縮檔案格式偽裝的。

現在是壓縮工具發揮功效的時候了!我個人建議使用 WinZip (命令列模式),但是網路上也有不少免費的工具。在這份指南中 , 我會使用 UNIX 壓縮工具;在 Windows 中,可以使用 Cygwin

更新 Chrome 清單 (Updating the Chrome Manifest)

第一步封裝包含了更新 chrome 清單。注意,如果要繼續動態開發,你要有兩個 chrome 清單:一個是封裝用的,另一個是動態開發用的。處理這兩個檔案是很麻煩的,所以我們只針對指南中已存在的 chrome 清單做處理。注意,在我們修改清單後,就不能用在動態開發了,除非你修改回來。讓我們先來回憶一下清單的內容:

content tuttoolbar chrome/content/
overlay chrome://browser/content/browser.xul chrome://tuttoolbar/content/tuttoolbar.xul
skin tuttoolbar classic/1.0 chrome/skin/

有兩個地方需要修改,先看看第一行:

content tuttoolbar jar:chrome/tuttoolbar.jar!/content/

我們加入了兩個項目。首先是 jar: ,放置在路徑的前面。這指定 Firefox 必須從 JAR 檔案中取出套件的內容。再來,我們加入 tuttoolbar.jar!/,介於 chrome/ 和 content/ 目錄中間,這描述了哪個 JAR 需要被取出的。這個 JAR 的檔名必須要跟套件的檔名相同,同樣地,必須跟檔名一樣使用小寫字體。注意,驚嘆號不是打字錯誤,這是必要的。另一個需要修改的路徑是 skin ,來看看修改後的內容:

content tuttoolbar jar:chrome/tuttoolbar.jar!/content/
overlay chrome://browser/content/browser.xul chrome://tuttoolbar/content/tuttoolbar.xul
skin tuttoolbar classic/1.0 jar:chrome/tuttoolbar.jar!/skin/

建立 JAR 檔案 (Creating the JAR File)

第一個要封裝的是 JAR 檔案。首先我們處理 chrome 目錄,包含了 XUL、JavaScript、CSS 以及圖片。檔案結構如下:

+- TutToolbar/
   +- install.rdf
   +- chrome.manifest
   +- chrome/
      +- tuttoolbar.jar
      +- content/
         +- tuttoolbar.xul
         +- tuttoolbar.js
      +- skin/
         +- combined.png
         +- gripper.png
         +- images.png
         +- main.png
         +- web.png
         +- tuttoolbar.css

封裝 JAR 或是 XPI 時,最容易犯的錯誤就是遺漏了相關的檔案。如果我們假裝 JAR 檔案是個目錄,裡面包含了:

+- content/
   +- tuttoolbar.xul
   +- tuttoolbar.js
+- skin/
   +- combined.png
   +- gripper.png
   +- images.png
   +- main.png
   +- web.png
   +- tuttoolbar.css

這是我用 UNIX 工具的方式(命令列模式):

zip -r tuttoolbar.jar content/* skin/*

(譯者:一般 Windows 使用者可以直接將 content 及 skin 目錄以 zip 格式壓縮成副檔名 .jar 的檔案。)

建立 XPI 檔案(Creating the XPI File)

在建立好 JAR 檔案之後,我們必須建立 XPI 檔案,這是真正你要給使用者用的套件安裝檔。我們必須在最上層目錄中建立這個檔案,結構如下:

+- TutToolbar/
   +- tuttoolbar.xpi
   +- install.rdf
   +- chrome.manifest
   +- chrome/
      +- tuttoolbar.jar
      +- content/
         +- tuttoolbar.xul
         +- tuttoolbar.js
      +- skin/
         +- combined.png
         +- gripper.png
         +- images.png
         +- main.png
         +- web.png
         +- tuttoolbar.css

跟 JAR 檔案一樣,XPI 必須維持相對的路徑。在 XPI 檔案中,包含了三個檔案:安裝清單、chrome 清單及剛剛建立的 JAR 檔案。XPI 裡包含了:

+- install.rdf
+- chrome.manifest
+- chrome/
   +- tuttoolbar.jar

使用 UNIX 工具的方式:

zip tuttoolbar.xpi install.rdf chrome.manifest chrome/tuttoolbar.jar

套件安裝測試 (Installing Your Toolbar Extension)

有了 XPI 檔案之後,就可以開始安裝的步驟。在發表套件前,你一定要先確定套件可以安裝。不可以使用開發設定檔來測試安裝,儘管在那份設定檔已經有套件的安裝版本。用你一般使用的,或是另一個設定檔,依下面的步驟來測試安裝:

  1. 檔案 > 開啟檔案 (或是用 Ctrl+O) 。
  2. 在對話框中,找到你的 XPI 檔案,點選「開啟」。

如果正確地完成後,你會看到安裝畫面。選擇你要安裝後,重新開啟 Firefox。之後,你應該會看到工具列了,恭喜你完成第一個工具列套件!

加速封裝過程 (Speeding Up the Packaging Process)

手動封裝套件很讓人厭煩的,為何不用描述檔來完成呢?如果有命令列模式的 ZIP 工具,你可以將過程自動化。下面的範例使用 DOS 批次檔及 UNIX 壓縮工具。

首先建立兩個文字檔:最上層目錄的 xpizip.txt ,及 chrome 目錄中的 jarzip.txt 。 檔案中包含了要封裝的項目: [View xpizip.txt] [View jarzip.txt]

下一步,在最上層目錄中建立 DOS 批次檔(.bat),內容如下:

zip -r chrome/tuttoolbar.jar -@ < chrome/jarzip.txt
zip -r tuttoolbar.xpi -@ < xpizip.txt

如你所見,這個描述檔相當簡單。然而,你的描述檔可能比這個更複雜。我用來建立 Googlebar Lite 的描述檔可以做更多事。它可以為我自動更新所有的版本代號,及完成檔案的封裝。這個描述檔是用 Perl 完成的,而且使用 1.0.x 的安裝方案(與這份指南有些微的不同)。我提供這個描述檔給有興趣的人:[View the Googlebar Lite Build Script]。

(Ant Support)

如果你寧願使用 Ant 來建立你的套件,可以參考 sample XML file sent in by Brett Clippingdale. 這個描述檔應該跟安裝清單放置在同一個目錄,然後使用 ant 指令。

第八章:測試 (Testing Our Extension)

在第四章討論的動態開發對於套件測試有很大的幫助,但是這裡還有一些竅門是開發者需要知道的。JavaScript 是很難除錯的 ,幸好 Firefox 的一些特色可以讓我們避免那些惱人的警示(此指 alert() 函式呼叫)。

當 Firefox 損毀時該怎麼辦 (What to Do if Firefox Breaks)

有時候套件會產生災難性的錯誤,導致 Firefox 拒絕開啟。當這種情況發生時,你必須先檢查工作管理員是否有當掉的 Firefox 在執行中,並把它關閉。一旦確定沒有 Firefox 在執行,你需要移除有問題的套件。移除的方法取決於你是否正在使用動態開發系統。

如果你正在用動態開發,只要瀏覽動態開發設定的套件目錄(第四章),然後將套件的指標檔案(tuttoolbar@borngeek.com)移動到暫存的位置。用你的動態開發設定檔重新開啟 Firefox (允許它清除最新移除的套件), 然後再次關閉瀏覽器。在你解決這個導致 Firefox 當掉的問題之後,將你的指標檔案移回到套件目錄,然後重新開啟 Firefox 。

如果你沒有使用動態開發的話,就用 Firefox 的安全模式來移除有問題的套件。你可以用兩種方式來:

  1. 開始 > 所有程式 > Mozilla Firefox >> Mozilla Firefox (Safe Mode) 。
  2. 在 Firefox 捷徑中加入 -safe-mode 命令列參數。

在安全模式下, Firefox 不會載入任何套件或是主題。一旦在安全模式中,你就可以使用套件管理員來移除有問題的套件。在移除套件之後,記得將 -safe-mode 命令列參數移除(如果你在捷徑中手動加入)。

有用的瀏覽器設定 (Useful Browser Settings)

在 about:config 介面中,有兩個設定可以讓你更容易開發,這會影響 JavaScript 主控台的輸出訊息。

第一個設定是 javascript.options.showInConsole 。當設定為 true 時,所有套件產生的錯誤或是警告都會被傳送到 JavaScript 主控台。預設值是 false , 所以你必須開啟它。這個設定對於追蹤錯誤是很方便的。

下一個是 javascript.options.strict ,預設值也是 false 。當設定為 true 時,Firefox 的 JavaScript 語法會變成嚴謹模式,將你的程式碼變成嚴格的約束。這讓你的程式碼更完善,也更容易追蹤程式碼錯誤。

JavaScript 主控台紀錄 (Logging to the JavaScript Console)

測試 JavaScript 程式碼的好方法就是在 JavaScript 主控台中印出除錯值,你可以在 「工具 > JavaScript 主控台」開啟。我們必須先得到 nsIConsoleService 介面的實體(instance),下面是程式碼的片段:

const TutTB_ConsoleService =
      Components.
          classes['@mozilla.org/consoleservice;1'].
              getService(Components.interfaces.nsIConsoleService);

注意到我又用了 TutTB_ 的前置詞,就如我使用於所有函式與變數一樣。一旦我們取得這個控制台實體(console instance),我們就可以使用它來編寫自己的訊息。下面的函式會幫我們完成這件事:

function TutTB_Log(aMessage)
{
    TutTB_ConsoleService.logStringMessage('Tut_Toolbar: ' + aMessage);
}

實際上,你應該改變在你的套件中「My_Extension:」的訊息部份名稱。藉由使用你的套件名稱來當前置詞,你可以更容易取得哪些訊息是要知道的。現在,讓函式作用之後,我們可以在程式碼中的任一位置來呼叫它,以顯示除錯訊息:

TutTB_Log("The value of the URL variable is: " + URL);

記得一定要在發表你的套件前移除這個函式。不然,這不只是降低主控台紀錄的速度,還增加了使用者主控台視窗的非必要訊息。要更進一步地了解 JavaScript 主控台,可以參考 MozillaZine knowledge base article

標準主控台紀錄 (Logging to the Standard Console)

另一個紀錄除錯資訊的方法,就是使用標準主控台的技巧。在使用這個方法之前,有一些瀏覽器的修改必須要先完成。首先,我們必須加入新的瀏覽器設定,在網址列輸入 about:config ,並按下 Enter 。右鍵點擊列表並選擇 「新增 > 真假值(Boolean)」來建立一個新的布林值設定。將這個值設定為 browser.dom.window.dump.enabled ,並啟用它(true)。

下一步,是在 Firefox 捷徑中加入 -console 命令字串。使用這個參數,會你每次執行 Firefox 時,顯示標準輸出主控台。一但這項完成後, Firefox 也開啟了,任何藉由 dump() 函式產生的輸出,都會顯示在主控台視窗中。dump()函式就如 JavaScript 標準的 alert() 函式一樣,語法也相似。

文件物件模型檢查器 (The DOM Inspector)

對於設計工具列最大的幫助就是 DOM (Document Object Model) Inspector ,這是一個可以讓你檢查 XML 文件(包含 XUL, HTML)結構的工具。這個工具跟 Firefox 包裝在一起,但是預設是不安裝的,你必須使用進階安裝來安裝這個工具。一旦可以使用時,就會有豐富的資訊可以獲得,所以你要先學會如何使用它。

在一些網頁可以找到這樣的介紹:

這是幫助我們尋找 XUL 設計問題的最好工具,它可以讓你檢視多個 XUL 元素的語法。我強烈建議你去學習如何使用這個工具,這點相當容易,而且你會對於學會使用這項工具感到非常地高興。

個人工具