191 lines
6.2 KiB
JavaScript
191 lines
6.2 KiB
JavaScript
/**
|
|
* @author Toru Nagashima
|
|
* @copyright 2015 Toru Nagashima. All rights reserved.
|
|
* See LICENSE file in root directory for full license.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Requirements
|
|
//-----------------------------------------------------------------------------
|
|
|
|
var Commons = require("./commons");
|
|
var CustomEventTarget = require("./custom-event-target");
|
|
var EventWrapper = require("./event-wrapper");
|
|
var LISTENERS = Commons.LISTENERS;
|
|
var CAPTURE = Commons.CAPTURE;
|
|
var BUBBLE = Commons.BUBBLE;
|
|
var ATTRIBUTE = Commons.ATTRIBUTE;
|
|
var newNode = Commons.newNode;
|
|
var defineCustomEventTarget = CustomEventTarget.defineCustomEventTarget;
|
|
var createEventWrapper = EventWrapper.createEventWrapper;
|
|
var STOP_IMMEDIATE_PROPAGATION_FLAG =
|
|
EventWrapper.STOP_IMMEDIATE_PROPAGATION_FLAG;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constants
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* A flag which shows there is the native `EventTarget` interface object.
|
|
*
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
var HAS_EVENTTARGET_INTERFACE = (
|
|
typeof window !== "undefined" &&
|
|
typeof window.EventTarget !== "undefined"
|
|
);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Public Interface
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* An implementation for `EventTarget` interface.
|
|
*
|
|
* @constructor
|
|
* @public
|
|
*/
|
|
var EventTarget = module.exports = function EventTarget() {
|
|
if (this instanceof EventTarget) {
|
|
// this[LISTENERS] is a Map.
|
|
// Its key is event type.
|
|
// Its value is ListenerNode object or null.
|
|
//
|
|
// interface ListenerNode {
|
|
// var listener: Function
|
|
// var kind: CAPTURE|BUBBLE|ATTRIBUTE
|
|
// var next: ListenerNode|null
|
|
// }
|
|
Object.defineProperty(this, LISTENERS, {value: Object.create(null)});
|
|
}
|
|
else if (arguments.length === 1 && Array.isArray(arguments[0])) {
|
|
return defineCustomEventTarget(EventTarget, arguments[0]);
|
|
}
|
|
else if (arguments.length > 0) {
|
|
var types = Array(arguments.length);
|
|
for (var i = 0; i < arguments.length; ++i) {
|
|
types[i] = arguments[i];
|
|
}
|
|
|
|
// To use to extend with attribute listener properties.
|
|
// e.g.
|
|
// class MyCustomObject extends EventTarget("message", "error") {
|
|
// //...
|
|
// }
|
|
return defineCustomEventTarget(EventTarget, types);
|
|
}
|
|
else {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
};
|
|
|
|
EventTarget.prototype = Object.create(
|
|
(HAS_EVENTTARGET_INTERFACE ? window.EventTarget : Object).prototype,
|
|
{
|
|
constructor: {
|
|
value: EventTarget,
|
|
writable: true,
|
|
configurable: true
|
|
},
|
|
|
|
addEventListener: {
|
|
value: function addEventListener(type, listener, capture) {
|
|
if (listener == null) {
|
|
return false;
|
|
}
|
|
if (typeof listener !== "function" && typeof listener !== "object") {
|
|
throw new TypeError("\"listener\" is not an object.");
|
|
}
|
|
|
|
var kind = (capture ? CAPTURE : BUBBLE);
|
|
var node = this[LISTENERS][type];
|
|
if (node == null) {
|
|
this[LISTENERS][type] = newNode(listener, kind);
|
|
return true;
|
|
}
|
|
|
|
var prev = null;
|
|
while (node != null) {
|
|
if (node.listener === listener && node.kind === kind) {
|
|
// Should ignore a duplicated listener.
|
|
return false;
|
|
}
|
|
prev = node;
|
|
node = node.next;
|
|
}
|
|
|
|
prev.next = newNode(listener, kind);
|
|
return true;
|
|
},
|
|
configurable: true,
|
|
writable: true
|
|
},
|
|
|
|
removeEventListener: {
|
|
value: function removeEventListener(type, listener, capture) {
|
|
if (listener == null) {
|
|
return false;
|
|
}
|
|
|
|
var kind = (capture ? CAPTURE : BUBBLE);
|
|
var prev = null;
|
|
var node = this[LISTENERS][type];
|
|
while (node != null) {
|
|
if (node.listener === listener && node.kind === kind) {
|
|
if (prev == null) {
|
|
this[LISTENERS][type] = node.next;
|
|
}
|
|
else {
|
|
prev.next = node.next;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
prev = node;
|
|
node = node.next;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
configurable: true,
|
|
writable: true
|
|
},
|
|
|
|
dispatchEvent: {
|
|
value: function dispatchEvent(event) {
|
|
// If listeners aren't registered, terminate.
|
|
var node = this[LISTENERS][event.type];
|
|
if (node == null) {
|
|
return true;
|
|
}
|
|
|
|
// Since we cannot rewrite several properties, so wrap object.
|
|
var wrapped = createEventWrapper(event, this);
|
|
|
|
// This doesn't process capturing phase and bubbling phase.
|
|
// This isn't participating in a tree.
|
|
while (node != null) {
|
|
if (typeof node.listener === "function") {
|
|
node.listener.call(this, wrapped);
|
|
}
|
|
else if (node.kind !== ATTRIBUTE && typeof node.listener.handleEvent === "function") {
|
|
node.listener.handleEvent(wrapped);
|
|
}
|
|
|
|
if (wrapped[STOP_IMMEDIATE_PROPAGATION_FLAG]) {
|
|
break;
|
|
}
|
|
node = node.next;
|
|
}
|
|
|
|
return !wrapped.defaultPrevented;
|
|
},
|
|
configurable: true,
|
|
writable: true
|
|
}
|
|
}
|
|
);
|