遊戯王カードWiki 印刷用Scriptlet

3年以上もご無沙汰しておりました…

最近、息子に 遊戯王カードWiki ページの印刷をよく頼まれるので、印刷に不要な部分を除去するScriptletを作成してみましたので、メモしておきます。

ブックマークを作成してURLのかわりに以下を設定します。

javascript:function t(v,n){var e=document.getElementById(v);while(n--)e=e.parentNode;if(e)e.parentNode.removeChild(e)}t('header');t('navigator');t('menubar',1)

Prototype 1.6.1 RC3 と CHANGELOG

6月16日に Prototype 1.6.1 RC3 が出てますね。 例によって、お知らせを適当に訳してみましょう。

Prototype 1.6.1 RC3: Chrome support and PDoc

Prototype 1.6.1 の3番目のリリース候補版(RC3)を公開しました。このリリースの主な特徴は、Chrome を正式にサポートしたこと、IE8 の互換性を向上させたこと、PDoc によってAPIドキュメントの生成が速くなったこと、それと多くのバグ修正です。

Chrome support

Google ChromeSafari とは兄弟みたいなものですから、Chrome が最初にリリースされたときから Prototype は素晴らしい互換性をもっていました。 今、我々は Chrome 1.0 と今後のバージョンを正式なサポート対象に加えます。

rake test を実行する際に全てのローカルにインストールされたブラウザが対象になりますが、もしあなたが Chrome をインストールしたのなら、Chrome も対象になります。今は Windows だけですが、マック用にアーリー・アルファ版 の Chrome もありますよ。

Generate your own docs with PDoc

Rails の RDoc のようなインラインのドキュメント・ツールである PDoc が、Prototype と script.aculo.us に組み込まれるまでには、長い道のりがありました。 1年以上前に Tobie が発案し、それが完成するまでにはJames Coglan と Samuel Lebeau による素晴らしい貢献がありました。

PDoc は RC2 より組み込まれていますが、今回はそれがスピードアップされました。 私のマシンで20分かかっていた処理が、今では60秒まで短縮されています。 またWindows 環境で発生していた、幾つかのマイナーな問題に対応しました。

Prototype 1.5 から、私たちはこのサイトやブログと同じエンジン、Mephisto でドキュメントを保持してきました。 いったんあるバージョンをリリースした後、ドキュメントをアップデートしていくのはちょっと辛かったのです。 PDoc は私たちのドキュメント管理を容易にし、そして古いバージョンの Prototype のためのアーカイブを保持するのも容易にします。

1.6.1の最終リリースの際、私たちは生成されたドキュメントをこのサイトに置きます。 それは Rails のサイトが最新の安定したドキュメントを提供しているのと、同じような感じです。 それまでの間、もしあなたがドキュメントを参照したいならば、ソースファイルをチェックアウトし、コマンドラインから rake doc を実行して、ローカルにドキュメントを生成してください。

Other improvements

RC2 からは他にも幾つかの問題修正があります。 Event#observe に関係した憎らしいバグとか、キー最適化に関する幾つかのバグとか。 RC2 で報告された幾つかの edge case 問題に対応し、IE8 への互換性が向上しました。 1.6.1 のために最後の数ヶ月間頑張ってくれ、ブラウザー検出への依存を少なくしてくれた、私たちの新しいメンバー Juriy (kangax) に特に感謝します。

CHANGELOG

