TEST
Simple Implementation of DOM Events in JavaScript
最終更新:
eriax
-
view
制限
- DOM Events のシンプル(?)な実装試案。名前空間ありの旧仕様。
ソースコード
if ('undefined' === typeof Array.prototype.indexOf) { Array.prototype.indexOf = function (ceil, floor) { return function (searchElement) { var fromIndex = arguments[1]; var count = this.length; var i = Number(fromIndex) || 0; i = (i < 0) ? ceil(i) : floor(i); if (i < 0) { i += count; } for (; i < count; i++) { if (i in this) { if (this[i] === searchElement) { return i; } } } return -1; }; }(Math.ceil, Math.floor); } //////////////////////////////////////////////////////////////////////// function DOMObject(arg) { if (arguments.length > 0) { var name; for (name in arg) { if (arg.hasOwnProperty(name)) { this[name] = arg[name]; } } } } (function () { this.constructor = DOMObject; }).call(DOMObject.prototype); //////////////////////////////////////////////////////////////////////// (function () { // EventPhase this.CAPTURING_PHASE = 1; this.AT_TARGET = 2; this.BUBBLING_PHASE = 3; }).call(DOMEvent); function DOMEvent(arg) { if (arguments.length > 0) { if (arg) { DOMObject.apply(this, arguments); } } } DOMEvent.prototype = new DOMObject; (function () { this.constructor = DOMEvent; // Level 2 this.type = null; this.target = null; this.currentTarget = null; this.eventPhase = null; this.bubbles = null; this.cancelable = null; this.timeStamp = null; // Level 3 this.namespaceURI = null; this.defaultPrevented = false; this.trusted = false; // extension this.ownerDocument = null; this.propagationStopped = false; this.immediatePropagationStopped = false; }).call(DOMEvent.prototype); (function () { this.stopPropagation = function () { this.propagationStopped = true; }; this.preventDefault = function () { if (this.cancelable) { this.defaultPrevented = true; } }; this.initEvent = function (eventTypeArg, canBubbleArg, cancelableArg) { this.initEventNS(null, eventTypeArg, canBubbleArg, cancelableArg); }; this.stopImmediatePropagation = function () { this.immediatePropagationStopped = true; }; this.toString = function () { return '[object Event]'; }; this.initEventNS = function (namespaceURI, eventTypeArg, canBubbleArg, cancelableArg) { this.namespaceURI = namespaceURI; this.type = eventTypeArg; this.bubbles = canBubbleArg; this.cancelable = cancelableArg; }; }).call(DOMEvent.prototype); //////////////////////////////////////////////////////////////////////// function DOMEventTarget(arg) { if (arguments.length > 0) { if (arg) { DOMObject.apply(this, arguments); } var name = '[EventRegistry]'; if (!this.hasOwnProperty(name)) { var memo = this[name] = []; memo[DOMEvent.CAPTURING_PHASE] = { '': [] }; memo[DOMEvent.BUBBLING_PHASE] = { '': [] }; } } } DOMEventTarget.prototype = new DOMObject; (function () { this.constructor = DOMEventTarget; this['[EventRegistry]'] = null; }).call(DOMEventTarget.prototype); (function () { this['[IsValidEventListener]'] = function (listener) { return ('function' === typeof listener) || ('object' === typeof listener && 'function' === typeof listener.handleEvent); }; this.addEventListener = function (type, listener, useCapture) { return this.addEventListenerNS(null, type, listener, useCapture); }; this.removeEventListener = function (type, listener, useCapture) { return this.removeEventListenerNS(null, type, listener, useCapture); }; this.dispatchEvent = function (evt) { evt.target = this; evt.timeStamp = new Date; PROPAGATION: { var targets = this['[TraceRoute]'](); var targetCount = targets.length; var i; // capture evt.eventPhase = DOMEvent.CAPTURING_PHASE; for (i = targetCount - 1; 0 <= i; i -= 1) { if (!targets[i]['[CallEventListener]'](evt)) { break PROPAGATION; // stop (Immediate)Propagation () called } } // target evt.eventPhase = DOMEvent.AT_TARGET; if (!this['[CallEventListener]'](evt)) { break PROPAGATION; // stop (Immediate)Propagation () called } // bubble if (evt.bubbles) { evt.eventPhase = DOMEvent.BUBBLING_PHASE; for (i = 0; i < targetCount; i++) { if (!targets[i]['[CallEventListener]'](evt)) { break PROPAGATION; // stop (Immediate)Propagation () called } } } } return evt.defaultPrevented; // preventDefault () called? }; this['[TraceRoute]'] = function () { var result = []; var node = this; if (node.nodeType > 0) { while ((node = node.parentNode)) { result[result.length] = node; } } return result; }; this['[GetEventListener]'] = function (evt) { var type = evt.type; var eventPhase = evt.eventPhase; var namespaceURI = evt.namespaceURI; if (namespaceURI == null) { namespaceURI = ''; } switch (eventPhase) { case DOMEvent.CAPTURING_PHASE: case DOMEvent.BUBBLING_PHASE: var registry = this['[EventRegistry]'][eventPhase]; var listeners = registry[namespaceURI]; if (listeners instanceof Object) { var handlers = listeners[type]; if (handlers instanceof Array) { return handlers; } return listeners[type] = []; } listeners = registry[namespaceURI] = {}; return listeners[type] = []; case DOMEvent.AT_TARGET: var listeners1 = this['[GetEventListener]']({ namespaceURI: namespaceURI, type: type, eventPhase: DOMEvent.CAPTURING_PHASE }); var listeners2 = this['[GetEventListener]']({ namespaceURI: namespaceURI, type: type, eventPhase: DOMEvent.BUBBLING_PHASE }); return listeners1.concat(listeners2); default: return []; } }; this['[CallEventListener]'] = function (evt) { var listeners = this['[GetEventListener]'](evt); var listenerCount = listeners.length; var listener; var i; for (i = 0; i < listenerCount; i++) { listener = listeners[i]; try { evt.currentTarget = this; if ('function' === typeof listener) { listener.call(this, evt); } else if ('function' === typeof listener.handleEvent) { listener.handleEvent(evt); } } catch (err) { ; } finally { if (evt.immediatePropagationStopped) { // stopImmediatePropagation () called return false; } } } return !evt.propagationStopped; // stopPropagation () called }; this.toString = function () { return '[object EventTarget]'; }; this.addEventListenerNS = function (namespaceURI, type, listener, useCapture) { if (this['[IsValidEventListener]'](listener)) { var listeners = this['[GetEventListener]']({ namespaceURI: namespaceURI, type: type, eventPhase: useCapture ? DOMEvent.CAPTURING_PHASE : DOMEvent.BUBBLING_PHASE }); listeners.push(listener); } }; this.removeEventListenerNS = function (namespaceURI, type, listener, useCapture) { if (this['[IsValidEventListener]'](listener)) { var listeners = this['[GetEventListener]']({ namespaceURI: namespaceURI, type: type, eventPhase: useCapture ? DOMEvent.CAPTURING_PHASE : DOMEvent.BUBBLING_PHASE }); if (listeners.length > 0) { var index = listeners.indexOf(listener); if (index >= 0) { listeners.splice(index, 1); } } } }; }).call(DOMEventTarget.prototype);
テスト。
<!DOCTYPE HTML> <title>TEST</title> <script type="text/javascript"></script> <script type="text/javascript"> function TestController(controls) { var Log1 = []; // T1 を要素ノードとして作る var T1 = new DOMEventTarget({ nodeType: 1, parentNode: null, childNodes: [] }); // T1 をイベントリスナで監視(キャプチャ) T1.addEventListener('click', function (e) { var log = []; log.push('T1: capturing_listener[0]'); // デフォルトアクションを取り消す if (controls['preventDefault'].checked) { e.preventDefault(); log.push('preventDefault() was called'); } // イベント伝播を抑止する if (controls['stopPropagation'].checked) { e.stopPropagation(); log.push('stopPropagation() was called'); } // イベント伝播を直ちに抑止する if (controls['stopImmediatePropagation'].checked) { e.stopImmediatePropagation(); log.push('stopImmediatePropagation() was called'); } Log1.push(log.join(' | ')); }, true); T1.addEventListener('click', function (e) { Log1.push('T1: capturing_listener[1]'); }, true); T1.addEventListener('click', function (e) { Log1.push('T1: capturing_listener[2]'); }, true); // T1 をイベントリスナで監視(バブル) T1.addEventListener('click', function (e) { Log1.push('T1: bubbling_listener[0]'); }, false); T1.addEventListener('click', function (e) { Log1.push('T1: bubbling_listener[1]'); }, false); T1.addEventListener('click', function (e) { Log1.push('T1: bubbling_listener[2]'); }, false); //////////////////////////////////////////////////////////////////////// // T2 を要素ノード(T1 の子)として作る var T2 = new DOMEventTarget({ nodeType: 1, parentNode: T1, childNodes: [] }); T1.childNodes.push(T2); // T2 をイベントリスナで監視(キャプチャ) T2.addEventListener('click', function (e) { Log1.push('T2: capturing_listener[0]'); }, true); T2.addEventListener('click', function (e) { Log1.push('T2: capturing_listener[1]'); }, true); T2.addEventListener('click', function (e) { Log1.push('T2: capturing_listener[2]'); }, true); // T2 をイベントリスナで監視(バブル) var T2_listener0 = { handleEvent: function (e) { Log1.push('T2: bubbling_listener[0], this=' + this); }, toString: function () { return '[object DOMEventListener]'; } }; T2.addEventListener('click', T2_listener0, false); T2.addEventListener('click', function (e) { Log1.push('T2: bubbling_listener[1], this=' + this); }, false); T2.addEventListener('click', function (e) { Log1.push('T2: bubbling_listener[2]'); }, false); // T2 を監視している T2_listener0 を除去する(バブル) // T2.removeEventListener ('click', T2_listener0, false); //////////////////////////////////////////////////////////////////////// // T3 を要素ノード(T2 の子)として作る var T3 = new DOMEventTarget({ nodeType: 1, parentNode: T2, childNodes: [] }); T2.childNodes.push(T3); // T3 をイベントリスナで監視(キャプチャ) T3.addEventListener('click', function (e) { Log1.push('T3: capturing_listener[0]'); }, true); T3.addEventListener('click', function (e) { Log1.push('T3: capturing_listener[1]'); }, true); T3.addEventListener('click', function (e) { Log1.push('T3: capturing_listener[2]'); }, true); // T3 をイベントリスナで監視(バブル) T3.addEventListener('click', function (e) { Log1.push('T3: bubbling_listener[0]'); }, false); T3.addEventListener('click', function (e) { Log1.push('T3: bubbling_listener[1]'); }, false); T3.addEventListener('click', function (e) { Log1.push('T3: bubbling_listener[2]'); }, false); //////////////////////////////////////////////////////////////////////// // イベント生成 var e = new DOMEvent({ ownerDocument: document }); // イベント初期化 e.initEvent('click', controls['bubbles'].checked, controls['cancelable'].checked); Log1.push('bubbles: ' + e.bubbles); Log1.push('cancelable: ' + e.cancelable); // イベント通知 var defaultPrevented = T3.dispatchEvent(e); // デフォルトアクションが取り消されたか Log1.push('defaultPrevented: ' + defaultPrevented); //////////////////////////////////////////////////////////////////////// controls['OUTPUT-1'].value = Log1.join('.\n'); } </script> <!-- ここから --> <p id="D1">T1 と、その子 T2 と、その子 T3 を作り、それぞれにイベントリスナを取り付けた後、T3 にイベントを送る。</p> <pre role="img" aria-describedby="D1"> + T1 | + T2 | + T3 <--- click! </pre> <form action="#" id="FORM-1"> <p> <input type="button" value="試す" aria-describedby="D1" onclick="TestController(this.form.elements)"> <label><input type="checkbox" name="bubbles" checked="checked">bubbles</label> <label><input type="checkbox" name="cancelable" checked="checked">cancelable</label> <label><input type="checkbox" name="preventDefault">preventDefault</label> <label><input type="checkbox" name="stopPropagation">stopPropagation</label> <label><input type="checkbox" name="stopImmediatePropagation">stopImmediatePropagation</label> </p> <p><textarea rows="20" cols="100" name="OUTPUT-1"></textarea></p> </form>
- 初出 2011-08-23/25