script.aculo.usを読み解く

第4回 builder.js

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

先ほどと違うのは,innerHTML法のとき次のようにすることと,

0057:             try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
0058:               parentElement.innerHTML = "<" +elementName + " " +
0059:                 attrs + "></" + elementName + ">";

createElement法ならば次のようにすることです。

0064:               element = document.createElement(elementName);
0065:               for(attr in arguments[1]) 
0066:                 element[attr == 'class' ? 'className' : attr] = arguments[1][attr];

66行目で,class属性の指定はclassName属性にいれかえます。

75行目で,3番めの引数は入れ子として扱うので,後述の_children関数に渡します。

77行目で,結果のelementを返します。

0079:   _text: function(text) {
0080:      return document.createTextNode(text);
0081:   },

79~81行目の_textは,引数の値の文字列ノードを作って返す関数です。

0083:   ATTR_MAP: {
0084:     'className': 'class',
0085:     'htmlFor': 'for'
0086:   },
0087:

83~87行目のATTR_MAP は,後述するとおり,'className','htmlFor'と属性名を指定されたときに,それぞれHTMLで対応する属性名の'class','for'に指定をおきかえるのに使う対応表です。このように間接的に指定するのは,classとforがJavaScriptの予約語だからです。

0088:   _attributes: function(attributes) {
0089:     var attrs = [];
0090:     for(attribute in attributes)
0091:       attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
0092:           '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
0093:     return attrs.join(" ");
0094:   },

88~94行目の_attributesは,属性を表現したオブジェクトを受けとって,HTMLの表現に整形した文字列を返す関数です。

91行目で,属性名を,ATTR_MAPを使って,'className','htmlFor'の指定をHTMLの表現の'class','for'におきかえています。

92行目で,属性の値の内部にあるダブルクォートを,HTMLの記法の&quot;に,正規表現で置き換えます。これで次のような指定も正しく扱うことができます。

{onclick:'alert("' + 'hello' + '")'}

93行目で,全ての結果を結合して,"属性名1=値1 属性名2=値2 ..."という形の文字列を返します

0095:   _children: function(element, children) {
0096:     if(children.tagName) {
0097:       element.appendChild(children);
0098:       return;
0099:     }
0100:     if(typeof children=='object') { // array can hold nodes and text
0101:       children.flatten().each( function(e) {
0102:         if(typeof e=='object')
0103:           element.appendChild(e)
0104:         else
0105:           if(Builder._isStringOrNumber(e))
0106:             element.appendChild(Builder._text(e));
0107:       });
0108:     } else
0109:       if(Builder._isStringOrNumber(children))
0110:         element.appendChild(Builder._text(children));
0111:   },

95~111行目の_children は,入れ子を処理するための関数です。1番めの引数に入れ子の親のDOM要素を,2番めの引数に子をとります。

96行目で,子がtagName属性を持ったDOM要素ならば,単に親にappendChildします。

100行目で,子がtypeof演算子に'object'を返すならば,配列であるとみなします。

101行目で,子の配列をflattenメソッドでつぶしてから,eachメソッドで,配列の各要素について,次のような処理をします。

102行目で,各要素のうち,typeof演算子に'object'と返すものは,DOM要素とみなして,親にappendChildします。

105行目で,各要素のうち,typeof演算子に'string'か'number'を返すものは,上述の_text関数を使って適当なDOM要素にしてから,親にappendChildします。

109行目で,子がtypeof演算子に'object'ではなく,'string'か'number'を返すならば,上述の_text関数を使って適当なDOM要素にしてから,親にappendChildします。

0112:   _isStringOrNumber: function(param) {
0113:     return(typeof param=='string' || typeof param=='number');
0114:   },

112~114行目の_isStringOrNumberは,引数の型が文字列か数字であるかを返す関数です。typeof演算子を使って判断します。

0115:   build: function(html) {
0116:     var element = this.node('div');
0117:     $(element).update(html.strip());
0118:     return element.down();
0119:   },

115~119行目のbuildは,引数にHTMLが書かれた文字列をとって,そのDOMを返す関数です。DIV要素を作ってHTMLを書き込み,中身をElement.downメソッドで取り出して返します。

0120:   dump: function(scope) { 
0121:     if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
0122:   
0123:     var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
0124:       "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
0125:       "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
0126:       "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
0127:       "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
0128:       "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
0129:   
0130:     tags.each( function(tag){ 
0131:       scope[tag] = function() { 
0132:         return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
0133:       } 
0134:     });
0135:   }
0136: }

120~136行目のdumpは,DIV(P('hello world'))などと書けるように,DIV関数やP関数などをまとめて定義する関数です。デフォルトでは,これらはwindow以下に定義されてグローバル関数になりますが,引数にオブジェクト(いわゆるインスタンス)か関数(いわゆるクラス定義)を与えて,これらのスコープを制限することもできます。

132行目からわかるように,DIV(...)の実体はBuilder.node ('DIV',...)です。JavaScriptのapplyの定義はこちらを参照してください。

著者プロフィール

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

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

ブログ:Gemmaの日記