以前のエントリー でprototype.js1.5のElement.Methodsについて書きました。 前回はいいところをプッシュして書いたんですが、皆さんご存じの通り問題児でもあります。

Element.Methodsの問題点

  • elementを拡張するかしないかが選択できない。
  • プロトタイプチェーンがつながっていない。
  • document.createElementが考慮されていない。

■elementを拡張するかしないかが選択できない。

まずわかりやすくこれから。elementは取得された時に拡張されるわけですが、当然パフォーマンスに影響があります。使いたくない人もいるでしょう。しかし、Element.extendによる拡張は$にも$$にも組み込まれていて選択できません。メソッドを置き換えて回避は可能ですが、組み込まれている、ということを考慮すると当然extendされている、ということを前提としたライブラリも出てくるでしょう。となった場合にやっかいです。別名のメソッドを定義してもいいですが・・・やっぱり$って名前がいいよね!(何

■プロトタイプチェーンがつながっていない。

これは当然の問題。Element.extendは文字通りelementにElement.Methodsのメソッドをコピーしているだけ。しかも一回取得すると、_extendedというフラグが立って次回からはもうextendされない(パフォーマンスを考慮してのこと)。つまり Element.Methodsへのメソッド追加が反映されない、メソッドの内容変更も反映されない。  つまり、恐らく$か$$されるだろう、window.onloadイベント前にElement.Methodsへのメソッド追加を完了させ、それ以降変化させない、という事に注意しないといけない。

ではなぜプロトタイプチェーンがつながっていないのだろうか。Elementは拡張できないんだろうか。これはYesでありNoです。 まず、このElement.Methodsによる害をある程度押さえ込むことのできるソースを示します。 これはFirefoxなどMozilla系、safari、Operaで有効です。

 1(function() {
 2if(!window.HTMLElement) window.HTMLElement = document.createElement("xxx").constructor;
 3if(window.HTMLElement) {
 4    var methods = Element.Methods, property;
 5    var bind = function(name, method) {
 6      HTMLElement.prototype[name] = function(){
 7        Array.prototype.unshift.call(arguments, this);
 8        return method.apply(null, arguments) || this;
 9      }
10    }
11    for (property in methods) {
12      var value = methods[property];
13      if (typeof value == 'function') bind(property, value);
14    }
15    HTMLElement.prototype._extended = true;
16}
17
18})();

このコードを 最後に(window.onloadの直前で)実行すれば OKです(scriptaculousとかもElement.Methodsを拡張するので、その拡張が終わった後)。 これでelementがプロトタイプチェーンでつながっているブラウザでは$による負荷を軽減できます。$(“test”).show()とかも実行できますし、引数操作によるパフォーマンス低下がいやならElement.show(“test”)と実行すれば良いだけです。しかも地味にreturn method.apply(null, arguments) || this;とすることによって$(“test”).update(“hogehoge”).show()とか書けるようにもしてみました(笑

しかしIEはどうしようもありません。 そもそもIEはelementがプロトタイプチェーンをもっていません。 document.createElement (“div”).constructorとやってもprototypeとやってもundefinedと抜かす強敵です。これは昔から対策が考えられていますが、つまるところElement.Methodsと同様にコピーするだけです。それをhtcをつかってみたり、コンストラクタを擬似的に作ってみたり、とかっていう方法で実装しているだけです。なのでElement prototypingしようと思うとIEでは現状これが限界です。

■ document.createElementが考慮されていない。

先ほど示したソースでelementがプロトタイプチェーンをもつブラウザでは当然document.createElementで作成した elementもElement.Methodsのメソッドが実行できます。しかしIEではそれができません。この document.createElementも考慮して先ほどのElement.Methods対策ソースを改良しましょう。

 1(function() {
 2if(!window.HTMLElement) window.HTMLElement = document.createElement("xxx").constructor;
 3if(window.HTMLElement) {
 4    var methods = Element.Methods, property;
 5    var bind = function(name, method) {
 6      HTMLElement.prototype[name] = function(){
 7        Array.prototype.unshift.call(arguments, this);
 8        return method.apply(null, arguments) || this;
 9      }
10    }
11    for (property in methods) {
12      var value = methods[property];
13      if (typeof value == 'function') bind(property, value);
14    }
15    HTMLElement.prototype._extended = true;
16}else {
17  document._createElement = document.createElement;
18  document.createElement = function(tag){
19    return Element.extend(document._createElement(tag));
20  }
21}
22
23})();

赤色で示した部分が追加部分です。これでIEでもdocument.createElementした後に直接Element.Methodsのメソッドが実行できます。何度も言いますがパフォーマンス低下がいやな人はこの追加部分を省いてください。

というわけで今回はElement.Methodsの問題への対応策を示してみました。要はIEがelementがプロトタイプチェーンを持つようにしてくれれば万事解決なわけなんですよね。でも結局IE7でも実装されていないようで、なかなか・・・


prototype.js1.5ではみんながこうしたいなーと思っていた機能が実装されました。(via Encytemedia ) (SVN co http://dev.rubyonrails.org/svn/rails/spinoffs/prototype して rake distしたもの )

■Element.Methods

1.4まではelementに対する操作は

1Element.show($("test"));
2Element.update($("test"), "hoge");

のように書く必要がありました。

これが

1$("test").show();
2$("test").update("hoge");

と書けるようになりました。

これはprototype.jsで最も偉大な$メソッドの変更によるモノです。

 1function $() {
 2  var results = [], element;
 3  for (var i = 0; i < arguments.length; i++) {
 4    element = arguments[i];
 5    if (typeof element == 'string')
 6      element = document.getElementById(element);
 7    results.push(Element.extend(element)); //<= ココ!
 8  }
 9  return results.length < 2 ? results[0] : results;
10}

つまり取得したelementに対してElement.extendが適応されるようになった、と。 んでそのElement.extendは

 1Element.extend = function(element) {
 2  if (!element) return;
 3
 4  if (!element._extended && element.tagName && element != window) {
 5    var methods = Element.Methods;
 6    for (property in methods) {
 7      var value = methods[property];
 8      if (typeof value == 'function')
 9        element[property] = value.bind(null, element);
10    }
11  }
12  element._extended = true;
13  return element;
14}

Element.Methodsのメソッドがelementに対して第1引数をそのelementに束縛した状態でセットされます。 つまりElement.Methodsにメソッドを追加すれば自動的に$で取得したelementに対してそのメソッドが追加されます。

ちなみにscriptaculousでも

1if(Element.Methods) {
2  Element.Methods.visualEffect = function(element, effect, options) {
3    s = effect.gsub(/_/, '-').camelize();
4    effect_class = s.charAt(0).toUpperCase() + s.substring(1);
5    new Effect[effect_class](element, options);
6    return $(element);
7  }
8}

というコードが追加されています。 クラス名変換のオーバーヘッドはあるものの、$(“test”).visualEffect(‘scale’)と書けるようになりました。

IEで$$が動くようになっていたりと1.5のリリースが待ち遠しいですね。


drk7.jp さんでXML2JSON serviceという面白いサービスが開始されました。

ということで早速ハックしてみました。

前のエントリーで書いた Javascriptテンプレートと組み合わせてめちゃくちゃ簡単にRSSを表示できます。

サンプルはこちら

使い方ですが、多分ソースをみてもらったほうが速いです。

サンプルHTMLから抜粋

1Event.observe(window, "load", function(){
2        new XML2JSON("<a href="http://feeds.feedburner.com/Inforno">http://feeds.feedburner.com/Inforno</a>", {container: "test", template : {element:"template"}});
3});

初期化コードはコレだけです。 containerでinnerHTMLに結果を流し込みたいElementを指定します。 templateは Lyase.View.render の引数と同じです。 例ではtextareaエレメントのid:“tempalte"のinnerHTMLをテンプレートとして扱います。

 1//テンプレート
 2  <textarea id="template" style="display:none;">
 3
 4<h3>< %= context.channel.title %></h3>
 5
 6<ul class="items">
 7      < % context.channel.item.each(function(item,index){%>
 8
 9    <li><a href="<%= item.link%>" target="_blank">< %= item.title%></a></li>
10        < % if(index > 5){throw $break;}%>
11      < %})%>
12   </ul>
13  </textarea>

こんな感じでテンプレートがかけるので、デザインの変更が大変楽になります。 またJSONデータ取得時のonloadを好きなメソッドに置き換えることもできるのでLyase.Viewを使わなくてもXML2JSONライブラリだけで おいしくいただけます。すきに改造してやってください、たいしたものじゃないですし(笑