ダイアログウィンドウ左のツリーは preftree.xul に既にある項目を参考に overlay で組み込みます。パネル本体は<page>
をルート要素にしたXULのファイルを使用します。パネルの中身については XPCOM の prefs.js 読み書き用の機能を使って自力で作ってもよいのですが、後述するような他のパネルで使われている機能を流用すれば、色々と手間を省けます。
まず、 XUL でパネルの UI を作ります。Mozilla 1.0 では個々のパネルのルート要素は page にするようになっていますが、Netscape 6 でも使えるようにするなら、 window 要素にしても構いません。設定項目に使う checkbox や textbox 、 menulist などには以下の属性を指定してください。
属性名 | 値 | 説明 | 備考 |
---|---|---|---|
pref | true | その要素が設定項目であることを示す。 | Netscape 7以降では省略可。 |
preftype | bool | string | localizedstring | int | 設定のタイプ。真偽値、文字列、整数値のどれかを選びます。 | Netscape 7以降では省略可。ただし、整数値専用のtextboxなどの場合には必要。 |
prefstring | 任意 | 保存する設定の名前。 pref.js にあるような形式で書きます。 | |
prefattribute | value | label | checked | ... | 設定として保存する、その項目の属性。 checkbox なら checked 、 textbox なら value になります。 | Netscape 7以降では省略可。 |
新規ウィンドウを開かなくする隠し設定「 browser.target_new_blocked
」を例に取ってみると、 <checkbox pref="true" preftype="bool" prefattribute="checked" prefstring="browser.target_new_blocked" id="browser.target_new_blocked" label="新規ウィンドウを開かない"/>
という具合です。ちなみに id と prefstring を同じにしておくと管理が楽になると思います。
その XUL ドキュメント内に出てくる設定項目の要素の id 全てを _elementIDs
_elementIDs = ['id1', 'id2', ...]
と自分で書いても、 onload 時に document.getAttribute('pref', 'true')
などの方法で要素を集めて自動処理しても、どちらでも構いません(収集漏れを防ぐなら自動処理する方がいいでしょう)。ここで ID を登録しなかった要素については設定が保存されません。
また、 panel
var panel = location.href;
とでも書いておけば OK 。
仕上げに、ルート要素の onload 属性に parent.initPanel(location.href);
と書きます(前処理・後処理が必要なら、この前後に書く)。これで、設定の読み込み・保存・キャンセル時の処理など後のことは全て Mozilla がやってくれます。非常に楽チンですね。
パネルのタイトルは、NS6 では <box orient="horizontal" class="box-smallheader" title="タイトル"/>
という風に記述していましたが、 Mozilla 1.0 ではルート要素( window あるいは page )に <page headertitle="タイトル">
と書いておくとそれが自動で表示されるようになっています。 NS6 と最新の Mozilla の両方で問題なく使えるようにしたい場合、 window.parent.getElementById('header')
があるかないかを調べて自前で処理する必要があります。
パネルのヘッダでは、以下のスタイルシートを読み込むように指定します。対象の Mozilla のバージョンによってファイルが違いますので、注意してください。
xul-stylesheet や xul-overlay などの XML 処理命令で存在しないファイルを指定していると、そのパネルを開いたあと別のパネルを開けなくなるという問題が起こるようです。
僕が引っかかったのは dialogOverlay.css です。このファイルはパネルの見出しなどのスタイル指定なのですが、 Mozilla 0.9.8 以降では仕様の変更でファイルがなくなったため、 NS6 用に作ったパネルを Moz で開こうとすると必ずこの問題にぶち当たります。というわけで、面倒ですが、 NS6 用と Mozilla 0.9.8 以降用とでは嫌でもパッケージを分けなければなりません。
これが面倒な場合、多少イリーガルですが、ラクな回避法法もあります。以下のようなスタイルシートを <?xml-stylesheet href="forNS6.css" type="text/css"?>
のような形で指定して下さい。
/* forNS6.css */
@import url("chrome://communicator/skin/dialogOverlay.css");
@import で指定したファイルについては、前述のような問題は起こらないようです。
設定項目によっては、ある別の設定が有効になっているときにだけ変更可能にする、あるいはその逆に、ある設定が無効になっているときにだけ変更可能にする、という処理をした方がよい場合があります。
チェックボックス同士を連携させるには、以下のような関数を用意しておくと便利です。
function controlLinkedItems(aElem, aShouldEnable, aAttr)
{
// 連携する設定項目のIDを得る
var targets = aElem.getAttribute(aAttr || 'linked').split(/ +/);
var item;
// 連携する設定項目を変更可能にするかどうかの判別
// 特別な指定がなければ、「チェックボックスがチェックされている」場合と
// 「テキストボックスに文字列が入力されている」場合に、連携先を変更可能
// にする
var disabled = (aShouldEnable !== void(0)) ? !aShouldEnable :
(aElem.localName == 'textbox' ? (!aElem.value || !Number(aElem.value)) : !aElem.checked );
for (var i in targets)
{
item = document.getElementById(targets[i]);
item.disabled = disabled;
}
}
この関数は、以下のようにして使います。
<checkbox id="checkbox-A"
label="設定項目A"
prefstring="original.setting_A"
oncommand="controlLinkedItems(this);"
linked="checkbox-B checkbox-C"/>
<checkbox id="checkbox-B"
label="設定項目B"
prefstring="original.setting_B"/>
<checkbox id="checkbox-C"
label="設定項目C"
prefstring="original.setting_C"/>
何らかの属性(ここでは linked
)に連携対象の項目のIDをスペース区切りで列挙しておき、oncommand
イベントハンドラで、チェック状態が変わったときに連携対象の項目を今の状態に合わせて変更します。
ただし、これだけだと、パネルが読み込まれた段階でおかしな事になる場合があります(チェックボックスのチェックが外れているのに連携対象が変更可能のままになっている、など)。このような場合でも正しく設定項目同士を連携させたい場合、初期化用の関数の中でwindow.parent.initPanel()
の後に自動で処理を行うようにしておくと良いでしょう。
function init()
{
// 前処理:Mac用とそれ以外の環境とでラベルを変える、など
// initMacLabel();
// パネル内の設定項目の状態を初期化
parent.initPanel(location.href);
// 後処理:初期化された設定項目の状態に合わせて連携対象を制御
controlLinkedItems(document.getElementById('checkbox-A'));
}
この場合、パネルのonload
にはparent.initPanel(location.href);
ではなくinit();
と書くことになります。
一つのパネルの中での連携は、前述したとおり、比較的簡単に実装できます。では、別々の設定パネルの内容同士を連携させるにはどうすればよいでしょうか。
一番簡単なのは、親のフレーム(parent
)の変数にそれぞれのパネルの設定の変更状態を記憶しておいて、連携対象のパネルでそれを読み込む方法です。以下の二つの関数は、ある設定が変更された場合にその情報を記憶しておき、別のパネルから「設定が変更されているかどうか」を調べられるようにするものです。
function isModified(aKey, aPageURI, aArray)
{
if (!aArray) aArray = 'modifiedData';
var pageData = parent.hPrefWindow.wsm.dataManager.pageData;
if (aPageURI in pageData) {
var data = pageData[aPageURI];
return (data && aArray in data && aKey in data[aArray] && data[aArray][aKey].value);
}
return false;
}
function onModified(aKey, aArray)
{
if (!aArray) aArray = 'modifiedData';
var pageData = parent.hPrefWindow.wsm.dataManager.pageData;
if (!(location.href in pageData))
pageData[location.href] = [];
var data = pageData[location.href];
if (!(aArray in data))
data[aArray] = [];
if (aKey in data[aArray])
data[aArray][aKey].value = !data[aArray][aKey].value;
else
data[aArray][aKey] = { value : true };
}
以下に、chrome://original/content/panel_A.xulとchrome://original/content/panel_B.xulの二つのパネルについて、panel_A.xul内のチェックボックス「checkbox-A」のチェック状態に応じてpanel_B.xulの内容を制御する場合の例を示します。
<!-- panel_A.xul -->
<checkbox id="checkbox-A"
label="設定項目A"
prefstring="original.setting_A"
oncommand="onModified(this.getAttribute('prefstring'));"/>
// panel_B.xul
init()
{
parent.initPanel(location.href);
var pref = Components.classes['@mozilla.org/preferences;1']
.getService(Components.interfaces.nsIPrefBranch);
var panelA = 'chrome://original/content/panel_A.xul';
// checkbox-Aがチェックされている場合にcheckbox-Bとcheckbox-Cを変更可能にする
// 真偽値の設定「original.setting_A」がfalseで且つcheckbox-Aが変更されていない、
// あるいは「original.setting_A」がtrueで且つcheckbox-Aが変更されているとき、
// この式はtrueになる
if (
isModified('original.setting_A', panelA) ?
!pref.getBoolPref('original.setting_A') :
pref.getBoolPref('original.setting_A')
) {
document.getElementById('checkbox-B').setAttribute('disabled', 'true');
document.getElementById('checkbox-C').setAttribute('disabled', 'true');
}
}