コマンドは、操作に応じて呼び出され、必要な処理を行います。
command 要素
要素は、操作に対する処理を実行していくために使用可能な、コマンドの作成に利用されます。
操作に関する処理を行うためには、単にスクリプトを呼び出すだけでもよいので、コマンドは「利用しなければならない」ものではありません。
しかしながら、コマンドには、必要に応じて自動的に無効にできたり、実装の詳細について知らなくても外部から呼び出せるといった利点があります。
また、コマンドは、操作をコードから切り離して抽象化するのに適しており、アプリケーションが大きくなるほど有用になります。
command
例えば、クリップボード系のメニューコマンドである、「切り取り」や、「コピー」、「貼り付け」を実装するために、コマンドを利用することができます。 これらを、コマンドを利用せずに実装しようとした場合、どのフィールドがフォーカスを得ているかを調べ、その要素に対して適切な操作であるかを確認する必要があります。 加えて、切り取りやコピー操作では、フォーカスを得ている要素が、選択状態のテキストを含んでいるかどうかに応じて、メニューコマンドの有効化や無効化を行ったり、 貼り付け操作では、クリップボードの内容が、貼り付けるのに適しているかを調べる必要があります。 こういった具合に、これらを独自に実装する場合は複雑な記述が必要になりますが、 コマンドを利用すれば、上記の処理のほとんどをコマンドが実施してくれます。
コマンドは、任意の操作に対して利用できます。 Mozilla では、ほぼすべてのメニューコマンドで利用されています。 加えて、テキストフィールドなどのウィジェットには、最初からサポートされているコマンドが多数あり、アプリケーションからも呼び出すことが可能になっています。 どの要素がフォーカスを得ているかに依存するような操作については、これらのコマンドを利用すべきでしょう。
コマンドは、その id 属性によって識別されます。 Mozilla では、コマンドの id は、「<tt>cmd_</tt>」で始めるのが慣例になっています。 もし、既に使用されているコマンドで利用できそうなものがある場合、おそらくは、そのコマンドを利用するために、その id を指定したいと考えるはずですが、 独自のコマンドを作成する場合は、id は好きなように決められます。 このときは、id の衝突を避けるために、id にアプリケーション名を含めておくのも良いでしょう。
単純な command の例
以下に、単純なコマンドの利用方法を示します。
var el = env.locale; 例 1 : ソース 表示
<command id="cmd_openhelp" oncommand="alert('Help!');"/> <button label="Help" command="cmd_openhelp"/>
この例では、
属性を、oncommand
要素ではなく、button
要素に設定しています。
また、この 2 つの要素は、コマンドの command
を値とする id
属性によって結びつけられています。
この結果、ボタンが押されたときに、「<tt>cmd_openhelp</tt>」コマンドが呼び出されるようになります。
command
この方法には、2 つの利点があります。
- まず、全ての操作をコマンドにすることで、 XUL ファイルの 1 カ所にまとめることができます。コードをまとめることで、ハンドラが UI のコード中に散在することを防げます。
- もう 1 つの利点は、複数のボタンなどの UI 要素を、同じコマンドに結びつけられるようになることです。例えば、同じ操作を行う、メニュー項目、ツールバーボタン、キーボードショートカットのそれぞれに対して、同じコマンドを設定できます。つまり、同じコードを 3 回繰り返して記述するのではなく、3 つの全てに対して同じコマンドを割り当てるだけで済みます。通常、コマンドは、コマンドイベントを送出するものに対してのみ割り当てます。
さらに、以下のような利点もあります。
- コマンドに対して
属性を設定することで、コマンドは無効化され、起動されなくなます。disabled
- コマンドが割り当てられたボタンやメニュー項目も、自動的に無効化されます。
- コマンドを再び有効にすると、ボタンも有効になります。
コマンド無効化の例
var el = env.locale; 例 2 : ソース 表示
<command id="cmd_openhelp" oncommand="alert('Help');"/> <button label="Help" command="cmd_openhelp"/> <button label="More Help" command="cmd_openhelp"/> <button label="Disable" oncommand="document.getElementById('cmd_openhelp').setAttribute('disabled','true');"/> <button label="Enable" oncommand="document.getElementById('cmd_openhelp').removeAttribute('disabled');"/>
この例では、2 つのボタンに同じコマンドが割り当てられています。
Disable ボタンが押されると、コマンドの
属性が設定され、ボタンは、両方とも無効化されます。
disabled
通常、コマンドは XUL ファイルの先頭の方に
要素を使って、以下のようにまとめて置きます。
commandset
<commandset> <command id="cmd_open" oncommand="alert('Open!');"/> <command id="cmd_help" oncommand="alert('Help!');"/> </commandset>
コマンドは、コマンドが割り当てられている、ボタンなどの要素が活性化されると起動されますが、
要素や、ボタンなどのコマンドが割り当てられている要素に対して、command
メソッドを呼び出すことによっても起動することが可能です。
doCommand
コマンドのディスパッチ
コマンドを、
要素を使わずに作成したり、あるいは、少なくともコマンドに command
属性を設定せずに利用することが可能です。
この場合、コマンドがスクリプトを直接起動することはありませんが、代わりにコマンドを処理するための要素や関数が検索されることになります。
これらの関数の多くは、XUL そのものとは分けられており、一部は、ウィジェットで内部的に処理されます。
コマンドの処理を振り分けるために、XUL ではコマンドディスパッチャと呼ばれるオブジェクトを利用します。
このオブジェクトは、コントローラと呼ばれる、コマンドを処理するためのハンドラを特定します。
つまり、コマンドが起動されると、コマンドディスパッチャはそのコマンドを処理可能なコントローラを特定することになります。
また、oncommand
要素は、コマンドのためのコントローラの一種だと考えることができます。
command
コマンドディスパッチャは、現在フォーカスを得ている要素に対し、そのコマンドを処理可能なコントローラを持っているかどうかをチェックします。
このために、XUL の要素は、
プロパティを持っています。
この、controllers
プロパティを使用して、独自のコマンドを追加することも可能です。
例えば、これによって、リストボックスに「切り取り」、「コピー」、「貼り付け」処理の追加を行うことができます。
(実際の例は、後で示します)。
デフォルトでは、テキスト入力欄のみがコントローラを持っており、
「クリップボード操作」、「選択」、「元に戻す」、「やり直し」などの編集操作を行います。
要素は、複数のコントローラを持っているかもしれず、その場合、全てがチェック対象であることに注意して下さい。
controllers
現在フォーカスを得ている要素が、適合するコントローラを持っていない場合、次にウインドウがチェックされます。
ウインドウにも、
プロパティがあり、必要ならば変更することも可能です。
フレーム利用時に、フォーカスをフレームの内側の要素が得ていた場合、トップレベルウインドウに達するまで、入れ子になっている各フレームについて、同様のチェックがされていきます。
これによって、フォーカスがフレームの内側にある場合であっても、コマンドは問題なく機能します。
また、Mozilla ブラウザのメインメニューからの編集系コマンドの呼び出しが、コンテンツ領域に対して有効であることから、
コマンドは、ブラウザ要素に対しても、問題なく機能することがいえます。
HTML でも、コマンドとコントローラのシステムが利用できることを補足しておきます。
といっても、特権の無いウェブページからは利用できませんが、例えばブラウザの拡張機能などから利用することは可能です。
最終的に、ウインドウにもコマンドを処理可能なコントローラが存在しない場合は、何も起きません。
controllers
コマンドディスパッチャは、文書オブジェクトの commandDispatcher
プロパティ、あるいは、要素かウインドウにあるコントローラのリストから取得することが可能です。
コマンドディスパッチャは、コマンドのためのコントローラを取得したり、フォーカスを得ている要素の取得や変更を行うメソッドを持っています。
コントローラを追加する
コマンドに反応するために、独自のコントローラを実装することも可能です。 コントローラは、慎重に配置すれば、デフォルトのコマンド処理を置き換えることさえ可能になります。 コントローラには、以下の 4 つのメソッドを実装することが求められています。
- supportsCommand (command)
- このメソッドは、コントローラがコマンドをサポートする場合に true を返す必要があります。 false が返された場合、コマンドはハンドルされず、コマンドディスパッチャは他のコントローラの検索を続けます。 1 つのコントローラが、複数のコマンドをサポートすることも可能です。
- isCommandEnabled (command)
- このメソッドは、コマンドが有効な場合に true を返し、無効な場合に false を返す必要があります。対応するボタンは自動的に有効化、または無効化されます。
- doCommand (command)
- コマンドを実行します。ここにコマンドを処理するコードを記述して下さい。
- onEvent (event)
- このメソッドはイベントを処理します。
コントローラの実装例
それでは、削除コマンドを処理可能なリストボックスの実装を行ってみましょう。
これは、利用者がメニューから削除を選択したときに、リストボックスの選択されている行を削除します。
このためには、コントローラを作成して doCommand
メソッドの動作としてこの動作を行うように実装し、単純にこのリストボックスに結びつけるようにします。
ブラウザウインドウで以下の例 (ソース 表示) を開いて、リストの項目を選択してみてください。 ブラウザの編集メニューの削除コマンドが有効になっていて、それを選択すると選択行が削除されることが確認できるはずです。【訳注: この例は リモートではなく、chrome URL からアクセスして特権を与えないと (少なくとも訳者の Firefox 1.5 では) うまく動作しないようです】 以下の例は、完璧に仕上げられたものではありません。 実際には、削除後に選択域とフォーカスを正しく調整する必要があります。
<window id="controller-example" title="Controller Example" onload="init();" xmlns="https://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <script> function init() { var list = document.getElementById("theList"); var listController = { supportsCommand : function(cmd){ return (cmd == "cmd_delete"); }, isCommandEnabled : function(cmd){ if (cmd == "cmd_delete") return (list.selectedItem != null); return false; }, doCommand : function(cmd){ list.removeItemAt(list.selectedIndex); }, onEvent : function(evt){ } }; list.controllers.appendController(listController); } </script> <listbox id="theList"> <listitem label="Ocean"/> <listitem label="Desert"/> <listitem label="Jungle"/> <listitem label="Swamp"/> </listbox> </window>
このコントローラ (listController
) は、上記の 4 つのメソッドを実装しています。
まず、supportsCommand
メソッドは、「<tt>cmd_delete</tt>」コマンドの場合に true を返します。
「<tt>cmd_delete</tt>」は、削除メニュー項目が起動されたときに使用されるコマンド名になります。
また、このコントローラは、それ以外のコマンドには対応していないため、その場合は false を返さなければなりません。
対応するコマンドを追加したい場合は、このチェックが、対応する全てのコマンド対して true を返すように変更する必要があります。
1 つのコントローラを、関連する複数のコマンド処理に使用するような場面は、しばしばあると思います。
次に、isCommandEnabled
メソッドは、コマンドが有効になっているべきである場合に true を返します。
この例では、リストボックス内に選択された項目があるかどうかを調べて、あれば true 、無ければ false を返しています。
このため、すべての行が削除されると、削除コマンドは無効化されますが、
この例は単純なため、反映させるにはリストボックスを明示的にクリックしてメニューを更新する必要があります。
3 つめの、doCommand
メソッドは、メニュー項目の削除が選択されたときに呼び出され、その結果、リストボックス内で選択されている行は削除されます。
最後の、onEvent
メソッドは、何もする必要がないため、何のコードも付与されていません。
デフォルトのコントローラを置き換える
前の例では、コントローラをリストボックスに付加するために、リストボックスの
プロパティの controllers
appendController
メソッドを呼び出しています。
この controller オブジェクトには、コントローラのリストを操作するメソッドがいくつかあります。
例えば、insertControllerAt
メソッドは、その要素の他のコントローラの前にコントローラを挿入します。
これによって、既存のコマンドを置き換えることが可能です。
例えば、以下の例で示す方法で、テキスト入力欄への貼り付けを無効化することができます。
var tboxController = { supportsCommand : function(cmd){ return (cmd == "cmd_paste"); }, isCommandEnabled : function(cmd){ return false; }, doCommand : function(cmd){ }, onEvent : function(evt){ } }; document.getElementById("tbox").controllers.insertControllerAt(0,tboxController);
この例では、新しいコントローラは、「<tt>cmd_paste</tt>」コマンドをサポートしますが、常に無効であると応答します。 このコントローラをインデックス 0 の位置、すなわち他の全てのコントローラの前に挿入します。 これによって、コマンドディスパッチャは、「<tt>cmd_paste</tt>」コマンドを処理するためのコントローラを検索するとき、新しいコントローラの方を先に見つけることになり、 デフォルトのテキスト入力欄のコントローラは、一切呼び出されなくなります。
次のセクションでは、コマンドの状態を自動更新する方法を見ていきます。