Now browsing the archives for 10月, 2007.
xul:filefield
ユーザが選択したファイルやフォルダを表示するような設定UIを作る場合には xul:filefield 要素が便利である。例えば Firefox のダウンロード先フォルダの設定UIには xul:filefield 要素が使われている。
XUL
filefield 要素を使うためには、以下の2つのスタイルシート参照する処理命令を追加する必要がある。2つめのスタイルシートは Firefox 専用である。
<?xml-stylesheet href="chrome://mozapps/content/preferences/preferences.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
多くの場合、 filefield 要素はファイルを選択するためのボタンとセットで使用される。
<hbox align="center"> <filefield id="myFileField" flex="1" /> <button label="Browse..." oncommand="selectFile();" /> </hbox>
JavaScript
「Browse…」ボタンを押したときの selectFile 関数を定義する。 nsIFilePicker を使ってファイル選択ダイアログを表示し、ユーザがファイルを選択すると、その選択したファイル (nsILocalFile 型) を filefield 要素の file プロパティへセットする。すると、ファイル名とアイコンが表示される。
Windows の場合、選択したファイルがアプリケーションであれば、ファイルのプロパティで「バージョン情報」の「説明」に設定された文字列が表示される。例えば「iexplore.exe」であれば「Internet Explorer」のようにアプリケーション名となる。
function selectFile() { var filePicker = Components.classes["@mozilla.org/filepicker;1"] .createInstance(Components.interfaces.nsIFilePicker); filePicker.init(window, "Choose a file.", filePicker.modeOpen); if (filePicker.show() == filePicker.returnOK) { var fileField = document.getElementById("myFileField"); fileField.file = filePicker.file; } }
nsIFilePicker#modeGetFolder を使ってフォルダ選択ダイアログを表示させ、ユーザにフォルダを選択させる場合、 filefield 要素の file プロパティへフォルダを表す nsILocalFile をセットしてもアイコンしか表示されない。この場合、以下のように filefield 要素の label プロパティへフォルダのパスなどを表示させるよう処理を加える。
fileField.label = filePicker.file.path;
応用例
実際のところ filefield 要素は prefwindow 要素で作成した設定画面内に配置することが多い。 preference[type=”file”] の要素と組み合わせて使用する例を下記に示す。
<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin" type="text/css"?> <?xml-stylesheet href="chrome://mozapps/content/preferences/preferences.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> <prefwindow id="testPrefWindow" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="width: 30em;"> <prefpane id="mainPane" onpaneload="updateUI();"> <preferences> <preference id="extensions.hoge.testDir" name="extensions.hoge.testDir" type="file" /> </preferences> <script type="application/x-javascript"><![CDATA[ function updateUI() { var file = document.getElementById("extensions.hoge.testDir").value; if (file) { var fileField = document.getElementById("myFileField"); fileField.file = file; fileField.label = file.path; } } function selectDir() { var filePicker = Components.classes["@mozilla.org/filepicker;1"] .createInstance(Components.interfaces.nsIFilePicker); filePicker.init(window, "Choose a folder.", filePicker.modeGetFolder); if (filePicker.show() == filePicker.returnOK) { document.getElementById("extensions.hoge.testDir").value = filePicker.file; updateUI(); } } ]]></script> <hbox align="center"> <filefield id="myFileField" flex="1" /> <button label="Browse..." oncommand="selectDir();" /> </hbox> </prefpane> </prefwindow>
参考
chrome://mozapps/content/preferences/preferences.xml#fileField
カスタムツリービューの基本的な使い方(その8~チェックボックス)
ツリーセルに文字列の代わりにチェックボックス(厳密にはチェックマーク?)を表示させることも可能である。
ベースとなるソースコードはその1~表示を参照。
fruits.xul
まず、チェックボックスの画像を表示させるためのスタイルシート fruits.css を読み込む処理命令を追加する。treecol 要素へ type=”progressmeter” 属性を付加すると、その列はプログレスバーが表示可能になる。
また、チェックボックスを直接クリックして切り替え可能にするため、 tree 要素と treecol 要素の両方へ editable=”true” 属性を追加する。
<?xml-stylesheet href="fruits.css" type="text/css"?>
<tree id="fruitsTree" flex="1" editable="true"> <treecols> <treecol label="Name" flex="1" primary="true" /> <treecol label="Juicy" type="checkbox" editable="true" /> </treecols> </tree>
fruits.css
furits.xul から読み込まれるスタイルシート fruits.css を作成する。以下のようにするとチェックマークが表示される。もちろん自前の画像を使用したり、チェックされていない状態でも画像を表示させたりすることも可能である。
treechildren::-moz-tree-checkbox(checked) { list-style-image: url(chrome://global/skin/checkbox/cbox-check.gif); }
fruits.js
配列データの内容を二次元配列化する。各アイテムの0番目の要素は「Name」列で表示させる文字列、1番目の要素は「Juicy」列のチェックボックスのオン/オフ状態である。
var gFruitsData = [ ["Grape" , true ], ["Apple" , false], ["Orange" , true ], ["Banana" , false], [null , false], // separator ["Pear" , false], ["Peach" , true ], ["Strawberry", false], ["Cherry" , false], ["Melon" , true ], [null , false], // separator ["Watermelon", true ], ["Plum" , false], ["Papaya" , false], ["Lemon" , true ], ];
これに伴い、 nsITreeView#isSeparator, getCellText を以下のように修正する。
isSeparator: function(index) {
return this._data[index][0] == null;
},
getCellText: function(row, col) {
switch (col.index) {
case 0: return this._data[row][0];
}
},
各セルのチェックボックスの現在のオン/オフ状態は nsITreeView#getCellValue で決定される。
getCellValue: function(row, col) { return this._data[row][1]; },
各セルのチェックボックスがクリックでオン/オフ切り替えが可能かどうかは、 nsITreeView#isEditable メソッドで決定される。今回の場合は、列番号が1でなおかつセパレータ行でなければ切り替え可能である。
isEditable: function(row, col) { return (col.index == 1 && !this.isSeparator(row)); },
チェックボックスをクリックしてオン/オフ切り替えしようとすると、 nsITreeView#setCellValue メソッドが呼び出される。このメソッドではクリックしたセルに対応するアイテムの値を変更し、なおかつ nsITreeBoxObject#invalidateCell でチェック状態が変化したセルだけを再描画する。
setCellValue: function(row, col, value) { this._data[row][col.index] = value; this._treeBoxObject.invalidateCell(row, col); },
関連記事
- カスタムツリービューの基本的な使い方(その1~表示)
- カスタムツリービューの基本的な使い方(その2~追加・削除)
- カスタムツリービューの基本的な使い方(その3~インライン編集)
- カスタムツリービューの基本的な使い方(その4~並び替え)
- カスタムツリービューの基本的な使い方(その5~ドラッグ&ドロップ)
- カスタムツリービューの基本的な使い方(その6~複数列ツリー)
- カスタムツリービューの基本的な使い方(その7~プログレスバー)
- カスタムツリービューの基本的な使い方(その8~チェックボックス)
- カスタムツリービューの基本的な使い方(その9~階層構造 – 表示)
- カスタムツリービューの基本的な使い方(その10~階層構造 – フォルダ開閉)
カスタムツリービューの基本的な使い方(その7~プログレスバー)
ツリーセルに文字列の代わりにプログレスバーを表示させることも可能である。 SeaMonkey のダウンロードマネージャで実際に使われている。
ベースとなるソースコードはその1~表示を参照。
fruits.xul
まず、 treecol 要素をひとつ追加する。 treecol 要素へ type=”progressmeter” 属性を付加すると、その列はプログレスバーが表示可能になる。
<treecol label="Name" flex="1" primary="true" />
<treecol label="Weight" flex="1" type="progressmeter" />
fruits.js
配列データの内容を二次元配列化する。各アイテムの0番目の要素は「Name」列で表示させる文字列、1番目の要素は「Weight」列のプログレスバーで表示させる値である。
var gFruitsData = [ ["Grape" , 30 ], ["Apple" , 50 ], ["Orange" , 40 ], ["Banana" , 20 ], [null , null], // separator ["Pear" , 30 ], ["Peach" , 40 ], ["Strawberry", 5 ], ["Cherry" , 0 ], ["Melon" , 80 ], [null , null], // separator ["Watermelon", 100 ], ["Plum" , 20 ], ["Papaya" , 70 ], ["Lemon" , 10 ], ];
これに伴い、 nsITreeView#isSeparator, getCellText を以下のように修正する。
isSeparator: function(index) {
return this._data[index][0] == null;
},
getCellText: function(row, col) {
switch (col.index) {
case 0: return this._data[row][0];
}
},
プログレスバーには値をメーターの割合として表示する通常タイプ (nsITreeView#PROGRESS_NORMAL) と、値が定まらずメーターがアニメーションするような不定タイプ (nsITreeView#PROGRESS_UNDETERMINED) の2種類がある。各セルのプログレスバーがどちらのタイプであるかは nsITreeView#getProgressMode メソッドで決定される。今回の場合、列番号が1であればすべて通常のプログレスバー、それ以外は非プログレスバーなので、以下のように実装する。ただし、実際は treecol 要素が type=”progressmeter” 属性となっている列についてのみ getProgressMode が呼び出されるため、今回の場合は col.index の値は常に1である。
getProgressMode: function(row, col) { switch (col.index) { case 1 : return Components.interfaces.nsITreeView.PROGRESS_NORMAL; default: return Components.interfaces.nsITreeView.PROGRESS_NONE; } },
通常タイプのプログレスバーの割合は nsITreeView#getCellValue で決定される。
getCellValue: function(row, col) { return this._data[row][1]; },
関連記事
- カスタムツリービューの基本的な使い方(その1~表示)
- カスタムツリービューの基本的な使い方(その2~追加・削除)
- カスタムツリービューの基本的な使い方(その3~インライン編集)
- カスタムツリービューの基本的な使い方(その4~並び替え)
- カスタムツリービューの基本的な使い方(その5~ドラッグ&ドロップ)
- カスタムツリービューの基本的な使い方(その6~複数列ツリー)
- カスタムツリービューの基本的な使い方(その7~プログレスバー)
- カスタムツリービューの基本的な使い方(その8~チェックボックス)
- カスタムツリービューの基本的な使い方(その9~階層構造 – 表示)
- カスタムツリービューの基本的な使い方(その10~階層構造 – フォルダ開閉)
カスタムツリービューの基本的な使い方(その6~複数列ツリー)
複数列ツリーを作成する。ベースとなるソースコードはその1~表示を参照。
fruits.xul
fruits.xul へ treecol 要素を2つ追加する。
<treecols>
<treecol label="Name" flex="1" primary="true" />
<treecol label="Color" flex="1" />
<treecol label="Season" flex="1" />
</treecols>
fruits.js
データ構造を一次元配列から二次元配列へと拡張する。
var gFruitsData = [ ["Grape" , "Purple", "Summer"], ["Apple" , "Red" , "Winter"], ["Orange" , "Yellow", "Always"], ["Banana" , "Yellow", "Always"], [null , null , null ], // separator ["Pear" , "Green" , "Winter"], ["Peach" , "Red" , "Summer"], ["Strawberry", "Red" , "Winter"], ["Cherry" , "Red" , "Spring"], ["Melon" , "Green" , "Spring"], [null , null , null ], // separator ["Watermelon", "Green" , "Summer"], ["Plum" , "Purple", "Summer"], ["Papaya" , "Yellow", "Always"], ["Lemon" , "Yellow", "Always"], ];
nsITreeView#isSeparator, getCellText を以下のように修正する。
isSeparator: function(index) {
return this._data[index][0] == null;
},
getCellText: function(row, col) { switch (col.index) { case 0: return this._data[row][0]; case 1: return this._data[row][1]; case 2: return this._data[row][2]; } },
応用例1~ツリーカラムを非表示にする~
複数列ツリーに限った話ではないが、ツリーカラムを非表示にするためには、以下のように tree 要素へ hidecolumnpicker 属性を追加し、各 treecol 要素の label 属性を削除した上で hideheader 属性を追加する。
<tree id="fruitsTree" flex="1" hidecolumnpicker="true"> <treecols> <treecol flex="1" hideheader="true" primary="true" /> <treecol flex="1" hideheader="true" /> <treecol flex="1" hideheader="true" /> </treecols> <treechildren flex="1" /> </tree>
応用例2~列の順番を変更できるようにする~
以下のように tree 要素へ enableColumnDrag 属性を追加すると、列をドラッグ&ドロップで順番を変えることが可能になる。
<tree id="fruitsTree" flex="1" enableColumnDrag="true">
応用例3~列の幅を調整できるようにする~
以下のように treecol 要素間に splitter 要素を挿入すると、列の幅をドラッグ&ドロップで調整することが可能になる。
<treecol label="Name" flex="1" primary="true" /> <splitter class="tree-splitter" /> <treecol label="Color" flex="1" /> <splitter class="tree-splitter" /> <treecol label="Season" flex="1" />
応用例4~列の表示状態を保存する~
以下のように各 treecol 要素へ id と persist 属性を追加すると、各列の表示/非表示(カラムヘッダ右端のピッカーから変更可能)、順序(応用例2にて変更可能)、幅(応用例3にて変更可能)といった表示状態が localstore.rdf へ保存され、次回 fruits.xul をロードしたときに自動的に復元されるようになる。
<treecol id="fruitsName" persist="hidden ordinal width" label="Name" flex="1" primary="true" /> <splitter class="tree-splitter" /> <treecol id="fruitsColor" persist="hidden ordinal width" label="Color" flex="1" /> <splitter class="tree-splitter" /> <treecol id="fruitsSeason" persist="hidden ordinal width" label="Season" flex="1" />
関連記事
- カスタムツリービューの基本的な使い方(その1~表示)
- カスタムツリービューの基本的な使い方(その2~追加・削除)
- カスタムツリービューの基本的な使い方(その3~インライン編集)
- カスタムツリービューの基本的な使い方(その4~並び替え)
- カスタムツリービューの基本的な使い方(その5~ドラッグ&ドロップ)
- カスタムツリービューの基本的な使い方(その6~複数列ツリー)
- カスタムツリービューの基本的な使い方(その7~プログレスバー)
- カスタムツリービューの基本的な使い方(その8~チェックボックス)
- カスタムツリービューの基本的な使い方(その9~階層構造 – 表示)
- カスタムツリービューの基本的な使い方(その10~階層構造 – フォルダ開閉)
カスタムツリービューの基本的な使い方(その5~ドラッグ&ドロップ)
ツリーのアイテムをドラッグ&ドロップして移動できるようにする。ただし一度にドラッグ&ドロップ可能な行数は1とする。つまり、複数選択してのドラッグ&ドロップは不許可とする。
ベースとなるソースコードはその1~表示を参照。
fruits.xul
tree か treechildren 要素に対して、ドラッグ開始時に発生する ondragstart イベントハンドラを追加する。 handleDragStart 関数については後述。
<tree id="fruitsTree" ondragstart="handleDragStart(event);" flex="1">
fruits.js
はじめに handleDragStart 関数を実装する。この関数では、まず dragstart イベント発生元が treechildren 要素であることを確認し、それ以外の場合のイベント(ツリーのスクロールバーのドラッグ)を無視する。ただし、 treechildren 要素に ondragstart イベントハンドラを付加した場合、この処理は不要となる。次に、現在選択している行数が1である場合のみ、現在の行番号(つまりドラッグ元の行番号)を取得し、その値を text/x-moz-tree-index という固有のデータ型で、ドラッグ&ドロップの転送データとしてセットする。この処理は Firefox 3.5 で導入された新しいドラッグ&ドロップAPIを使って実装されている。 Firefox 3.0 以下に対応させる場合、 nsDragAndDrop.js を使ったレガシーな実装方式が必要となる。
function handleDragStart(event) { // ignore when dragging scrollbar if (event.target.localName != "treechildren") return; // disallow dragging multiple rows if (gFruitsTreeView.selection.count != 1) return; // set current row index to transfer data var sourceIndex = gFruitsTreeView.selection.currentIndex; event.dataTransfer.setData("text/x-moz-tree-index", sourceIndex); event.dataTransfer.dropEffect = "move"; }
前述のドラッグ&ドロップAPIでは、ドラッグ開始時に発生する dragstart イベントの他に、ドラッグオーバー時に発生する dragover イベントやドロップ時に発生する drop イベントなどもある。しかし、ツリー内の行のドラッグ&ドロップを実装する場合はドラッグ時の処理だけを tree 要素側に実装し、ドラッグオーバー時とドロップ時の処理は nsITreeView 側に実装する。
ツリーへのドラッグオーバー時は、 nsITreeView#canDrop メソッドが呼び出され、このメソッドで true を返すと現在の位置に対するドロップが可能であることを示すアンダーラインが表示され、 false を返すとマウスポインタが駐車禁止の標識のようなアイコンへと変わり、ドロップが不可であることが示される。 canDrop メソッドへ渡される第1引数 targetIndex はドロップしようとしている位置の行番号、第2引数 orientation はその行の前後どちらに対してドロップしようとしているかを示す値で、 nsITreeView で定義されている3つの定数、 DROP_BEFORE (-1) 、 DROP_ON (0) 、 DROP_AFTER (1) のうちのいずれかである。ただし、 DROP_ON は階層構造があるツリーのコンテナ(いわゆるフォルダ)へのドラッグオーバー時にセットされるので、今回は考慮する必要はない。第3引数 dataTransfer は nsIDOMDataTransfer オブジェクト(dragstart イベントなど発生時の event.dataTransfer に相当)である。ただし、第3引数は Firefox 3.5 以前では渡されないので、ここからは Firefox 3.6 以上を前提として話を進める。なお、 Firefox 3.5 に対応させるためには nsIDOMDataTransfer の代わりに nsIDragSession から転送データの値を取得するやや面倒な手順が必要となる
canDrop メソッドではまず dataTransfer からドラッグされた転送データ内に text/x-moz-tree-index データ型のデータが存在するかを確認する。存在しない場合、ツリーアイテム以外の何らかのデータがドラッグオーバーされたということなので、 false を返してドロップを不許可にする。さらに、複数のツリーアイテムのドロップを不許可、現在の行と同一の行前後へのドロップは無意味なため不許可にする。
下記のコード中にデバッグ用の dump 関数を仕込んだので、コンソールをみながらドラッグして具体的な引数の値を調べるとわかりやすい。
canDrop: function(targetIndex, orientation, dataTransfer) { dump("canDrop(" + targetIndex + ", " + orientation + ") "); if (!dataTransfer.types.contains("text/x-moz-tree-index")) return false; if (this.selection.count != 1) return false; var sourceIndex = this.selection.currentIndex; if (sourceIndex == -1) return false; if (sourceIndex == targetIndex) return false; if (sourceIndex == (targetIndex + orientation)) return false; return true; },
ツリーへのドロップ時は、 nsITreeView#drop メソッドが呼び出される。引数は canDrop メソッドと同様である。下記コードでは、まず前述の canDrop メソッドを使ってドロップが可能であることをチェックする。次に、ドロップ先の行番号 targetIndex の値をドロップした後の状態でのそのアイテムの行番号へと補正している。この補正は以下のような条件判断で行われる。
(1) ドラッグ元の行よりも下にある行の前へドロップした場合、アイテム移動によって行番号が1減ることを考慮する
(2) ドラッグ元の行よりも上にある行の後へドロップした場合、その行のすぐ下の位置へ移動する
この条件判断は頭で考えるだけでは難しいので、 canDrop メソッド同様に dump 関数を使ってデバッグを行うと良い。特に階層構造を持ったツリーでは条件判断がかなり複雑になる。
ドロップ後のツリーアイテムの行番号を求めたあとは、新たに追加する自前の moveItem メソッドによって実際のデータ内のアイテム移動およびツリーの表示更新を行う。
drop: function(targetIndex, orientation, dataTransfer) { if (!this.canDrop(targetIndex, orientation, dataTransfer)) return; var sourceIndex = this.selection.currentIndex; if (sourceIndex < targetIndex) { if (orientation == Components.interfaces.nsITreeView.DROP_BEFORE) targetIndex--; } else { if (orientation == Components.interfaces.nsITreeView.DROP_AFTER) targetIndex++; } this.moveItem(sourceIndex, targetIndex); },
ドロップ時に drop メソッドから呼び出す moveItem メソッドでは、データ _data 配列の位置 aSourceIndex の要素を、位置 aTargetIndex へと移動させ、ツリーの表示を更新して移動した要素に対応する行を再度選択状態にする。
/** * @param Number aSourceIndex The array index wherefrom move. * @param Number aTargetIndex The array index whereto move. */ moveItem: function(aSourceIndex, aTargetIndex) { if (aTargetIndex < 0 || aTargetIndex > this.rowCount - 1) return; var removedItems = this._data.splice(aSourceIndex, 1); this._data.splice(aTargetIndex, 0, removedItems[0]); this._treeBoxObject.invalidate(); // select moved item again this.selection.clearSelection(); this.selection.select(aTargetIndex); this._treeBoxObject.ensureRowIsVisible(aTargetIndex); this._treeBoxObject.treeBody.parentNode.focus(); },
応用例1~ボタンによる移動~
Firefox の「検索バーの管理」のように、「上へ」「下へ」のボタンによって移動できるようにする。
まずは fruits.xul へ以下のように Up / Down ボタンを追加する。
<vbox> <button label="Up" oncommand="bumpFruit(-1);" /> <button label="Down" oncommand="bumpFruit(1);" /> </vbox>
次に、ボタンクリック時に呼び出される bumpFruit 関数を実装する。
function bumpFruit(aUpDown) { if (gFruitsTreeView.selection.count != 1) return; var sourceIndex = gFruitsTreeView.selection.currentIndex; var targetIndex = sourceIndex + aUpDown; gFruitsTreeView.moveItem(sourceIndex, targetIndex); }
応用例2~ドラッグ&ドロップの転送データを利用する~
ここまでは「ドラッグ元のツリー行=ドロップ時に選択されたツリー行」という前提で nsITreeView#canDrop および drop メソッドを実装したが、ドラッグ開始時にドラッグ元の行番号を転送データとしてセットしているので、これを利用する手もある。 nsITreeView#canDrop および nsITreeView#drop は以下のように修正される。
- var sourceIndex = this.selection.currentIndex; + var sourceIndex = parseInt(dataTransfer.getData("text/x-moz-tree-index"));
今回のような階層構造の無いツリーでは応用例2の方式で問題ないが、階層構造のあるツリーではドラッグ中にフォルダ上にマウスオーバーするとフォルダが自動的に開いてツリーの行番号に狂いが生じる場合があるため、応用例2の方式が使えない。
関連記事
- カスタムツリービューの基本的な使い方(その1~表示)
- カスタムツリービューの基本的な使い方(その2~追加・削除)
- カスタムツリービューの基本的な使い方(その3~インライン編集)
- カスタムツリービューの基本的な使い方(その4~並び替え)
- カスタムツリービューの基本的な使い方(その5~ドラッグ&ドロップ)
- カスタムツリービューの基本的な使い方(その6~複数列ツリー)
- カスタムツリービューの基本的な使い方(その7~プログレスバー)
- カスタムツリービューの基本的な使い方(その8~チェックボックス)
- カスタムツリービューの基本的な使い方(その9~階層構造 - 表示)
- カスタムツリービューの基本的な使い方(その10~階層構造 - フォルダ開閉)
カスタムツリービューの基本的な使い方(その4~並び替え)
ツリーカラム(”Name” と表示されている部分)をクリックしたときに、ツリーの表示内容を並び替えする機能を追加する。
ベースとなるソースコードはその1~表示を参照。
3つのソート状態
一般的に、ツリーカラムをクリックするたびに下表に挙げた3通りのソート状態が循環される(例えば「ブックマークの管理」)。
RDF データソースから生成するツリーの場合、treecol 要素の sort 属性へ並び替えのキーとなるプロパティの URI を記述するだけで半自動的に並び替えが可能となるが、カスタムツリービューを使用したツリーの場合、ツリーカラムがクリックされるたびに treecol 要素の sortDirection 属性を変更してツリーカラムの表示を変更し、なおかつ適切な順序で内部的に保持しているデータ自体も並び替えてツリーの表示を更新してやる必要がある。
ソート状態 | sortDirection 属性 | ツリーカラムの表示 |
---|---|---|
未ソート | natural | マークなし |
昇順 (A – Z) | ascending | ▽マーク表示 |
降順 (Z – A) | descending | △マーク表示 |
fruits.js
ツリーカラムがクリックされると nsITreeView#cycleHeader メソッドが呼び出される。
引数 col にはクリックしたツリーカラムに対応する nsITreeColumn 型オブジェクトが渡されるので、ここから treecol 要素の sortDirection 属性値を取得し、新しいソート状態へ変更する。実際の並び替え処理は、後述する自前のメソッド sortItems で行う。
cycleHeader: function(col) { // change sort direction var sortDir = col.element.getAttribute("sortDirection"); switch (sortDir) { case "ascending" : sortDir = "descending"; break; case "descending": sortDir = "natural" ; break; default : sortDir = "ascending" ; break; } col.element.setAttribute("sortDirection", sortDir); // sort data this.sortItems(sortDir); },
FruitsTreeView.prototype へ sortItems メソッドを追加する。このメソッドは、引数 aDirection で指定された順序で内部的に保持しているデータ _data を並び替える。
ここで一つ問題があり、配列のデータを昇順なり降順なりで一度並び替えを行ってしまうと、元の順序がわからなくなってしまう。そこで、やむを得ず _originalData として元の順序の配列データをコピーしてバックアップするようにする。最後に、 nsITreeBoxObject.invalidate を呼び出すことでツリーの描画を更新する。
/** * sort items in an order specified as aDirection * @param String aDirection "ascending", "descending" or "natural". */ sortItems: function(aDirection) { // clone data to backup if (!this._originalData) this._originalData = this._data.concat(); switch (aDirection) { case "ascending": this._data.sort(); break; case "descending": this._data.sort(); this._data.reverse(); break; case "natural": // restore from original data this._data = this._originalData.concat(); this._originalData = null; break; } // refresh tree this._treeBoxObject.invalidate(); },
isSorted メソッドの実装
nsITreeView インタフェースにはツリーが現在並び替えされた状態にあるかどうかを示す isSorted メソッドが定義されており、 IDL の説明によればドラッグ&ドロップ時のフィードバック(ドロップ先を示すインジケータ)関連で使用されるらしい。詳細は不明だが、とりあえずこのメソッドを実装してみる。
効率化のために FruitsTreeView へ自前のプロパティ _sorted を追加し、 cycleHeader が呼び出されたタイミングでこれの値を変更する。そうすれば isSorted メソッドは _sorted の値を返すだけでよい。
_sorted: false,
isSorted: function() { return this._sorted; },
cycleHeader: function(col) {
/* snip */
this._sorted = (sortDir != "natural");
},
isSorted メソッドの使い道として、並び替えされた状態でのドラッグ&ドロップによるアイテム移動を禁止するといった使い道が考えられる。並び替えされた状態でのドラッグ&ドロップによる移動を許可すると、ややこしいことになるため、ドラッグしようとした際に isSorted で並び替えされた状態かどうかをチェックし、そうであればドラッグを禁止する。
関連記事
- カスタムツリービューの基本的な使い方(その1~表示)
- カスタムツリービューの基本的な使い方(その2~追加・削除)
- カスタムツリービューの基本的な使い方(その3~インライン編集)
- カスタムツリービューの基本的な使い方(その4~並び替え)
- カスタムツリービューの基本的な使い方(その5~ドラッグ&ドロップ)
- カスタムツリービューの基本的な使い方(その6~複数列ツリー)
- カスタムツリービューの基本的な使い方(その7~プログレスバー)
- カスタムツリービューの基本的な使い方(その8~チェックボックス)
- カスタムツリービューの基本的な使い方(その9~階層構造 – 表示)
- カスタムツリービューの基本的な使い方(その10~階層構造 – フォルダ開閉)
setTimeout のコールバック関数内でローカル変数を使用する
var fruits = ["apple", "orange", "banana"];
という配列があるとき、
for (var i = 0; i < fruits.length; i++) { window.setTimeout(function() { alert(fruits[i]); }, i * 1000); }
こうすると1秒おきに「undefined」が3回表示されてしまう。コールバック関数が呼び出されたときにはすでにローカル変数 i は破棄されている i の値が3になっているためである。
以下のようにコールバック関数を文字列にしておけば、1秒おきに「apple」「orange」「banana」が表示される。
for (var i = 0; i < fruits.length; i++) { window.setTimeout("alert('" + fruits[i] + "');", i * 1000); }
あるいは、以下のように setTimeout の第3引数でコールバック関数へ引数を渡す方法もある。コールバック関数の内容が複雑になる場合はこの方が良い。 by Piroさん
for (var i = 0; i < fruits.length; i++) { window.setTimeout(function(aArg) { alert(aArg); }, i * 1000, fruits[i]); }
Firefox 2以降、JavaScript 1.7以降限定 by nanto_viさん
for (var i = 0; i < fruits.length; i++) { window.setTimeout(let (fruit = fruits[i]) function() { alert(fruit); }, i * 1000); }
こちらは Firefox 1.0 でもOK by nanto_viさん
for (var i = 0; i < fruits.length; i++) { with ({ fruit: fruits[i] }) { window.setTimeout(function() { alert(fruit); }, i * 1000); } }
クロージャを使って以下のように書く手もある。 by os0xさん
for (var i = 0; i < fruits.length; i++) { (function(fruit){ window.setTimeout(function() { alert(fruit); }, i * 1000); })(fruits[i]); }
prefpane には必ず id を付与する
設定値 browser.preferences.animateFadeIn
が true の場合に、 id を持たない prefpane が丸ごと表示されない。
<prefpane id="paneMain">
というように必ず id を付与しなければならない。
確かに MDC の prefpane の解説 にも id は必ず付与すべしと書いてある。
prefwindow を使用している場合はパネルの中身が見切れるなどのトラブルがつき物なので、可能な限り以下のような観点での動作確認をした方がよさそうだ。
- 各パネルの中身が正しく表示されるか?
- 最後に選択したパネルが次回正しく初期選択されるか?
browser.preferences.animateFadeIn
が true の場合の動作確認browser.preferences.instantApply
が true の場合の動作確認- 上記を {Firefox 2 | Minefield} × {Win | Mac | Linux} の6パターンで動作確認
Firefox 2 / Firefox 3 の判別方法
browser.xul にて Firefox 2 か Firefox 3 かを手っ取り早く調べるには、例えば BookmarksUtils か PlacesUtils が存在することを調べる。
if ("PlacesUtils" in window) alert("Maybe Firefox 3.");
if ("BookmarksUtils" in window) alert("Maybe Firefox 2.");
より厳密に調べるなら、 nsIXULAppInfo を使う。これなら browser.xul 以外の場所でも可能。
var appInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo); if (appInfo.version.substr(0, 1) == "3") alert("Firefox 3");
カスタムツリービューの基本的な使い方(その3~インライン編集)
Firefox 3 以降限定だが、ツリーのインライン編集が可能となる。この機能を使い、ツリーのアイテムをダブルクリックして果物の名前を変更する機能を追加する。ベースとなるソースコードはその1~表示を参照。
fruits.xul
tree 要素へ editable=”true” 属性を追加する。
<tree id="fruitsTree" editable="true" flex="1">
fruits.js
nsITreeView#isEditable メソッドを実装する。引数 row で指定された行がセパレータでなければ編集を可能にする。
isEditable: function(row, col) { return !this.isSeparator(row); },
ダブルクリックでセルを編集して Enter キー押下で確定させると、 nsITreeView#setCellText メソッドが呼び出される。
setCellText メソッドでは、引数 row で指定されたアイテムのデータそのものを変更してから nsITreeBoxObject#invalidate でツリーを再描画する。
setCellText: function(row, col, value) { this._data[row] = value; this._treeBoxObject.invalidate(); },
関連記事
- カスタムツリービューの基本的な使い方(その1~表示)
- カスタムツリービューの基本的な使い方(その2~追加・削除)
- カスタムツリービューの基本的な使い方(その3~インライン編集)
- カスタムツリービューの基本的な使い方(その4~並び替え)
- カスタムツリービューの基本的な使い方(その5~ドラッグ&ドロップ)
- カスタムツリービューの基本的な使い方(その6~複数列ツリー)
- カスタムツリービューの基本的な使い方(その7~プログレスバー)
- カスタムツリービューの基本的な使い方(その8~チェックボックス)
- カスタムツリービューの基本的な使い方(その9~階層構造 – 表示)
- カスタムツリービューの基本的な使い方(その10~階層構造 – フォルダ開閉)