Custom XUL Elements with XBL

XML Binding Language (XBL や拡張可能なバインディング言語 (Extensible Bindings Language) と呼ばれることもあります) は、他のドキュメント内の要素に取り付けられるバインディングを記述する言語です。バインディングを取り付ける要素は、「XBL に結び付けられた要素」と呼ばれ、バインディングで定義された新しい振る舞いを獲得します。

これは、XBL のページからの引用です。

この暗号のような説明は、XBL で独自のカスタム要素を作成できるという、とても簡単なコンセプトについて述べています。XBL は XUL 内で多用されていますが、理論的にはどの XML 言語にも適用できます。XBL は標準化のために W3C へ提案されましたが、現在は専ら XUL でしか使用されていません。

XBL で新しい要素を作成し、それらのプロパティや属性、メソッド、イベントハンドラを定義できます。タブやボタン、入力コントロールなどの多くの複雑な要素が XBL と簡単な XUL 要素で実装されています。前にも説明したように、XUL は、ただのボックスやテキスト、画像です。

ここでは、Hello World 拡張の変更したバージョンを使用して XBL について見ていきます。Hello World XBL プロジェクトをダウンロードしてビルドし、しばらくの間テストしてください。Hello World メニューに新しい項目があるのが分かるでしょう。これは、リストに "Persons" を追加できる Binding Test ウィンドウを開きます。

XBL の基本

XBL バインディングを作成するには、XBL ファイルと、要素名をあなたの XBL 宣言に結び付ける CSS ファイルの 2 個のファイルが必要です。これらのファイルは両方とも、プロジェクトの content ディレクトリ内にあります:

  • person.xml - これは、メインのバインディングファイルです。作成した新しい要素をコントロールするために必要なすべてのコードを保持しています。残りのセクションを通してこのコードを見ていきます。今は、binding 要素のはじめの部分 (<binding id="person">) に注目してください。
  • bindings.css - これは、要素名を XBL ファイルに関連付けるファイルです。xshelloperson 要素名を XBL ファイルで定義された binding に関連付けます。1 個のファイルで複数のバインディングができます。"#person" の部分は、私たちが求めるものの ID を示します。この CSS ファイルは、通常の skin によって置き換えるものではなく、style を定義するものでもありません。これは、content の振る舞いを定義するものなので、content ディレクトリ内に置きます。
ツールバー要素に binding を用いる場合は、chrome.manifest ファイル内で style 指示子を使用してツールバーのカスタマイズダイアログに CSS ファイルを含めることを忘れないでください。

これら 2 個のファイルで正しく定義すれば、新しい要素が使えるようになります。bindingDialog.xul ファイルを見ると、この CSS ファイルが含まれていることが分かるでしょう。これは、xshelloperson タグが他の XUL タグと同じように使用できるようになったことを意味します。ここでは "Persons" 要素を動的に追加しているため、xshelloperson 要素がどのように他の要素と同じように作成され DOM に追加されているかを知るには、JavaScript ファイルを見なければなりません。

addPerson : function(aEvent) {
  // ...
  let person = document.createElement("xshelloperson");
  // ...
  person.setAttribute("name", name);
  person.setAttribute("greeting", greeting);
  // ...
  personList.appendChild(person);
  // ...
},

これは、XBL の利点が明らかになる箇所です。1 個のノードを作成し、いくつかの属性を設定するだけでよいのです。"Person" が作成されるたびに毎回、およそ 7 個のノードで構成された XUL 構造全体を作成する必要はありませんでした。XBL は、これらのノードを一体にして管理する必要があったものをカプセル化します。

ボーナスとして、すべてのシステムでネイティブに見せる "ファイルを開く" ダイアログを開く nsIFilePickerの使い方を調べてください。

それでは、person.xml XBL ファイルの中身を見ていきましょう。

<bindings xmlns="http://www.mozilla.org/xbl"
  xmlns:xbl="http://www.mozilla.org/xbl"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

