Mozilla Mozilla の憂鬱 Mozilla


◆ドキュメントの出力

HTML のロード中にスクリプトで HTML を出力する場合は従来どおり document.write を使用することができますが、 onLoad 後に HTML の一部を変更するような場合には IE のように insertAdjacentHTML や NN4 のようなレイアに document.write を使用する方法は使えません。

ここでは onLoad 後に HTML の一部を変更する方法について 研究してみましょう


W3C DOM Level 1 な方法

「ECMAScript Binding」の「オブジェクトの生成」でも言及したのですが、 W3C DOM でオブジェクトを生成するには createElementcreateTextNode などの関数を駆使する必要があります。

これは従来の NN や IE での方法とは異なり、直感的に HTML を作成し これをそのまま出力することが難しいことを意味します。

つまり、これを実現するクロスなドキュメント出力関数は、そのインターフェイスとして 出力するドキュメントの構造を指定しなければならなくなるわけです。

例えば、私が DHTML で多用しているレイア内を書き換える汎用のクロスな関数 writeDivHTML は
writeDivHTML(div,op,cl,html1,html2,...);
ではなく
writeDivHTML(div,op,cl,obj1,obj2,...);
と言うように HTML ではなく生成済みのオブジェクトを引数としなければならなくなります。

また、オブジェクトの生成は NN4 や IE4,5 では従来どおり HTML で良いのですが、 Mozilla の場合は createElement や createTextNode を 使用するので、これを隠蔽する必要があります。

つまり、writeDivHTML を呼び出す側では レイア "div" に
"<p>Hello! Mozilla</p>"
と出力するには
writeDivHTML(div,true,true,
    pElement(null,null,textElement('Hello! Mozilla'))
    );
という具合に HTML ではなく概念的なオブジェクトを指定するわけです。

そして、pElement や textElement は以下のようにします。
_dom=(document.all?3:
     (document.getElementById?1:(document.layers?2:0)));
function textElement(text){
  if(_dom==1)
      return document.createTextNode(text); // Mozilla
  return text;                              // NN4,IE4,5
}
function pElement(nm,cls){
  if(_dom==1){                 // Mozilla
    var p=document.createElement('P');
    if(nm)     p.setAttribute('id', nm);
    if(cls)    p.setAttribute('class',cls);
    for(var i=2; i<arguments.length; i++) p.appendChild(arguments[i]);
    return p;
  }
  else if(_dom==2 || _dom==3){ // NN4,IE4,5
    var chlds='';
    for(var i=2; i<arguments.length; i++) chlds+=arguments[i];
    var attrName  =nm?(' name="'+nm+'"'):'';
    var attrClass =cls?(' class="'+cls+'"'):'';
    return '<p'+attrName+attrClass+'>'+chlds+'</p>';
  }
  return '';
}
因みに、writeDivHTML 関数の内容は以下のようになるわけです。
function writeDivHTML(div,op,cl){
  if(_dom==1){ // Mozilla
    if(op){
      for(var i=div.childNodes.length; i>0; i--)
          div.removeChild(div.childNodes.item(i-1));
    }
    for(var i=3; i<arguments.length; i++)
          div.appendChild(arguments[i]);
  }
  if(_dom==2){ // NN4
    var s='';
    if(op) div.document.open('text/html','replace');
    for(var i=3; i<arguments.length; i++) s+=arguments[i];
    div.document.write(s);
    if(cl) div.document.close();
  }
  if(_dom==3){ // IE4,5
    var s='';
    for(var i=3; i<arguments.length; i++) s+=arguments[i];
    if(op) div.innerHTML='';
    div.insertAdjacentHTML('BeforeEnd',s);
  }
}


ん〜。...これじゃあ、今まで作成したスクリプトは大改造を余儀なくされますよね。

#> それに、HTML を指定するのも面倒だし... f(^^;;

Mozilla の開発者もこの点を問題視していたらしく( 類推 )、 W3C DOM Level2 にもない関数をいくつか追加した結果、 HTML からオブジェクトを直接生成する手段が提供されているようです。


Mozilla な方法 1

HTML から直接オブジェクトを生成する手法は HTML Model ではなく、 Range Model にあります。

