2007/09/24

DOM検索効率化のメソッド群を作ってみた。

というわけで、もういっちょ。考えていたDOM検索効率を考えたメソッド群を作ってみた。

ええと、prototype.jsのElementオブジェクトにaddMethodsしていることからもわかるようにprototype.js必須っす。ええと、私の使っているprototype.jsは、1.5.1ね。script.aculo.us(v1.7.1_beta3)と一緒に使っているので。

  1. document.getElementsByClassNameAndTagName = function(className, parentElement, tagName) {
  2.   if (Prototype.BrowserFeatures.XPath) {
  3.      return $(parentElement).getElementsByClassName(className);
  4.   } else {
  5.     var children = $(parentElement).getElementsByTagName(tagName || '*');
  6.     var elements = [], child;
  7.     for (var i = 0, length = children.length; i <length; i++) {
  8.       child = children[i];
  9.       if (Element.hasClassName(child, className))
  10.         elements.push(Element.extend(child));
  11.     }
  12.     return elements;
  13.   }
  14. }
  15.  
  16. Element.addMethods({
  17.   getElementsByClassNameAndTagName: function(element, className, tagName) {
  18.     return document.getElementsByClassNameAndTagName(className, element, tagName);
  19.   },
  20.  
  21.   nextElement: function(element) {
  22.     do {
  23.       element = element.nextSibling;
  24.     } while (element && element.nodeType != 1);
  25.     return $(element);
  26.   },
  27.  
  28.   previousElement: function(element) {
  29.     do {
  30.       element = element.previousSibling;
  31.     } while (element && element.nodeType != 1);
  32.     return $(element);
  33.   },
  34.  
  35.   firstChildElement: function(element) {
  36.     var child = element.firstChild;
  37.     while (child && child.nodeType != 1) {
  38.       child = child.nextSibling;
  39.     }
  40.     return $(child);
  41.   },
  42.  
  43.   lastChildElement: function(element) {
  44.     var child = element.lastChild;
  45.     while (child && child.nodeType != 1) {
  46.       child = child.previousSibling;
  47.     }
  48.     return $(child);
  49.   },
  50.  
  51.   childElements: function(element) {
  52.     var children = [];
  53.     var child = element.firstChild;
  54.     while (child) {
  55.       if (child.nodeType == 1) {
  56.         children.push($(child));
  57.       }
  58.       child = child.nextSibling;
  59.     }
  60.     return children;
  61.   },
  62.  
  63.   childElement: function(element, index) {
  64.     var nodeIndex = 0;
  65.     var child = element.firstChild;
  66.     while (child) {
  67.       if (child.nodeType == 1 && index == nodeIndex++) {
  68.           return $(child);
  69.       }
  70.       child = child.nextSibling;
  71.     }
  72.     return null;
  73.   },
  74.  
  75.   cleanWhitespaceRecursive: function(element) {
  76.     var f = function(element) {
  77.       var child = $(element).cleanWhitespace().firstChild;
  78.       while (child) {
  79.         if (child.nodeType == 1) {
  80.           f(child);
  81.         }
  82.         child = child.nextSibling;
  83.       }
  84.     };
  85.     f(element);
  86.     return element;
  87.   }
  88. });

nextElement, previousElementはそのまんま。textNodeはすっ飛ばして、elementNodeだけを見ている。firstChildElementとlastChildElementはfirstElementとlastElementって命名しようかと思ったけど、childだということを意識したかったのでちょいと冗長だけど、これで堪忍してや。で、意味もそのまんま。textNodeをすっ飛ばして最初や最後のelementNodeを返す。

childElementsは、前回のポストの結果を考慮してfirstChildとnextSiblingを採用。elementNodeの配列を返す。childElementは、index指定でelementNodeを返す。本当は、こんな感じでchildElementsメソッドの返す配列のindexを返す方がすっきりしていていいんだけどなぁ。

  1. childElement: function(element, index) {
  2.      return $(element).childElements()[index];
  3.   },

しかし、それだとnextSiblingを全て見てまわってしまうので、遅くなりそうなので、不採用。

cleanWhitespaceRecursiveはついでの産物。今回作成したものとは関係がないのだけども、一応。HTMLコーダにじかにHTMLを書かれるとwhitespaceが入ってしまい、困るので再帰的に消してみることにした。JavaScriptで再帰をするときってやっぱり、ローカル変数に関数をぶちこんで、それを何度も呼び出す方がいいのかな、と思ったので、自分自身を何度も呼び出すような方は不採用。
自分自身を呼び出すのは、こんな感じか。

  1. cleanWhitespaceRecursive: function(element) {
  2.     element = $(element);
  3.     var child = element.cleanWhitespace().fistChild;
  4.     while (child) {
  5.       if (child.nodeType == 1) {
  6.         child.cleanWhitespaceRecursive();
  7.       }
  8.       child = child.nextSibling;
  9.     }
  10.     return element;
  11.   }