bindings 要素は、binding 要素のコンテナであり、このドキュメントのルートです。このドキュメントのデフォルトの名前空間が XBL であり、XUL 名前空間が "xul" として定義されている部分に注目してください。binding のコンテンツを定義する時は、このことを心に留めておく必要があります。"xul:" を追加しておかないと、あなたのコンテンツのノードにおかしなことが起こってしまいます。

<binding id="person">

一般的には、binding の定義はファイルごとに 1 個にしてください。バインディングは多くのコードを必要としがちなため、複数定義するとファイルが肥大化し、コードが読みづらくなります。一方で、バインディングが小さく他のバインディングと強い関連性があるなら、それらを 1 個のファイルで定義すると分かりやすくなります。CSS ファイルについては、あなたの拡張機能のすべてのバインディングを 1 個のファイルで宣言するとよいでしょう。

コンテンツ

content タグ以下には、この要素で表示される XUL コンテンツを定義します。

<content>
  <xul:hbox>
    <xul:image class="xulshoolhello-person-image" xbl:inherits="src=image" />
    <xul:vbox flex="1">
      <xul:label xbl:inherits="value=name" />
      <xul:description xbl:inherits="value=greeting" />
    </xul:vbox>
    <xul:vbox>
      <xul:button label="&xulshoolhello.remove.label;"
        accesskey="&xulshoolhello.remove.accesskey;"
        oncommand="document.getBindingParent(this).remove(event);" />
    </xul:vbox>
  </xul:hbox>
</content>

私たちの要素は、画像と 2 行のテキスト、ボタンを表示するとても簡単な要素です。ここでは、次の 3 つの重要なことに注意してください:

  • "xul:" 名前空間は、content 内のすべての XUL タグに対して使用されなければなりません。
  • xbl:inherits 属性は、XUL 内部でそのノード上に設定された属性を使用させます。XUL ファイルで <xshelloperson name="Pete" greeting="Good morning" image="" /> のように記述すると、これらの属性が、この特別な属性を持つ content ノードに "継承" されます。xul:label 要素の value 属性は "Pete" になります。
  • button タグの oncommand 属性には、document.getBindingParent(this) という今までに見たことのないコードがあります。このコードは、xshelloperson タグに相当する DOM オブジェクトを取得し、そのメソッドやプロパティへアクセスできるようにします。この場合は、後で取り上げる remove メソッドを呼び出しています。

コンテナ要素や他の子ノードを持つ要素を作成する必要がある場合は、content 内で子ノードが挿入される場所を示すための XBL children タグを使用してください。includes 属性は、子ノードに少しの柔軟性を与えますが、あまり必要な場面はありません。

考慮すべき重要なことが一つあります。どの content ノードでも id 属性を使用すべきではありません。これらのノードは他と同じ XUL DOM の一部であるため、id 属性を持たせると問題を起こす原因となります。同じドキュメント内にあなたの要素が複数与えられると、それらが内部で同じ id を持つことになってしまいます。この問題を回避するため、代わりに anonid 属性が使用できます:

<xul:label anonid="xulshoolhello-name-label" xbl:inherits="value=name" />

そして、バインディング内で JavaScript コードからノードへの参照を得るために、getAnonymousElementByAttribute DOM メソッドを使用します:

let nameLabel = document.getAnonymousElementByAttribute(
    this, "anonid", "xulshoolhello-name-label"
);

実装

implementation セクションでは、要素のほとんどのスクリプトを定義します。ここでは、メソッドとプロパティ、要素の function Object() { [native code] }destructor も定義できます。JavaScript コードは、JavaScript と XML 構文の衝突を避けるため、CDATA セクション内にカプセル化されます。

プロパティとフィールド

field タグと property タグは、要素の変数を扱い、要素の外側からこれらの変数へアクセスできるようにします。

field は変更できる値を保持します。ただし、readonly 属性が設定されている場合は変更できません。これは、JavaScript オブジェクトの変数ととても似ています。通常は、 field を要素内部のプライベート変数のために使用します。

