uupaa.jsの不具合報告

Twitterの@uupaaさんのメールアドレスが分からないので、
日記で不具合報告を書くことにします。
 
まぁ、ずっと更新されてない日記なので、こんな使い方でも良いでしょう。
 

<table>
<tr id='xx'><td>C</td><td>D</td></tr>
</table>

 
以上のようなHTMLがあって、スクリプト内にて、以下の様な処理を行います。
 

uu('#xx').add('<tr><td>A</td><td>B</td></tr>', 'prev');

 
すると、OperaSafari以外のブラウザ(IE, firefox, Chrome)にて、
予想と異なる結果になりました。
(各ブラウザでどうなるかは、各自やってみてください)
 
それで、少しソースコードを変えて、以下の様にしてやると、
失敗していたブラウザでも、予想と同じ結果になりました。
 

uu('#xx').add(uu.node.bulk('<tr><td>A</td><td>B</td></tr>'), 'prev');

 
で、uupaa.js初心者な私としては、
これはこういうものだと最初のうちは思ったのですが、
どうやら違うことに気が付きます。
 
これはバグだ――そんな予感がして、ちょいと調べてみました。
バグな気がしたのは、addのみの処理の場合でも、その中では
uu.node.bulkを呼び出していたからです。
 
addの第二引数は、内部関数のuunodeaddの中では
positionという変数に収納されます。
 
そして以下の様な処理で、指定した場所に追加する処理が書かれています。
 

        switch (uunodeadd.pos[position] || 8) {
        case 1: reference = context[_parentNode][_firstChild];
        case 2: reference || (reference = context);
        case 3: reference || (reference = context[_nextSibling]);
        case 4: context[_parentNode].insertBefore(node, reference); break;
        case 5: reference = context[_firstChild];
        case 8: context.insertBefore(node, reference);
        }

 

uunodeadd.pos = { first: 1, prev: 2, next: 3, last: 4, "./first": 5, "./last": 8 };

 
uu.node.bulkが内部的に呼ばれているのは、その少し前……
下記の場所のuunodebulkが、それにあたります。
 

    var nodes = !source ? [newNode()]        // [1] uu.node.add()
              : isArray(source) ? source     // [4] uu.node.add([<div>, <div>])
              : source[_nodeType] ? [source] // [3][6] uu.node.add(Node or DocumentFragment)
              : !source[_indexOf]("<") ? [uunodebulk(source, context)]
                                             // [5] uu.node.add(HTMLFragmentString)
              : [newNode(source)],           // [2] uu.node.add("p")
        reference = null, i = 0, iz = nodes.length, node, rv;

 
uu.node.bulkを予め呼び出しておいた場合と、
ほとんど同じ扱いをされていることになるのですが、
唯一違うのはcontext、こいつの中身が、結果を変えていると言えます。
 
で……contextが何者なのか、調べました。
 
uu.node.bulkを直接呼び出した場合、
contextは'div'と同じものとして動作します。
そしてadd経由で呼び出されていた時は、'TR'になっていました。
 
uunodebulk内の大まかな流れとしては、以下の様な感じです。
 
1. contextをdoc.createElementする。
2. 1で作ったノードのinnerHTMLに、作りたいhtmlテキストを流し込んでやる。
3. ノードから、子要素を全て抜き出す。
 
つまり、具体例で言うと'<tr><td>A</td><td>B</td></tr>'の親ノードとして、
contextを使ってノードを作っている、ということです。
 
divの時にうまく行って、TRのときにうまくいかないのは、
trタグの中にtrタグを生成するのがDOM的に不正だからでしょう。
 
そう、本当にあるべきcontextは、TRではなく、TABLEなわけです。
 
もうお分かりでしょうか。
positionによって処理分岐が発生するのは、要素を収納する時だけでなく、
context(文脈)を特定する時にも、positionによる分岐が必要なのです。
 
ここまで書けば、あとはこれだけ立派なjsライブラリを作った@uupaa氏が、
良い感じに修正してくれることだと思います。
 
それにしても、Javascriptド素人の私が、
ちょっと読んだだけで原因を特定できるぐらい読みやすいコードを書くuupaa氏、
流石ですね。
 
ということで、原因だけ特定しておいて、この日記を終えます。
アデュー。