script.aculo.usを読み解く

第4回 builder.js

この記事を読むのに必要な時間:およそ 6 分

0008: var Builder = {
0009:   NODEMAP: {
0010:     AREA: 'map',
0011:     CAPTION: 'table',
0012:     COL: 'table',
0013:     COLGROUP: 'table',
0014:     LEGEND: 'fieldset',
0015:     OPTGROUP: 'select',
0016:     OPTION: 'select',
0017:     PARAM: 'object',
0018:     TBODY: 'table',
0019:     TD: 'table',
0020:     TFOOT: 'table',
0021:     TH: 'table',
0022:     THEAD: 'table',
0023:     TR: 'table'
0024:   },

9~24行目のNODEMAPは,後述するように,親タグがDIVでは,innerHTML法を使うのに不都合があるタグを列挙したものです。COLタグの親タグは'table',OPTIONタグの親タグは'select'となっているのがわかります。

0025:   // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
0026:   //       due to a Firefox bug
0027:   node: function(elementName) {
0028:     elementName = elementName.toUpperCase();
0029:     
0030:     // try innerHTML approach
0031:     var parentTag = this.NODEMAP[elementName] || 'div';
0032:     var parentElement = document.createElement(parentTag);
0033:     try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
0034:       parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
0035:     } catch(e) {}
0036:     var element = parentElement.firstChild || null;
0037:       
0038:     // see if browser added wrapping tags
0039:     if(element && (element.tagName.toUpperCase() != elementName))
0040:       element = element.getElementsByTagName(elementName)[0];
0041:     
0042:     // fallback to createElement approach
0043:     if(!element) element = document.createElement(elementName);
0044:     
0045:     // abort if nothing could be created
0046:     if(!element) return;
0047:
0048:     // attributes (or text)
0049:     if(arguments[1])
0050:       if(this._isStringOrNumber(arguments[1]) ||
0051:         (arguments[1] instanceof Array) ||
0052:          arguments[1].tagName) {
0053:           this._children(element, arguments[1]);
0054:         } else {
0055:           var attrs = this._attributes(arguments[1]);
0056:           if(attrs.length) {
0057:             try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
0058:               parentElement.innerHTML = "<" +elementName + " " +
0059:                 attrs + "></" + elementName + ">";
0060:             } catch(e) {}
0061:             element = parentElement.firstChild || null;
0062:             // workaround firefox 1.0.X bug
0063:             if(!element) {
0064:               element = document.createElement(elementName);
0065:               for(attr in arguments[1]) 
0066:                 element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
0067:             }
0068:             if(element.tagName.toUpperCase() != elementName)
0069:               element = parentElement.getElementsByTagName(elementName)[0];
0070:           }
0071:         } 
0072: 
0073:     // text, or array of children
0074:     if(arguments[2])
0075:       this._children(element, arguments[2]);
0076: 
0077:      return element;
0078:   },

25~78行目のnodeは,このBuilderのなかで最も大切な部分で,DOMを構築して返す関数です。

タグを作るには,まずはinnerHTML法を試します。この方法は,作りたいタグの親タグをcreateElementで作り,親タグのinnerHTMLにタグを書き込み,親タグのfirstChildから作りたかったタグを取り出すやりかたです。

それで上手くいかなければ, createElement法を使うようになっています。この方法は,単純に作りたいタグをcreateElementで作る方法です。

初期のライブラリでは単純なcreateElement法だけが使われていましたが,IE6に対応するために,リビジョン2090でinnerHTML法がとられるようになりました。それは,createElementを使うと,ブラウザのバグや仕様にでくわすことがあるからで,特に'table'や'option'が鬼門のようです。

30行目で,innerHTML法を試します。

31行目で,親タグは,たいていは'div'でよいのですが,NODEMAPにあげられているものは別です。

32行目で,親タグをcreateElementします。

33行目で,IEの仕様を回避しつつinnerHTMLに書き込みます。ここで,try..catch文を使っているのは,リビジョン2707にあるとおり,IE6の仕様で,いくつかのHTML要素のinnerHTMLプロパティを読み取り専用としていて,書き込もうとするとエラーがでて処理が止まってしまうからです。

36行目で,親タグから所望のタグを取り出します。

39行目で,本当に所望のタグかどうか,タグ名を確かめます。ブラウザが勝手にタグを追加することがあって,単純な親タグのfirstChildによる取りだしが失敗することがあるからです。

40行目で,タグ名が違っていたら,改めて親タグにgetElementsByTagNameを使って取りだしなおします。

43行目で,ここまでの処理が失敗していたら,createElement法を試します。

46行目で,どちらの方法も失敗したら,処理を中止します。

50行目で,2番めの引数が,入れ子か属性かによって処理がかわります。

53行目で,入れ子であれば,後述する_children関数に渡して終わりです。

55~71行目で,属性であれば,これまでのタグ作りをはじめからやりなおします。これまでの処理が無駄になるのと,先ほどとそっくりなコードがまた書かれているのには,あまり感心しません。

著者プロフィール

源馬照明(げんまてるあき)

名古屋大学大学院多元数理科学研究科1年。学部生のときにSchemeの素晴らしさを知ったのをきっかけに,関数型言語の世界へ。JavaScriptに,ブラウザからすぐに試せる関数型言語としての魅力と将来性を感じている。

ブログ:Gemmaの日記