Dethe Elza ( [email protected] )、Blast Radius シニア テクニカル アーキテクト
ドキュメント オブジェクト モデル (DOM) は、XML および HTML データを操作するために最も一般的に使用されるツールの 1 つですが、その可能性が十分に活用されていることはほとんどありません。 DOM を利用して使いやすくすることで、動的 Web アプリケーションを含む XML アプリケーション用の強力なツールが得られます。
この号には、友人で同僚のコラムニスト、デテ・エルザがゲストとして登場します。 Dethe は XML を使用した Web アプリケーションの開発に豊富な経験があり、DOM と ECMAScript を使用した XML プログラミングの紹介を手伝ってくれたことに感謝します。 Dethe のその他のコラムについては、このコラムにご期待ください。
- David Mertz
DOM は、XML および HTML を処理するための標準 API の 1 つです。多くの場合、メモリを大量に消費し、処理が遅く、冗長であると批判されます。それでも、これは多くのアプリケーションにとって最良の選択であり、XML の他の主要な API である SAX よりも確実に単純です。 DOM は、Web ブラウザ、SVG ブラウザ、OpenOffice などのツールに徐々に登場しています。
DOM が優れているのは、DOM が標準であり、広く実装され、他の標準に組み込まれているためです。標準として、そのデータの処理はプログラミング言語に依存しません (これが強みになる場合もそうでない場合もありますが、少なくともデータの処理方法に一貫性が生まれます)。 DOM は現在、Web ブラウザに組み込まれているだけでなく、多くの XML ベースの仕様の一部でもあります。今ではそれがあなたの武器の一部となり、おそらく今でも時々使用しているので、それがもたらすものを最大限に活用する時期が来たのではないでしょうか。
しばらく DOM を操作すると、繰り返し実行したくなるパターンが開発されることがわかります。ショートカットは、長い DOM を操作し、一目瞭然で洗練されたコードを作成するのに役立ちます。ここでは、私が頻繁に使用するヒントとテクニックのコレクションと、いくつかの JavaScript の例を示します。
insertAfter と prependChild の
最初のトリックは
、「トリックはない」ということです。DOM には、子ノードをコンテナ ノード (通常は要素、ドキュメント、ドキュメント フラグメント) に追加する 2 つのメソッドがあります。appendChild(node) と insertBefore(node,referenceNode) です。何かが足りないようです。参照ノードの後に子ノードを挿入または先頭に追加したい場合はどうすればよいですか (新しいノードをリストの最初にします)。長年にわたり、私の解決策は次の関数を書くことでした。
リスト 1. by による挿入と追加の間違ったメソッド
関数 insertAfter(親、ノード、参照ノード) {
if(referenceNode.nextSibling) {
parent.insertBefore(node,referenceNode.nextSibling);
} それ以外 {
親.appendChild(ノード);
}
}
関数 prependChild(親, ノード) {
if (parent.firstChild) {
parent.insertBefore(node,parent.firstChild);
} それ以外 {
親.appendChild(ノード);
}
}
実際、リスト 1 と同様に、insertBefore() 関数は、参照ノードが空の場合に appendChild() に戻るように定義されています。したがって、上記のメソッドを使用する代わりに、リスト 2 のメソッドを使用することも、それらをスキップして組み込み関数のみを使用することもできます。
リスト 2. 前から挿入および追加する正しい方法
関数 insertAfter(親、ノード、参照ノード) {
parent.insertBefore(node,referenceNode.nextSibling);
}
関数 prependChild(親, ノード) {
parent.insertBefore(node,parent.firstChild);
}
DOM プログラミングに慣れていない場合は、ノードを指す複数のポインターを持つことができますが、そのノードは DOM ツリー内の 1 つの場所にしか存在できないことに注意することが重要です。したがって、ツリーに挿入する場合は、自動的に削除されるため、最初にツリーから削除する必要はありません。このメカニズムは、ノードを新しい位置に挿入するだけでノードの順序を変更する場合に便利です。
node1
と node2 と呼ばれる) の位置を交換する場合は、次のいずれかの解決策を使用できます。
または
node1.parentNode.insertBefore(node1.nextSibling, node1);
DOM で他に何ができるでしょうか?
DOM は Web ページで広く使用されています。ブックマークレット サイト (「参考文献」を参照) にアクセスすると、ページの再配置、リンクの抽出、画像や Flash 広告の非表示などを実行できる、クリエイティブな短いスクリプトが多数見つかります。
ただし、Internet Explorer ではノード タイプの識別に使用できるノード インターフェイス定数が定義されていないため、インターフェイス定数を省略する場合は、最初に Web 用の DOM スクリプトでインターフェイス定数を定義する必要があります。
リスト 3. ノードが定義されていることを確認する
if (!window['ノード']) {
window.Node = 新しいオブジェクト();
ノード.ELEMENT_NODE = 1;
ノード.ATTRIBUTE_NODE = 2;
ノード.TEXT_NODE = 3;
ノード.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
ノード.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
ノード.COMMENT_NODE = 8;
ノード.DOCUMENT_NODE = 9;
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11;
ノード.NOTATION_NODE = 12;
}
リスト 4 は、ノードに含まれるすべてのテキスト ノードを抽出する方法を示しています。
リスト 4. 内部テキスト
関数 innerText(node) {
// これはテキスト ノードですか、それとも CDATA ノードですか?
if (node.nodeType == 3 || node.nodeType == 4) {
ノード.データを返します。
}
変数 i;
var returnValue = [];
for (i = 0; i < node.childNodes.length; i++) {
returnValue.push(innerText(node.childNodes[i]));
}
戻り値.join('');
}
ショートカット
DOM が冗長すぎる、単純な関数に多くのコードが必要になる、という不満がよくあります。たとえば、テキストを含み、ボタンのクリックに応答する <div> 要素を作成したい場合、コードは次のようになります。
リスト 5. <div> を作成するための「長い道のり」
関数ハンドルボタン() {
varparent = document.getElementById('myContainer');
var div = document.createElement('div');
div.className = 'myDivCSSClass';
div.id = 'myDivId';
div.style.position = '絶対';
div.style.left = '300px';
div.style.top = '200px';
var text = "これは、このコードの残りの部分の最初のテキストです";
var textNode = document.createTextNode(text);
div.appendChild(textNode);
親.appendChild(div);
}
この方法でノードを頻繁に作成する場合、このコードをすべて入力するとすぐに疲れてしまいます。もっと良い解決策があるはずです - そして、それはありました!これは、要素の作成、要素のプロパティとスタイルの設定、テキストの子ノードの追加に役立つユーティリティです。 name パラメータを除き、他のすべてのパラメータはオプションです。
リスト 6. 関数 elem() のショートカット
関数 elem(名前、属性、スタイル、テキスト) {
var e = document.createElement(name);
if (属性) {
for (属性のキー) {
if (キー == 'クラス') {
e.className = attrs[キー];
} else if (key == 'id') {
e.id = attrs[キー];
} それ以外 {
e.setAttribute(key, attrs[key]);
}
}
}
if (スタイル) {
for (スタイル内のキー) {
e.style[キー] = スタイル[キー];
}
}
if (テキスト) {
e.appendChild(document.createTextNode(text));
}
eを返します。
}
このショートカットを使用すると、リスト 5 の <div> 要素をより簡潔な方法で作成できます。 attrs および style パラメーターは JavaScript テキスト オブジェクトを使用して指定されることに注意してください。
リスト 7. <div> を作成する簡単な方法
関数ハンドルボタン() {
varparent = document.getElementById('myContainer');
parent.appendChild(elem('div',
{クラス: 'myDivCSSClass'、id: 'myDivId'}
{位置: '絶対'、左: '300px'、上: '200px'},
'これは、このコードの残りの部分の最初のテキストです'));
使用すると、
多数の複雑な DHTML オブジェクトを迅速に作成する場合に、時間を大幅に節約できます。ここでのパターンとは、頻繁に作成する必要がある特定の DOM 構造がある場合、ユーティリティを使用してそれらを作成することを意味します。これにより、作成するコードの量が減るだけでなく、コードのカットアンドペーストの繰り返し (エラーの原因) が減り、コードを読むときに明確に考えやすくなります。
次は何でしょうか?
DOM は、ドキュメントの順序で次のノードが何であるかを伝えるのが難しいことがよくあります。ノード間を前後に移動するのに役立つユーティリティをいくつか示します。
リスト 8. nextNode と prevNode
// ドキュメントの順序で次のノードを返します
関数 nextNode(ノード) {
if (!node) が null を返す。
if (node.firstChild){
ノード.firstChild を返します。
} それ以外 {
next Wide(ノード)を返します。
}
}
// nextNode() のヘルパー関数
関数 nextwide(node) {
if (!node) が null を返す。
if (node.nextSibling) {
ノード.nextSibling を返します。
} それ以外 {
nextwide(node.parentNode) を返します。
}
}
// 前のノードをドキュメント順に返します
関数 prevNode(ノード) {
if (!node) が null を返す。
if (node.previousSibling) {
returnPreviousDeep(node.previousSibling);
}
ノード.親ノードを返します;
}
// prevNode() のヘルパー関数
関数PreviousDeep(ノード) {
if (!node) が null を返す。
while (node.childNodes.length) {
ノード = ノード.lastChild;
}
リターンノード;
}
DOMを簡単に使用する
場合によっては、各ノードで関数を呼び出したり、各ノードから値を返したりして、DOM を反復処理する必要がある場合があります。実際、これらの考え方は非常に一般的なため、DOM レベル 2 には、DOM Traversal and Range (DOM 内のすべてのノードを反復するためのオブジェクトと API を定義する) と呼ばれる拡張機能が既に含まれており、関数を適用して DOM 内の範囲を選択するために使用されます。 。これらの関数は Internet Explorer では (少なくともまだ) 定義されていないため、nextNode() を使用して同様のことを行うことができます。
ここでのアイデアは、いくつかのシンプルで共通のツールを作成し、それらをさまざまな方法で組み立てて目的の効果を達成することです。関数型プログラミングに慣れている人であれば、これは馴染みのあるものだと思われるでしょう。 Beyond JS ライブラリ (「参考文献」を参照) は、このアイデアを前進させます。
リスト 9. 機能的な DOM ユーティリティ
// startNode から始まるすべてのノードの配列を返します。
// DOM ツリーの残りの部分を続行します
関数 listNodes(startNode) {
var list = 新しい配列();
var ノード = startNode;
while(ノード) {
list.push(ノード);
ノード = nextNode(ノード);
}
リストを返す;
}
// listNodes() と同じですが、startNode から逆方向に動作します。
// これは listNodes() および
// リストを反転します。
関数 listNodesReversed(startNode) {
var list = 新しい配列();
var ノード = startNode;
while(ノード) {
list.push(ノード);
ノード = prevNode(ノード);
}
リストを返す;
}
// nodeList 内の各ノードに func を適用し、結果の新しいリストを返します
関数マップ(リスト, 関数) {
var result_list = 新しい配列();
for (var i = 0; i < list.length; i++) {
result_list.push(func(list[i]));
}
結果リストを返す;
}
// 各ノードにテストを適用し、対象となるノードの新しいリストを返します。
// test(node) は true を返します
関数フィルター(リスト、テスト) {
var result_list = 新しい配列();
for (var i = 0; i < list.length; i++) {
if (test(list[i])) result_list.push(list[i]);
}
結果リストを返す;
}
リスト 9 には 4 つの基本ツールが含まれています。 listNodes() 関数と listNodesReversed() 関数は、Array のスライス() メソッドと同様に、オプションの長さに拡張できます。これは演習として残しておきます。もう 1 つ注意すべき点は、map() 関数と filter() 関数は完全に汎用的であり、(ノードのリストだけでなく) あらゆるリストを操作するために使用できることです。次に、それらを組み合わせる方法をいくつか紹介します。
リスト 10. 関数型ユーティリティの使用
// ドキュメント順のすべての要素名のリスト
関数 isElement(ノード) {
return node.nodeType == Node.ELEMENT_NODE;
}
関数ノード名(ノード) {
ノード.ノード名を返します。
}
var elementNames =map(filter(listNodes(document),isElement),nodeName);
// ドキュメントのすべてのテキスト (CDATA を無視します)
関数 isText(ノード) {
戻りnode.nodeType == Node.TEXT_NODE;
}
関数ノード値(ノード) {
ノード.ノード値を返します。
}
var allText = map(filter(listNodes(document), isText), nodeValue);
これらのユーティリティを使用して、ID の抽出、スタイルの変更、特定のノードの検索と削除などを行うことができます。 DOM Traversal API と Range API が広く実装されると、最初にリストを構築しなくても、それらを使用して DOM ツリーを変更できるようになります。これらは強力であるだけでなく、上で強調したものと同様の方法でも機能します。
DOM の危険ゾーン
コア DOM API では、XML データを DOM に解析したり、DOM を XML にシリアル化したりすることはできないことに注意してください。これらの関数は DOM レベル 3 拡張機能「Load and Save」で定義されていますが、まだ完全には実装されていないため、今は考えないでください。各プラットフォーム (ブラウザーまたはその他のプロフェッショナルな DOM アプリケーション) には、DOM と XML の間で変換する独自の方法がありますが、クロスプラットフォームの変換については、この記事の範囲外です。
DOM は、特に DOM API を使用して XML としてシリアル化できないツリーを作成する場合には、あまり安全なツールではありません。同じプログラム内で DOM1 非名前空間 API と DOM2 名前空間対応 API (createElement と createElementNS など) を決して混在させないでください。名前空間を使用する場合は、すべての名前空間をルート要素の位置で宣言し、名前空間の接頭辞をオーバーライドしないようにしてください。そうしないと、非常に混乱が生じます。一般的に、ルーチンに従っている限り、トラブルに巻き込まれる可能性のある重大な状況を引き起こすことはありません。
Internet Explorer の innerText および innerHTML を解析に使用している場合は、elem() 関数を使用してみることができます。同様のユーティリティを構築することで、利便性が向上し、クロスプラットフォーム コードの利点を継承できます。これら 2 つの方法を混在させると非常に良くありません。
一部の Unicode 文字は XML に含まれていません。 DOM の実装により、それらを追加できますが、その結果、それらをシリアル化できなくなります。これらの文字には、ほとんどの制御文字と Unicode サロゲート ペアの個々の文字が含まれます。これはドキュメントにバイナリ データを含めようとした場合にのみ発生しますが、これも厄介な状況です。
結論
DOM でできることの多くを説明しましたが、DOM (および JavaScript) でできることはさらにたくさんあります。これらの例を研究および検討して、クライアント スクリプト、テンプレート、または特殊な API を必要とする可能性がある問題を解決するためにそれらを使用する方法を確認してください。
DOM には制限と欠点もありますが、多くの利点もあります。Java テクノロジ、Python、JavaScript のいずれを使用しても同じように機能し、上記のテンプレートを使用できます。シンプルかつ強力な使い方ができます。 Mozilla ベースのアプリケーション、OpenOffice、Blast Radius の XMetaL など、DOM をサポートし始めているアプリケーションが増えています。 DOM を必要とし、それを拡張する仕様 (SVG など) が増えているため、DOM は常に身近にあります。この広く導入されているツールを使用するのが賢明です。