宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
いつまで続くんだこの話。やっと開発一日目最後のところだよ。コード書くのは手っ取り早いけど文章書くのってだりい。ドキュメント整備が遅れるのは世の常ですね。
ええと。とりあえず「分割」する、ブラウザをどんどん増やす方向については前回までのところでできたんで、次は追加したブラウザを削除する方向の処理の話です。
例として、「一回右に分割して、さらにその分割されたブラウザを下に分割する」という場合を想定してみよう。
これはDOM操作的には、hboxにsubbrowser-containerを入れて、さらにその中のvboxにsubbrowser-containerを入れる、という感じになる。タグで書いたらこんな感じだ。
<subbrowser-container>
<vbox>
<hbox>
<box anonid="wrapper">
<tabbrowser/> <!-- 左 -->
</box>
<splitter/>
<subbrowser-container>
<vbox>
<hbox>
<box anonid="wrapper">
<subbrowser/> <!-- 右上 -->
</box>
</hbox>
<splitter/>
<subbrowser-container>
<vbox>
<hbox>
<box anonid="wrapper">
<subbrowser/> <!-- 右下 -->
</box>
</hbox>
</vbox>
</subbrowser-container>
</vbox>
</subbrowser-container>
</hbox>
</vbox>
</subbrowser-container>
右下のブラウザを削除する場合はどうするか? これは分かりやすい。subbrowserの親の親の親の親にあたるsubbrowser-container要素を削除すればOKだ。また、左の部分、つまり最初からFirefoxにあるブラウザは削除できないので、ここも問題無いといえば問題無い。
難しいのは、右上の位置にあるブラウザ……何らかの子ブラウザを抱えている、中間の階層のブラウザを削除する場合だ。
parentNode
プロパティではアクセスできない(subbrowser-conttainerにアクセスしてしまう)。……という所でハマりにハマってしまい、2~3回構造や処理手順を変えてああでもないこうでもないとテストと試行錯誤を繰り返していた。
試してはみたものの結局うまくいかなかったやり方の一つに、DOM2 Rangeを使うやり方があった。
extractContents()
で抜き出す。extractContents()
で抜き出し、subbrowser-containerの親になっている要素の子として埋め込みなおす。右上のブラウザを削除する場合なら、
var range = document.createRange();
range.selectNodeContents(hContainer);
range.setEndBefore(wrapper);
var before = range.extractContents();
range.selectNodeContents(hContainer);
range.setStartAfter(wrapper);
var after = range.extractContents();
parentContainer.hContainer.insertBefore(before, parentContainer.wrapper);
parentContainer.hContainer.appendChild(after);
みたいな感じで。でもこれだと、前の前のエントリで書いたように、extractContents()
で内容をDOMツリーから切り離した瞬間にすでにあったbrowser要素の内容がぶっ壊されてしまって、再び埋め込んだときに空のbrowserになってしまう。それに、そもそも、こうして上の階層に内容を移動するとして、どこに入れれば一番自然かは場合によって全然異なるだろう。
この段階になって、box[anonid="wrapper"]の中にXBLのchildren要素を置いて通常の内容をそこに入れることに何も全然全くメリットが無いということにやっと気が付いた(children要素を置かずに、匿名内容だけで完結したものにする、という発想がそれまで自分に無かった)ので、構造を若干変えることにした。もうchildren要素を使った「匿名内容じゃない内容」として任意の位置にボックスを置く事は諦めて、おとなしくsubbrowserも匿名内容にしてしまっていいじゃないか、と。そうすればparentNode
プロパティで普通にhboxにアクセスできるようになるし。
そんな風にあれこれ試しているうちに、ジョージ・ジョースター1世が心の中で僕に語りかけてきた。「なにジョジョ? 無駄なボックスが残ってしまって困る(さっき挙げた4つの問題の4番目)? 逆に考えるんだ、無駄なボックスを残してもいいさと考えるんだ」と。
おお、エウレーカ!! これぞまさしく逆転の発想だ。無駄なボックスが存在していても他の処理(更なるブラウザの追加や状態の保存、削除など)に影響が出なくて、外見上も特に変化が無い(subbrowser-containerもvboxもhboxも、特に何もスタイルを設定していないし)なら、そのボックスを無理して消す必要は無いじゃないか。無駄なボックスを含んだままずっと動かし続けておいて、後になってsubbrowser要素を削除してsubbrowser-containerの内容がいよいよ空になってから、そのボックスを削除すればいいじゃないか。多少メモリの浪費にはなるけど、まあそのくらい大目に見てくださいよと。
DOMの構造で例示すると、
<subbrowser-container>
<vbox>
<hbox>
<tabbrowser/> <!-- 左 -->
<splitter/>
<subbrowser-container>
<vbox>
<hbox>
<subbrowser/> <!-- 右上 -->
</hbox>
<splitter/>
<subbrowser-container>
<vbox>
<hbox>
<subbrowser/> <!-- 右下 -->
</hbox>
</vbox>
</subbrowser-container>
</vbox>
</subbrowser-container>
</hbox>
</vbox>
</subbrowser-container>
この部分を削除した後に
<subbrowser-container>
<vbox>
<hbox>
<tabbrowser/> <!-- 左 -->
<splitter/>
<subbrowser-container>
<vbox>
<subbrowser-container>
<vbox>
<hbox>
<subbrowser/> <!-- 右 -->
</hbox>
</vbox>
</subbrowser-container>
</vbox>
</subbrowser-container>
</hbox>
</vbox>
</subbrowser-container>
こんな風に無駄なボックスが残ってしまうけれども、それは敢えて放置する。その上で、その中のsubbrowserを削除した後になって
<subbrowser-container>
<vbox>
<hbox>
<tabbrowser/>
<splitter/>
<subbrowser-container>
<vbox>
</vbox>
</subbrowser-container>
</hbox>
</vbox>
</subbrowser-container>
こんな風に本当に空っぽのボックスだけが残った時点で、その空のボックスを削除すればいいわけ。
これを実現するために、subbrowserを削除する処理と、それによってできた空のボックスを削除する処理とを、別々のメソッドに分けてみた。
2~4の「空のボックスを削除する」処理だけを再帰的に繰り返すことで、空になったボックスを漏れなく削除することができる。
やったよママン!
ところで、subbrowserを削除する処理というのはどこのオブジェクトのメソッドにするのがいいんだろう。subbrowser自身にメソッドとして持たせる? それともsubbrowser-containerのメソッド? でもどうやってそのsubbrowserに対する削除要求が発生したことを検知すればいいんだ?
やり方は色々あると思うけれども、僕は今回は、DOM2 Eventとサービスオブジェクトを使ったやり方を取ることにした。
コードで示すと、こんな感じ。まずはsubbrowserの中のクローズボックス。
<!-- subbrowser要素の匿名内容の定義 -->
<xul:toolbarbutton
class="tabs-closebutton toolbarbutton-1 chromeclass-toolbar-additional"
oncommand="
var node = this;
while (node.localName != 'subbrowser') node = node.parentNode;
node.close();
"/>
...
<!-- subbrowser要素のメソッドの定義 -->
<method name="close">
<body><![CDATA[
var event = document.createEvent('Events');
event.initEvent('SubBrowserRemoveRequest', false, true);
this.dispatchEvent(event);
]]></body>
</method>
次に、サービスオブジェクト。これは他にも色々機能持たせてるんだけど、とりあえず削除処理に関係してるとこだけ。
var SplitBrowser = {
...
handleEvent : function(aEvent)
{
switch (aEvent.type)
{
case 'load':
this.init();
break;
case 'SubBrowserRemoveRequest':
this.removeSubBrowser(aEvent.originalTarget);
break;
}
},
init : function()
{
document.documentElement.addEventListener(
'SubBrowserRemoveRequest', this, false);
window.removeEventListener('load', this, false);
}
};
window.addEventListener('load', SplitBrowser, false);
というわけで、これでやっと必要最低限の機能が出揃って、「ブラウザを無限に分割できる」拡張機能としての体裁が整ってきました、というところで開発一日目はおしまい。まぁ実際には、試行錯誤の途中で寝ちゃって、目が覚めた朝に先の決定版の処理を思いついて実装したんだけど。
実用性を高めるための機能の作り込みの話に、まだまだ続くのです。
の末尾に2020年11月30日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2007-01-20_solitbrowser-remove.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。
writeback message: Ready to post a comment.