<field name="fieldName">defaultValue</field>

あなたのバインディングメソッドの内側から、次のコードで field へアクセスできます:

this.fieldName

また、要素への参照がある場合は、要素の外側からもアクセスできます:

elementRef.fieldName
JavaScript オブジェクトと同じように、すべての field はパブリックにアクセス可能です。私たちは、field が "private" 変数であることをしめすために、変数名に "_" を使用します。

property は、もう少し堅固です。これは、getter メソッドと setter メソッドで定義され、読み込み専用や書き込み専用のプロパティにすることができ、さらに複雑な振る舞いにもできます。私たちのバインディングでは 2 個のプロパティが定義されています。これは、要素内の 2 個の text 属性へ簡単にアクセスするためのものです。私たちが使用する次のコードは、property タグの短い書き方です:

<property
  name="name"
  onget="return this.getAttribute('name');"
  onset="this.setAttribute('name', val);" />

getter や setter のコードが複数行にわたる場合は、次のさらにコンパクトな property タグを使用してください:

<property name="name">
  <getter><![CDATA[
    return this.getAttribute('name');
  ]]></getter>
  <setter><![CDATA[
    this.setAttribute('name', val);
  ]]></setter>
</property>

property は、field と同じようにアクセスできます。私たちは、これらをパブリックメンバに使用しています。

XBL バインディングを使用して XUL ドキュメントにノードを追加する時は、通常のすべての DOM 操作が行えます。ノードを移動したり削除したり、複製したりできます。ただし、ノードの移動時や複製時には、ノード内のすべての内部状態が失わることを知っておく必要があります。これは、すべての property と field がリセットされることを意味します。これらの DOM 操作を行った後も値を保持しておきたいときは、内部の値ではなく、属性値として設定しなければなりません。

メソッド

私たちの "Person" バインディングには、リストからアイテムを削除する 1 つのメソッドがあります:

<method name="remove">
  <parameter name="aEvent" />
  <body><![CDATA[
    this.parentNode.removeChild(this);
  ]]></body>
</method>

見ての通り、method と受け取る parameter の定義はとても簡単です。通常の JavaScript コードと同じように、return キーワードを使用して戻り値を指定することもできます。この method は、親ノードから Person ノードを削除します。とても簡単でしょう。

XBL のコードからは、XPCOM コンポーネントや JavaScript コードモジュール、利用可能な chrome スクリプトを使用するなど、どんなことでも行えます。主な欠点は、バインディング内で script タグを定義できないことです。スクリプトは、binding を使用する XUL ファイルに含まれた script に依存します。スクリプトと異なり、スタイルシートを stylesheet XBL 要素を使用して含めることができます。ローカライズのための DTD ファイルと properties ファイルは通常の XUL ファイルと同じように扱うことができます。

常に心に留めておくべき 2 つの競合するパターンがあります。表現コードと論理コードのカプセル化と分離です。カプセル化を行う場合は、あなたの XBL を外部への依存から解放しなければなりません。つまり、スクリプト X がどこか外部に含まれていることを仮定するべきではありません。含まれていなければ、バインディングが失敗する原因になります。これは、すべてをバインディング内に収めなければならないことを示唆しています。一方で、バインディングは本当にただの表現モジュールでしかありません。ほとんどの XUL 要素は、基本的な表現方式を持っており、表現以外の機能はどこか他の場所で処理されます。加えて、XBL ファイルは、通常の JavaScript ファイルよりも管理しにくいです。簡単な側面での誤りのほうが解決しやすいため、私たちは XBL をできるだけ簡単しておくことを好みます。XBL の外部へ依存しなければならない場合は、そのようにしてください。それでも、外部とコミュニケートするカスタムイベントを使用することによって、分離と使途の広さを保つことができます。この方法は、特定のスクリプトへの依存を減らし、あなたのタグの表現部分が大きな振る舞いになります。