今回はアナウンスからCHANGELOGがリンクされていませんが、このあたり ですかね。 短いので一緒に載せちゃいましょう。

  • コメントなどに含まれる non-ASCII 文字は、— のような文字実体参照に書き換えた
  • Chrome 1+ をサポートするブラウザに加えた
  • toTemplateReplacements 関数が null を返したとき、Template#evaluate が直前の文字を食べてしまう問題を修正
    • 1.6.1 rc2: if (Object.isFunction(object.toTemplateReplacements))
    • 1.6.1 rc3: if (object && Object.isFunction(object.toTemplateReplacements))
  • (不完全な) APPLET, OBJECT, EMBED 要素が、IE8 で擬似的なメソッドでちゃんと拡張されるようになった。 要素が既に拡張されている (_extendedByPrototypeが定義されている) 場合、すぐにリターンするようになった。
  • ElementExtensions が定義される方法を変更した。 SpecificElementExtensions で使用される要素がきちんと初期化されるようになった
    • 1.6.1 rc2: return function(element) {
    • 1.6.1 rc2:  if (element && element.tagName) {
    • 1.6.1 rc2:   var tagName = element.tagName.toUpperCase();
    • 1.6.1 rc2:   if (tagName === 'OBJECT' || tagName === 'APPLET') {
    • 1.6.1 rc2:    extendElementWith(element, Element.Methods);
    • 1.6.1 rc2:    if (tagName === 'OBJECT') {
    • 1.6.1 rc2:     extendElementWith(element, Element.Methods.ByTag.OBJECT)
    • 1.6.1 rc2:    }
    • 1.6.1 rc2:    else if (tagName === 'APPLET') {
    • 1.6.1 rc2:     extendElementWith(element, Element.Methods.ByTag.APPLET)
    • 1.6.1 rc2:    }
    • 1.6.1 rc2:   }
    • 1.6.1 rc2:  }
    • 1.6.1 rc3: return function(element) {
    • 1.6.1 rc3:  if (element && typeof element._extendedByPrototype == 'undefined') {
    • 1.6.1 rc3:   var t = element.tagName;
    • 1.6.1 rc3:   if (t && (/^(?:object|applet|embed)$/i.test(t))) {
    • 1.6.1 rc3:    extendElementWith(element, Element.Methods);
    • 1.6.1 rc3:    extendElementWith(element, Element.Methods.Simulated);
    • 1.6.1 rc3:    extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
    • 1.6.1 rc3:   }
  • より効率的な RegExp#test を利用するよう、配列の生成と Array#include を置き換えた
  • プリミティブ値でも $A がきちんと動作するようになった
    • 1.6.1 rc2: if ('toArray' in iterable) return iterable.toArray();
    • 1.6.1 rc3: if ('toArray' in Object(iterable)) return iterable.toArray();
  • selector 処理の際、unmark 機能を場合わけする際にブラウザ検出を利用しなくなった。 かわりに専用のテスト PROPERTIES_ATTRIBUTES_MAP を使用する
    • 1.6.1 rc3: var PROPERTIES_ATTRIBUTES_MAP = (function(){
    • 1.6.1 rc3:  var el = document.createElement('div'),
    • 1.6.1 rc3:   isBuggy = false,
    • 1.6.1 rc3:   propName = '_countedByPrototype',
    • 1.6.1 rc3:   value = 'x'
    • 1.6.1 rc3:  el[propName] = value;
    • 1.6.1 rc3:  isBuggy = (el.getAttribute(propName) === value);
    • 1.6.1 rc3:  el = null;
    • 1.6.1 rc3:  return isBuggy;
    • 1.6.1 rc3: })();
  • パフォーマンス上の理由から、要素のメソッドによる短縮表記 (@element.getStyle() -> Element.getStyle(@element)) を使わなくなった。 Array.prototype.slice が代わりに使用できる場合には、`$A` や `Array.prototype.shift を使用しない
  • Prototype.Browser.Opera のより確実な判定方法として、window.opera クラスの存在をチェックするようになった
    • 1.6.1 rc2: Opera: navigator.userAgent.indexOf('Opera') > -1,
    • 1.6.1 rc3: var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
  • event.js で、1つのイベント名/要素の組み合わせに、2つ以上のレスポンダーがアタッチされている場合に発生するエラーに対応
    • 1.6.1 rc2: var respondersForEvent = registry.get(eventName);
    • 1.6.1 rc2: if (Object.isUndefined()) {
    • 1.6.1 rc2:  respondersForEvent = [];
    • 1.6.1 rc2:  registry.set(eventName, respondersForEvent);
    • 1.6.1 rc2: }
    • 1.6.1 rc3: var respondersForEvent = registry.get(eventName);
    • 1.6.1 rc3: if (Object.isUndefined(respondersForEvent)) {
    • 1.6.1 rc3:  respondersForEvent = [];
    • 1.6.1 rc3:  registry.set(eventName, respondersForEvent);
    • 1.6.1 rc3: }
  • IE独自の mouseenter/mouseleave イベントを検査するのにブラウザ判別をするのを止めた。 かわりにもっと丈夫なインターフェイスを使うようにした
  • ユニットテスト中は一貫して Prototype.emptyFunction を使用する
  • Array#reduce() を削除予定に指定
  • 名前に "length" を含む要素をもつフォームで Form.serialize を安全に使用できるようになった

Dojo で最低限必要なファイル

とりあえず Dojo を始めるには、Dojo downloads から dojo.js のみを貰ってくれば良いみたいですね。 現在は v1.3.1 で、サイズは 81,498bytes でした。
ただし、これはあくまでコア部分。 console.dir() など便利な機能を利用するには、その下にあるフルセットが必要、、、ですよね。 ただこっちは2,161個のファイルが含まれていて、tar.gz 圧縮した状態で 2.6Mbytes、解凍すると 8.7Mbytes もあります。 すげー差。
ホントにこれ全部、サイトに上げないといけないの? と軽く調べてみました。

djConfig="isDebug: true" に必要なもの

console.debug() や console.dir() を使用するためには、dojo.js 読み込みの際にisDebug をセットする必要があります。 で、これを指定すると dojo 内部では

1665: //Load debug code if necessary.
1666: if(dojo.config.isDebug){
1667:       dojo.require("dojo._firebug.firebug");
1668: }

って感じで dojo/_firebug/firebug.js ファイルを自動で読み込むみたいです。 で、これに加えて以下の 9ファイルも必要みたい。

dojo/_firebug/firebug.css
   firebug が使えないときに利用する CSS
dojo/_firebug/errorIcon.png
dojo/_firebug/infoIcon.png
dojo/_firebug/warningIcon.png
   以上、CSSで指定された行ごとのステータスアイコン
dojo/_firebug/tab_lft_norm.png
dojo/_firebug/tab_lft_over.png
dojo/_firebug/tab_rgt_norm.png
dojo/_firebug/tab_rgt_over.png
   以上、CSSで指定された操作ボタン用画像

dojo.js に加え、これら計10個のファイルを配置しておけばOKみたいですねぇ。

dojo.js だけで利用可能な範囲

console.dir() が使えるようになったところで、以下を実行してみました。

dojo.addOnLoad(function(){
   console.dir(dojo._hasResource);
});

結果は以下のとおり。 dojo/_base の中の16ファイルは全て、dojo.js に含まれているみたいですね。

dojo._firebug.firebug : true,
dojo._base.lang : true,
dojo._base.declare : true,
dojo._base.connect : true,
dojo._base.Deferred : true,
dojo._base.json : true,
dojo._base.array : true,
dojo._base.Color : true,
dojo._base : true,
dojo._base.window : true,
dojo._base.event : true,
dojo._base.html : true,
dojo._base.NodeList : true,
dojo._base.query : true,
dojo._base.xhr : true,
dojo._base.fx : true,
dojo._base.browser : true

では djConfig="parseOnLoad: true" はどう?

同じく dojo.js 読み込みの際に指定するオプション、parseOnLoad の影響はどうでしょう? ・・・指定しても変化はありません。 あれ?
dojo.js の中を探しても parseOnLoad に関する定義は無く、parser.js の中で以下のようにチェックしているみたいですね。

if(dojo.config["parseOnLoad"]==true){
      dojo.parser.parse();
}

ということで、parseOnLoad をセットするだけでなく dojo.require("dojo.parser"); と明示的に読み込んであげる必要があります。 ま、UIパーツで必ず dojo.require() されているので、実際にはわざわざ読み込む必要は無いんですけどね。
ちなみに parser.js 内にも dojo.require() がありまして、結果として以下の2ファイルが必要なようです。

dojo/parser.js
dojo/date/stamp.js

dijit.layout.ContentPane だと、どんな感じ?

さて、ここで調子にのって dojo.require("dijit.layout.ContentPane"); とかやってみましょう。 きっと後悔します。 (ぉ

以下に読み込みの依存関係をざっとまとめてみました。 上から順に呼んでいき、インデントは依存関係を示しています。 同じモジュールが出てきたときには、末尾に * をつけてみました。

dijit.layout.ContentPane
   dijit._Widget
      dijit._base
         dijit._base.focus
         dijit._base.manager
         dijit._base.place
            dojo.AdapterRegistry
         dijit._base.popup
            dijit._base.focus*
            dijit._base.place*
            dijit._base.window
         dijit._base.scroll
         dijit._base.sniff
         dijit._base.typematic
         dijit._base.wai
         dijit._base.window*
   dijit._Contained
   dijit.layout._LayoutWidget
      dijit._Widget*
      dijit._Container
      dijit._Contained*
   dojo.parser
      dojo.date.stamp
   dojo.string
   dojo.html
      dojo.parser*
   dijit.nls.*.loading

最後のは dojo.requireLocalization によるリソース読み込みですね。
いやー、さすがに複雑だわ。 でも、こう整理してみると、digit のおおまかな構成がわかった気になりますな。
あと関係ないけど、必要モジュールがロードできない場合、それが digit 系のモジュールだった場合は呼び出しスタックの全てが console.error() してくれ、しかも足りないファイル名まで表示してくれて楽でした。
dojo 系だと常に「'dijit.layout.ContentPane' クラスがロードできへんけど? 名前打ち間違えてるちゃうんか。 'dijit.form.Button'みたいにフルパス指定しなきゃアカンで(適当訳)」ぐらいしか言ってくれません。。

Prototype から Dojo へ?

しばらく Prototype を使ってきた僕ですが、最近は Dojo を使い始めています。 良さげなトコは

  • JavaScriptの基本オブジェクトを拡張しない
    • dojo.* な表記なのはわかりやすい
  • パッケージ管理などJavaっぽい
    • dojo.provide/dojo.require は良いね
  • dijitが標準で使用できるのは嬉しい
  • consoleが地味に便利


さすがに多機能なライブラリだけあって、Prototope でできることは、ほぼできそうな雰囲気。 いいかも。 以下は Prototype からの移行メモ。

  • $() は dojo.byId()
  • $$() は dojo.query()
    • CSS3風の多彩な指定が可能
    • 戻ってきた配列モドキに一括操作メソッドが多数
  • Class.create は dojo.declare()
    • initialize: は constructor:
    • メソッド中で $super() は this.inherited(arguments)
  • 配列のeach() は dojo.forEach()
  • Event.observe() は dojo.connect()
  • 関数の bind() は dojo.hitch()


とりあえず以下の情報が役に立ちそうな感じ。


日本語の資料が少ないのが気になりますが、すこし頑張ってみますかね。。

prototype 1.6.1 RC2 の CHANGELOG を翻訳してみる

先日 1.6.1 RC2 のニュースを訳してみました が、CHANGELOG もざっと訳してみました。 今回は55個ぐらい? 喜ぶヒトが居るかは不明ですが、置いておきますね。

  • ユニットテストの間、一貫して Prototype.emptyFunction を使用するように
  • deprecation extension: Array#reduce() を削除としてマーク
  • "length" という名前の要素があっても Form.serialize が安全に動作するようになった
  • IE で Element#update が SCRIPT 要素を考慮するようになった
  • Element.extend から使用されていないローカル変数を削除。 form テストの1つで、_extendedByPrototype を false でなく undefined をセットすることにより削除するよう修正。(_extendedByPrototype が false の場合、Element.extend が要素を強制的に再extendしない動作になる)
    • 1.6.0.3: if (!element || element._extendedByPrototype ||
    • 1.6.1 RC2: if (!element || typeof element._extendedByPrototype != 'undefined' ||
  • escapeHTML と unescapeHTML へのテストをより厳格に。(Chrome 1.x は "<" "&" をエスケープするが、">" はしない)
  • DOMテストの1つより、ブラウザ判別を削除。 これで最後の IE8 対応。
  • HTMLAppletElement と HTMLObjectElement オブジェクトが Element.prototype から継承されないという IE8 のバグに Element.extend が対応した
  • DOMテストで setOpacity をテストする際に、正式の機能テストを使用するよう修正
  • 属性の get/set で、for/htmlFor を変換していたが、IE8 で問題になり対応
  • 正式な機能テストの不足により発生した、IE8 における Element#writeAttribute と Element#readAttribute の問題に対応
  • IE8 で問題を発生していたブラウザ判別を DOMテストの1つから削除した
  • Form.reset テストの際、IEでは関数ではなく単なるオブジェクトが返されるため、respondsTo がメソッドを認識しない問題を修正
  • JS1.8 を実装したクライアント(例: Firefox 3+)において、Array#reduce がネイティブの reduce を上書きしてしまうため削除
  • try/finally をサポートしないクライアントのため、かわりに try/catch/finally を使用することを明確化
  • 2.0.4 以前の Safari でクラッシュするのを防ぐため、 ノードリストのプロパティにアクセスするため in オペレータを使用する
  • Element#clone をネイティブの cloneNode のラッパーとして追加
  • Element コンストタクタで、IE8 が適切なクラス名をアサインしているかを確認するテストを追加
  • IEで破壊された setAttribute を検出する際に、ブラウザ判別を止めた
  • Element.updateで機能検出して分岐する際に、ブラウザ判別を止めた
  • escapeHTML と unescapeHTML の分岐で、ブラウザ判別を止めた
  • セレクタが与えられない場合、IE 6-7 で全ての子要素を返さないよう、Element#down を再定義した
  • 無駄な計算を省くため、Event#pointer(X|Y) と Event#pointer の定義を反転させた
  • IE以外のブラウザに mouseenter と mouseleave イベントに関するちゃんとしたサポートを追加 (IEはネイティブでサポートしている)
  • 幾つかの環境で問題が発生したため、 _extendedByPrototype, _countedByPrototype, prototypeUID のノード拡張は typeof 演算子でアクセスすることを明確化
  • Opera 9.x で子要素を含んだセレクター指定をすると、誤った結果が返される問題を修正
  • ページの unload 時に、キャッシュ中の要素に対する参照を Null クリアする。 これは Event#stopObserving 中にメモリーリーク対策で追加される。
  • IE6 で toString と valueOf プロパティが必要なときにだけサブクラスにコピーされるよう確認
  • iframe の type 属性にアクセスする際、getAttribute がフラグ無しで使用されるように明確化 (そうしないとIEでエラーになる)
  • String#gsub は最初の引数が文字列の場合、RegExp メタ文字をエスケープする
    • 1.6.1 rc2: if (Object.isString(pattern))
    • 1.6.1 rc2:  pattern = RegExp.escape(pattern);
  • String#unescapeHTML における置換の順番を修正
    • 1.6.0.3: return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
    • 1.6.1 rc2: return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
  • ルート要素が文書に関連づけられていない場合、IEセレクター指定が失敗する問題に対応
  • ハイフン文字を含んだ属性にセレクターがマッチするよう修正
  • Form.reset が常に受け側の要素への参照を返すよう明確化
  • querySelectorAll をサポートしたブラウザでは、contextual CSS 選択の際に ":" と "." の文字をエスケープする
  • IE でイベントの target 属性がけっして undefined にならないよう確認
  • Element#descendants が常に配列を返すように確認
  • Element.getDimensions で fixed position 要素を absolute に変換しないようにする
  • 空のパターンで String#sub が無限ループに陥らないようにする
    • 1.6.1 rc2: if (!(pattern.length || pattern.source)) {
    • 1.6.1 rc2:  replacement = replacement('');
    • 1.6.1 rc2:  return replacement + source.split('').join(replacement) + replacement;
    • 1.6.1 rc2: }
  • Prototype の配布ファイル作成ツールを Sprockets に変更
  • Object.is(Array|String|Number)を Juriyさんの発見した、より汎用的で優れた手法を利用するよう変更
    • 1.6.0.3: isString: function(object) {
    • 1.6.0.3:  return typeof object == "string";
    • 1.6.0.3: },
    • 1.6.1 rc2: function getClass(object) {
    • 1.6.1 rc2:  return Object.prototype.toString.call(object)
    • 1.6.1 rc2:   .match(/^\[object\s(.*)\]$/)[1];
    • 1.6.1 rc2: }
    • 1.6.1 rc2: function isString(object) {
    • 1.6.1 rc2:  return getClass(object) === "String";
    • 1.6.1 rc2: }
  • IE のホストオブジェクトで、Object.is(String|Number) が例外を発生しないように修正
  • Enumerable#grepRegExp メタ文字を含む文字列を扱えるよう修正
  • dom:loaded カスタムイベントを「doScroll アプローチ」に切り替えた
    • 1.6.1 RC2: function pollDoScroll() {
    • 1.6.1 RC2:  try { document.documentElement.doScroll('left'); }
    • 1.6.1 RC2:  catch(e) {
    • 1.6.1 RC2:   timer = pollDoScroll.defer();
    • 1.6.1 RC2:   return;
    • 1.6.1 RC2:  }
    • 1.6.1 RC2:  fireContentLoadedEvent();
    • 1.6.1 RC2: }
  • document.viewport.get(Dimensions|Width|Height) を最適化
  • 文字列と数値のラッパーに対し、Object#isString と Object#isNumber が false を返す問題に対応
  • dom:loaded カスタムイベントを発生する前に document.loaded = true をセット
  • Element#store に key/value ペアを含むオブジェクトを渡せるように
    • 1.6.1 RC2: if (arguments.length === 2) {
    • 1.6.1 RC2:  element.getStorage().update(key);
    • 1.6.1 RC2: } else {
    • 1.6.1 RC2:  element.getStorage().set(key, value);
    • 1.6.1 RC2: }
  • Element#store が要素自身を返すように変更
  • non-bubblingな(上に伝わっていかない)カスタムイベントを追加。 Element#fire の最後に追加された引数がこれを制御し、true をデフォルト値とする。 bubbling させたくない場合には false を指定すること。
  • イベントシステムはグローバルなハッシュではなく、要素の storage API を利用するようになった
  • 要素のメタ情報を安全にハッシュベースで格納するため、Element#store と Element#retrieve を追加。 メモリリークもなくなる。 ハッシュ領域を直接操作するため Element#getStorage も追加。 (mootools に感謝を)
  • いくつかのバージョンの Safari で、Selector/$$ アクセスの際に case-insensitiveな(大文字小文字を区別しない)のに対応
  • IEで、コメントと属性名が混じっているとき、Function#argumentNames が正しくない結果を返す問題を修正
  • Selector.patterns は順序だった構成で表現されているべき
    • 1.6.0.3: patterns: {
    • 1.6.0.3:  laterSibling: /^\s*~\s*/,
    • 1.6.0.3:  child: /^\s*>\s*/,
    • 1.6.1 RC2: patterns: [
    • 1.6.1 RC2:  { name: 'laterSibling', re: /^\s*~\s*/ },
    • 1.6.1 RC2:  { name: 'child', re: /^\s*>\s*/ },
  • Function メソッドでパフォーマンスを改善

Prototype 1.6.1 RC2 が出てますね

3月29日に Prototype 1.6.1 RC2 が出てますね。 お知らせを適当に訳してみましょう。

Prototype 1.6.1 RC2: IE8 compatibility, Element storage, and bug fixes

Prototype 1.6.1 の最初のリリース候補版を公開しました。(RC1 はどうしたって?聞かないで) このリリースにはまだまだ小規模の修正が必要なのですが、IE8が先週リリースされちゃったので、暫定的な版をリリースすることにしました。

これは IE8 の「super-standards」モードに完全に対応し、かつ最適化された最初のリリースです。 とくに今回のリリースにより Prototype は、IE8 がサポートする Selectors API、その DOM Element のプロトタイプ拡張に対応します。

What’s new?

  • IE8に完全対応。Juriy は既存の IE"検出" チェックの多くを、完全なものに置き換えてくれました。これにより IE8 において「super-standards」モードと互換モードの両方を容易にサポートできます
  • 以前にアナウンスした Element storage。複雑なメタデータを個別の Element に対し、安全に関連付けることができます
  • mouseenter と mouseleave イベント。 IE独自のイベントをシミュレートしており、mouseover と mouseout イベントより便利に利用できそうです
  • あなたが新しいコピーで“cleanup”できるよう、DOM ノードを複製する Element#clone メソッド

What’s been improved?


詳細は CHANGELOG を参照してください。

上記にくわえ1.6.1 リリースでは、以前から作業されてきた2つの外部プロジェクト、Sprockets (JavaScript concatenation) と PDoc (inline documentation) が組み込まれます。 Sprockets はビルド時に Prototype を1つの配布用ファイルに結合するため、既に使用されています。 PDoc はこれからの我々の文書フレームワークとなるでしょう。 正式な API ドキュメントはまだ準備中ですが、1.6.1 の最終リリースには間に合うでしょう。

余談

What’s new からリンクされている Juriy Zaytsev さんの、Detecting event support without browser sniffing というエントリが興味深いです。

これまで JavaScript は「ブラウザを判別」して処理を分けることが多かったように思えます。 英文にある IE“sniffs”という記述がソレです。 Juriyさんは「そろそろ機能ごとに在る/無しを判断して対応をわけようよ」と述べている様子。 IE8 のように3種類のモードをもつブラウザも出てきましたし、バージョンによる違いも増えてきました。 これは説得力ある意見ですし、だから 1.6.1 に取り入れられたのでしょう。 英文が苦手な方でも、ここに紹介された isEventSupported 関数のコードを読めば、Juriyさんの言いたいことが理解できるのではないでしょうか。

関係ないですが、この isEventSupported関数のコード、僕は好きですね。 短いですが、JavaScriptっぽさに溢れているとおもいます。 関数を単に定義するのではなく、関数を返す無名関数を定義し実行することで実行環境(定義した TAGNAMES)を保持していたり。 TAGNAMES[eventName] || 'div' なんて演算子で場合分けをしてみたり。 JavaScript初心者は後にある isMouseEventSupported の定義を理解してから、このコードにとりかかれば味わい深いでしょう。

ただ個人的には、Firefox ではちょっとチェック処理が重い気がしています。 呼ぶたびに、一時的なElementを作成してしまいますからね。 自分ならば、よく使う機能に限って、この関数の結果をキャッシュしちゃうとおもいます。 もしくは事前にチェックしておいて、該当する処理関数を置き換えちゃいます。

IEでイベントの順番がおかしくなる訳

ページ表示を待って処理を実施したいときに Prototype の Event.observe(window, 'load', function(){...}); をよく使います。 これを複数回使った場合、登録された順に実行される… はずだったのですが、IE では順番が狂うみたい。 んでさっそく、テストページを作ってみました。


実行する JavaScript は以下のように単純なものです。

Event.observe(window, 'load', function(){ alert(1); });
Event.observe(window, 'load', function(){ alert(2); });

実行結果

以下は定義した順 (1,2) で表示されたもの。


で、以下が定義した逆順 (2,1) で表示されたもの。

  • IE8 Beta
  • IE7
  • IE6 SP2


実行結果はやはりといいますか、IE だけが変でした。 標準準拠(xhtml)モード、互換(HTML)モードでは結果はかわりませんでした。

IE だけ結果が変な理由

Prototype 1.6.0.2 の該当部分のコードは以下のようになっています。 まぁ、一般的な記述でしょう。

if (element.addEventListener) {
  element.addEventListener(name, wrapper, false);
} else {
  element.attachEvent("on" + name, wrapper);
}

IE では addEventListener が使えなくて、かわりに attachEvent があるのですが、この実装が問題。 登録した順には実行してず、逆順ですらなく… なんと順不同で呼び出してくれちゃう模様。 なんじゃそりゃ。

この問題、IE8βでも直っていなかったのが、個人的にはプチショックです。 DOM に準拠して、そろそろ addEventListener が実装されてると予想していたから。 それなら attachEvent がこれまで順不同だったので、せめて登録順の実装に換えといてくれよぉ、とか呟いてみたり。

とりあえずの逃げ

自分的に最も困るのが、window の onLoad イベントで、こればかりは登録順であって欲しい。 他のは順不同でも呼ばれないわけじゃないから、まぁなんとかなります。

具体的には、自分の Web で使用させてもらっている GlassBox というライブラリに非常に癖がありまして… 初期化の順番を違えると表示がうまくできないんだ、これが。

で、クイックハックといいますか、いちばんベースのライブラリに以下のコードを追加しています。

jp.rinco.observeList = [];
Event.observe(window, 'load', function(){
  jp.rinco.observeList.each(function(f){
    if (f && f instanceof Function)
      f();
    });
});

利用するほうでは、以下のように記載します。 将来的に廃止できることを期待して、条件判断かませています。

if (jp.rinco.observeList)
  jp.rinco.observeList.push(myInit);
else
  Event.observe(window, 'load', myInit);

やってることは単純で、自分で関数リストを管理して、順に実行しちゃう訳ですね。 単なる配列ですから、処理の最初に放り込んだり自由にできます。 今のところ特にシビアな処理は呼ばないので、エラーチェックはサボっています。