builder.jsは、DOMを簡単に構築できるライブラリです。
prototype.jsにも、バージョン1.6でDOM構築を簡単にするためのElementコンストラクタが導入されましたが、script.aculo.usのBuilderのほうが開発の歴史が古いので、いくつかの点で一日の長があります。
機能の紹介
DOMを構築するときは、次のような一連の操作が頻出します。
builder.jsを使うと、次のように簡単に書くことができます。
さらに便利なのは、このBuilder.nodeはこのように入れ子にできることです。これはprototype.jsのElementコンストラクタにはない機能です。
入れ子にした結果はこのようになります。
便利な機能 Builder.dump()
私がBuilder.jsで愛してやまないのは、次のようにも書けることです。
このショートカットモードともいうべき機能は、Builder.dump()を呼ぶと、そこでDIV関数やA関数が定義されて、使えるようになります。DIV関数やA関数の実体はBuilder.node関数です。まるで閉じタグのないHTMLのように見えるのがお気に入りです。
Builder.node関数の使いかた
Builder.node関数は、引数に何が与えられるかで動作が変わります。引数の与えかたは以下のようになっています。
- Builder.node( 要素名 )
- Builder.node( 要素名, 属性 )
- Builder.node( 要素名, 入れ子 )
- Builder.node( 要素名, 属性, 入れ子 )
要素名は、文字列で、要素のタグ名を与えます。
属性は、オブジェクトを与えます。プロパティ名に例えばid、className、style、onclickなどを使います。
入れ子は、要素に追加される、単一の文字列か数字かノード、あるいはそれらの配列を与えます。
この入れ子の中の配列は次のように扱われます。
このライブラリは、Array.mapといった配列操作と組み合わせて使うことで、大きな威力を発揮します。例えばこれは、配列からボタンをまとめて作る例です。
Builder
それでは、実際のコードを見ていきましょう。
1~7行目は著作権表示です。
9~24行目のNODEMAPは、後述するように、親タグがDIVでは、innerHTML法を使うのに不都合があるタグを列挙したものです。COLタグの親タグは'table'、OPTIONタグの親タグは'select'となっているのがわかります。
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行目で、属性であれば、これまでのタグ作りをはじめからやりなおします。これまでの処理が無駄になるのと、先ほどとそっくりなコードがまた書かれているのには、あまり感心しません。
先ほどと違うのは、innerHTML法のとき次のようにすることと、
createElement法ならば次のようにすることです。
66行目で、class属性の指定はclassName属性にいれかえます。
75行目で、3番めの引数は入れ子として扱うので、後述の_children関数に渡します。
77行目で、結果のelementを返します。
79~81行目の_textは、引数の値の文字列ノードを作って返す関数です。
83~87行目のATTR_MAP は、後述するとおり、'className'、'htmlFor'と属性名を指定されたときに、それぞれHTMLで対応する属性名の'class'、'for'に指定をおきかえるのに使う対応表です。このように間接的に指定するのは、classとforがJavaScriptの予約語だからです。
88~94行目の_attributesは、属性を表現したオブジェクトを受けとって、HTMLの表現に整形した文字列を返す関数です。
91行目で、属性名を、ATTR_MAPを使って、'className','htmlFor'の指定をHTMLの表現の'class'、'for'におきかえています。
92行目で、属性の値の内部にあるダブルクォートを、HTMLの記法の"に、正規表現で置き換えます。これで次のような指定も正しく扱うことができます。
93行目で、全ての結果を結合して、"属性名1=値1 属性名2=値2 ..."という形の文字列を返します
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します。
112~114行目の_isStringOrNumberは、引数の型が文字列か数字であるかを返す関数です。typeof演算子を使って判断します。
115~119行目のbuildは、引数にHTMLが書かれた文字列をとって、そのDOMを返す関数です。DIV要素を作ってHTMLを書き込み、中身をElement.downメソッドで取り出して返します。
120~136行目のdumpは、DIV(P('hello world'))などと書けるように、DIV関数やP関数などをまとめて定義する関数です。デフォルトでは、これらはwindow以下に定義されてグローバル関数になりますが、引数にオブジェクト(いわゆるインスタンス)か関数(いわゆるクラス定義)を与えて、これらのスコープを制限することもできます。
132行目からわかるように、DIV(...)の実体はBuilder.node ('DIV',...)です。JavaScriptのapplyの定義はこちらを参照してください。