field や propertiy と同じように、method は、ノードに相当するオブジェクトへの参照があればとても簡単に呼び出せます。

XBL の要素が作成されドキュメントに挿入された直後、メソッドを呼び出し、プロパティを XBL 上のノードに設定する時に問題が起こることが分かっています。これはおそらく、ノードの挿入に関連する何らかの非同期の操作が原因です。ノードの挿入直後にそれを操作する必要がある場合は、タイムアウトを利用して操作を遅延させることをお勧めします (タイムアウトを 0 に設定するとうまく動作します)。

ハンドラ

handlershandler XBL 要素は、要素のイベントハンドラを定義するために使用されます。私たちは、"Person" 要素がクリックされた時に挨拶を表示するため、"click" ハンドラを使用しています:

<handler phase="bubbling" event="click"><![CDATA[
  window.alert(this.greeting);
]]></handler>

ハンドラは、多くの場合コード全体ではなく、バインディングの一部にだけ適用するイベントが必要な場合に使用されるため、あまり必要になることはありません。使用する場合は、通常の event 属性を content タグ内のノードに追加してください。

Note that with handlers a convenience arises in the fact that "this" refers to the XBL element, and so internal components can be accessed using "this.".

継承

継承は、XBL の中でも最も強力な機能です。これは、既存のバインディングを拡張するバインディングを作成して多くのコードを再利用し、細かな振る舞いを変更できるようにします。必要なことは、binding 要素の extends 属性を使用することだけです:

<binding id="manager"
  extends="chrome://xulschoolhello/content/person.xml#person">

このコードで "Person" バインディングと全く同じコピーが得られます。好きなように上書きしてください。

例えば、全く異なる XUL コンテンツの content タグを追加し、implementation をそのままにしておくことができます。ただし、すべての anonid 属性に矛盾がないように十分注意してください。さらに、何らかのコードがノードを横断する場合は DOM 構造にも注意してください。残念なことに、content の一部分だけを上書きすることはできません。上書きしたいときは、content 全体を上書きしなければなりません。

継承は一般的に、メソッドやプロパティの振る舞いを変更したい場合に行います。content タグを含めないことによって元の content をそのまま残しておき、上書きしたいメソッドやプロパティを追加するだけです。必要なことは、変更したものを継承元の名前と一致させることだけです。上書きされないすべてのメソッドやプロパティは、元の振る舞いを維持します。

richlistbox 要素を継承してわずかなコードを追加するだけで、高機能なアイテムツリーに変更したり、クリックするごとに状態を変化させるスイッチを作成したりできます。カスタム要素を作成する場合は、多くの時間を節約できることを心に留めてください。

既存の XUL 要素の置き換え

このセクションのはじめで見たとおり、実際のバインディング処理は、binding のタグ名を関連付ける簡単な CSS 規則によって決定されます。これは、CSS 規則を追加するだけで、binding を Firefox のどんな要素にも変更できることを意味します! どの XUL ウィンドウのどのインタフェースでも変更できるため、とても強力です。これも、継承と共に簡単に行えます。要素を拡張し、置き換えることによって、Firefox ウィンドウの UI を強化できます。Console² 拡張機能が、エラーコンソールウィンドウを改善するために、その XUL 要素の置き換えています。

要素の置き換えは常に最終手段であるべきです。メインのブラウザウィンドウ上で行う場合は特にそうです。間違った方法で置き換えると、とても簡単にアプリケーションや他の拡張機能の UI を壊してしまうため、十分に注意してください。要素の特定の部分だけを変更する必要がある場合は、それに限定した CSS 規則を使用してください。

-moz-binding プロパティは、どの CSS セレクタにも使用できます。

This tutorial was kindly donated to Mozilla by Appcoast.

添付ファイル

ファイル サイズ 日時 添付者:
html5 logo
html5 logo
9210 バイト 2012-11-13 08:33:42 ednapiranha

Document Tags and Contributors

タグ:
Contributors to this page: ethertank, Marsf
最終更新者: ethertank,