l'essentiel est invisible pour les yeux

Monday, January 14, 2008

[Javascript] Prototype.js 1.6.0 - Event.fireでNative DOM Eventを呼び出し可能にする

Javascriptの話題が続いてます。

Changeset 7835 - prototype: Namespace all custom event names to avoid conflicts with native DOM events.

上記Changesetで、Native DOM Eventとの名前衝突を避けるために、コロンで区切るネームスペースが導入された & Native DOM Eventの呼び出しが削除された?ため、Native DOM Eventが呼び出せない。[Javascript] クロージャを利用したイベントリスナの登録のように、クロージャー + 無名関数でイベントリスナを登録すると、スコープの外部から、現在選択中のボックスを取得するとなると、各ノードを走査して調べる事になりますが、これは美しくない。


Prior to this change, Prototype treated only the event names present in the Event.DOMEvents array as native DOM events. Now, Prototype looks for the presence of the namespace delimiter—a single colon—to determine whether you’re observing a custom event or a native event.(*1)

RC1の時点では、Event.DOMEventsの配列中の値を指定する事でNative Eventを呼び出せたみたいだけど、上記のChangesetを見る限り、1.6.0ではこれ無くなってるね。

で、Native DOM Eventを呼び出し可能な、Event.fireを再定義する事にした。
ボックスをクリックした時に呼び出されるイベントハンドラを、ボタンをクリックする事でも呼び出すようにしたサンプル。
Fire the native DOM Event


テスト

// A event listener
$('selection').observe('click', (function() {
var SELECTED_CLASS_NAME = 'selected';
var selected;
return function(event) {
if(selected) selected.removeClassName(SELECTED_CLASS_NAME);
event.target.addClassName(SELECTED_CLASS_NAME);
selected = event.target;
};
})());

// Fire the DOM Event dynamically
$('container').observe('click', function(event) {
if(event.target.tagName.toUpperCase() == 'INPUT') {
var choiceIdx = parseInt(event.target.id.replace('btn_', ''));
$('box' + choiceIdx).fire('click', {});
}
});



DOM 3 Eventならイベントオブジェクトを作成して、イベントを呼び出します。
Event.fireの第三引数には、Eventオブジェクト作成時のパラメータをハッシュ(Object)で指定できます。もし、カスタムイベントの場合は、通常のEvent.fireが呼び出され、Event.fireの第三引数として渡されます。最後に、Event#fireを新しく定義した、Event.fire_with_native_eventsで置換しておわり。

※今、手元にIEが無いので、IEでのテストが出来ていません。(あとで)

サンプル

$('text_field').fire('keydown', {charCode: Event.KEY_RETURN});


実装

Object.extend(Event, (function() {
// DOM Level 3 events
var W3C_MOUSE_EVENTS = $w('click mousedown mousemove mouseout mouseup');
var W3C_KEYBOARD_EVENTS = $w('keydown keyup keypress');
var W3C_BASIC_EVENTS = $w('abort change error load reset resize scroll submit unload');

function createDOMEvent(aEventName, aEventParams)
{
var event;
if(W3C_MOUSE_EVENTS.include(aEventName)) {
var p = Object.extend({
bubble: true,
cancelable: true,
view: window,
detail: 0,
screenX: 0,
screenY: 0,
clientX: 0,
clientY: 0,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
button: 0,
relatedTarget: null
}, aEventParams);
if(document.createEvent) {
event = document.createEvent('MouseEvent');
event.initMouseEvent(aEventName, p.bubble, p.cancelable, p.view, p.detail, p.screenX,
p.screenY, p.clientX, p.clientY, p.ctrlKey, p.altKey, p.shiftKey, p.metaKey,
p.button, p.relatedTarget);
} else {
// TODO: IE
Object.extend(event, p);
event.eventType = 'on' + aEventName;
}
} else if(W3C_KEYBOARD_EVENTS.include(aEventName)){
var p = Object.extend({
bubble: true,
cancelable: true,
view: null,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
keyCode: 0,
charCode: 0
}, aEventParams);
if(document.createEvent) {
event = document.createEvent('KeyboardEvent');
event.initKeyEvent(aEventName, p.canBubble, p.cancelable, p.view, p.ctrlkey, p.altKey, p.shiftKey, p.metaKey,
p.keyCode, p.charCode);
} else {
// TODO: IE
event = document.createEventObject();
Object.extend(event, p);
event.eventType = 'on' + aEventName;
}
} else if(W3C_BASIC_EVENTS.include(aEventName)) {
var p = Object.extend({
bubbles: true,
cancelable: true
}, aEventName);
if(document.createEvent) {
event = document.createEvent('HTMLEvents');
event.initEvent(aEventName, p.bubbles, p.cancelable);
} else {
// TODO: IE
event = doument.createEventObject();
Object.extend(event, p);
event.eventType = 'on' + aEventName;
}
}
return event;
}

return {
fire_with_native_events: function(element, eventName, eventParamsOrMemo) {
var event = createDOMEvent(eventName, eventParamsOrMemo);
if(event) {
document.createEvent? element.dispatchEvent(event) : element.fireEvent(event.eventType, event);
} else {
Event.fire(element, eventName, eventParamsOrMemo);
}
}
};
})());

// Replace an Element#fire
Element.addMethods({
fire: Event.fire_with_native_events
});


脚注
1. Prototype 1.6.0 RC1: Changes to the Class and Event APIs, Hash rewrite, and bug fixes