Range Model は W3C DOM Level 2 で規定されている、ツリー状に構築された DOM に おけるセレクションのためのモデルで、ある特定の2点間の HTML の断片 ( Document Fragment )を示すためのオブジェクトです。

Mozilla では NSRange というクラスに createContextualFragment 及び isValidFrgment というメソッドを追加しているため、HTML の断片から DocumentFragment( Node のサブクラス ) を生成することができます。

これを利用すれば writeDivHTML 関数は以下のように 従来のインターフェイスのままレイアに HTML を出力することができます。

function writeDivHTML(div,op,cl){
  var s='';
  for(var i=3; i<arguments.length; i++) s+=arguments[i];
  if(_dom==1){ // Mozilla
    if(op){
      // レイアの下位にある Node を全て削除
      while(div.hasChildNodes())
          div.removeChild(div.lastChild);
    }
    // DocumentFragment を生成する
    var range=document.createRange();
    range.selectNodeContents(div);
    range.collapse(true);
    var cf = range.createContextualFragment(s);
    // レイアの管理下におく
    div.appendChild(cf);
  }
  if(_dom==2){ // NN4
    if(op) div.document.open('text/html','replace');
    div.document.write(s);
    if(cl) div.document.close();
  }
  if(_dom==3){ // IE4,5
    if(op) div.innerHTML='';
    div.insertAdjacentHTML('BeforeEnd',s);
  }
}

Mozilla な方法 2

もう1つの Mozilla な方法は M16 から追加された innerHTML プロパティを使用する方法です。

このプロパティは IE との互換動作のために M16 で( 試験的に? )追加されたプロパティで 良くも悪くもほぼ IE の innerHTML と同様の動作をするようです。
#> このプロパティを使用する際の注意事項は 「DHTML 〜 怪談! Cross Browser」の「innerHTML と insertAdjacentHTML」を 参照してください。

例によって、 writeDivHTML 関数としての実装をしてみると

function writeDivHTML(div,op,cl){
  var s='';
  for(var i=3; i<arguments.length; i++) s+=arguments[i];
  if(_dom==1){ // Mozilla
    if(op) div.innerHTML='';
    div.innerHTML+=s;
  }
  if(_dom==2){ // NN4
    if(op) div.document.open('text/html','replace');
    div.document.write(s);
    if(cl) div.document.close();
  }
  if(_dom==3){ // IE4,5
    if(op) div.innerHTML='';
    div.insertAdjacentHTML('BeforeEnd',s);
  }
}

ってところでしょうか。


まとめ

...ってことで、 W3C DOM な方法と Mozilla な方法の3種類を紹介したわけですが、 考慮すべき点がないわけではありません。

つまり、

createContextualFragment は Mozilla 特有の実装である。
将来的には W3C DOM にも採用されることを狙っての実装だとは思いますが、 Level 2 には規定されていないので、 IE が DOM に対応したといっても createContextualFragment が実装されるかどうかは不明で、実装されない場合 旧バージョンとの互換を考慮しないとしても、 やはりブラウザ依存のコードが残ることになります。
Mozilla な方法はパフォーマンスが良くない。
当然 HTML を解析して Node を生成するのですから W3C DOM な方法より 実行スピードは落ちます。
innerHTML は Mozilla M16 の実験的な実装である。
innerHTML は IE との互換の要望により試験的に M16 で実装されたプロパティで、 W3C DOM に採用されるかどうか、更には M17 以降でも実装されるかどうか、 は不明です。
#> と言っていたら、NN6 正版になっちゃった(笑)。正式に採用ってことでしょうか... f(^^;
innerHTML は使い方に注意が必要である
innerHTML は基本的に該当のタグ内の記述を再定義するためにだけ使用できます。 やはり、一般的に使用するなら insertAdjacentHTML 相当の機能が実装されて はじめて可能になると思った方が良いでしょう。

結論を言えば「Mozilla な方法 2」はまだ様子見ですので 最初の2つの方法だけに限定すれば、どーせ IE は IE5 を見てもわかるように、 W3C DOM の実装は自分の都合で行うでしょうから、最初の項目はあまり気にせず、 高頻度で大量の出力を行うかどうかで、どちらの方法を採用するか決定しても よさそうな気がします。 f(^^;;


戻る Copyright(c) 2000 ShinSoft. All rights reserved.