どっちがいいんだろうなぁ。。。

で、本当は、オプションで、$指定で返すか、そのままのelementを返すかを指定できるようにするかで迷ったのだけど、prototype.jsと一緒に使うことを前提としているので、$で返すようにした。これもパフォーマンスに関係してくるんだけど、まぁ、その辺は考慮中。

つーか、また後で追記したり、ソース修正するかもしれん。名前はなんでもよかったのだけど、domx.jsとしよう。

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  2. <html lang="ja">
  3.     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  4.     <meta http-equiv="Content-Style-Type" content="text/css" />
  5.     <meta http-equiv="Content-Script-Type" content="text/javascript" />
  6.     <script language="JavaScript" type="text/javascript" src="js/prototype.js"></script>
  7.     <script language="JavaScript" type="text/javascript" src="js/scriptaculous.js"></script>
  8.     <script language="JavaScript" type="text/javascript" src="domx.js"></script>
  9.     <script language="JavaScript" type="text/javascript">
  10.     Event.observe(window, 'load', function() {
  11.       var mainContainer = $('main');
  12.       var fc = mainContainer.firstChildElement();
  13.       fc.style.backgroundColor = '#eee';
  14.  
  15.       var lc = mainContainer.lastChildElement();
  16.       lc.style.backgroundColor = '#aaa';
  17.  
  18.       var ce = mainContainer.childElement(1);
  19.       ce.style.backgroundColor = '#ddd';
  20.  
  21.       var ne = ce.nextElement();
  22.       ne.style.backgroundColor = '#ccc';
  23.  
  24.       var pe = lc.previousElement();
  25.       pe.style.backgroundColor = '#bbb';
  26.  
  27.       $A(mainContainer.childElements()).each(function(e) {
  28.         e.style.margin = '0.5em';
  29.       });
  30.  
  31.       var sushinoneta = mainContainer.getElementsByClassNameAndTagName('sushinoneta', 'input');
  32.       $A(sushinoneta).each(function(neta) {
  33.           neta.style.marginLeft = '20px';
  34.           switch (neta.name) {
  35.           case 'tako':
  36.             neta.value = '380';
  37.             break;
  38.           case 'ikura':
  39.             neta.value = '420';
  40.             break;
  41.           case 'uni':
  42.             neta.value = '600';
  43.             break;
  44.           default:
  45.             neta.value = '360';
  46.             break;
  47.           }
  48.       });
  49.       var before = $('before');
  50.       before.appendChild(Builder.node('h1', 'Before cleanWhitespaceRecursive'));
  51.       before.appendChild(Builder.node('textarea', {cols: '100', rows: '5'}, mainContainer.innerHTML));
  52.       mainContainer.cleanWhitespaceRecursive();
  53.       var after = $('after');
  54.       after.appendChild(Builder.node('h1', 'After cleanWhitespaceRecursive'));
  55.       after.appendChild(Builder.node('textarea', {cols: '100', rows: '5'}, mainContainer.innerHTML));
  56.     });
  57.     </script>
  58.     <title>DOMX Demo</title>
  59.   </head>
  60.     <div id="main">
  61.       <div>
  62.         <label>
  63.           <span>tako:</span>
  64.           <input type="text" name="tako" class="sushinoneta" />
  65.         </label>
  66.       </div>
  67.       <div>
  68.         <label>
  69.           <span>ika:</span>
  70.  
  71.           <input type="text" name="ika" class="sushinoneta" />
  72.         </label>
  73.       </div>
  74.       <div>
  75.         <label>
  76.           <span>uni:</span><input type="text" name="uni" class="sushinoneta" />
  77.         </label>
  78.       </div>
  79.  
  80.       <div>
  81.         <label>
  82.           <span>ikura:</span><input type="text" name="ikura" class="sushinoneta" />
  83.         </label>
  84.       </div>
  85.       <div>
  86.         <label>
  87.           <span>maguro:</span>
  88.  
  89.           <input type="text" name="maguro" class="sushinoneta" />
  90.         </label>
  91.       </div>
  92.     </div>
  93.     <div id="before"></div>
  94.     <div id="after"></div>
  95.   </body>
  96. </html>

ソースはここ。

domx.js
ライセンスは適当っす。自己責任で使ってちょ。

で、上のソースを動かす形にしたデモもここに置いておく。domx demo

うーん。そろそろjQuery使ってみようかなぁ。。。onReadyとか便利そうだし。。。インデントがタブなのが非常に嫌なので、敬遠しているのだけど。。。

全然関係ないが、ここ二日ほど愛知県図書館で開発をしている。なかなか良いね。ホットスポットもあるみたいだけど、契約をしていないのでインターネットにつなげない。でも、そのおかげで効率がいいよん。インターネットがあるとダラダラしちゃって、ダミだ。

Leave a comment

Bloglines feedburner