1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/event/core.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,172 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +module.metadata = { 1.11 + "stability": "unstable" 1.12 +}; 1.13 + 1.14 +const UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.'; 1.15 +const BAD_LISTENER = 'The event listener must be a function.'; 1.16 + 1.17 +const { ns } = require('../core/namespace'); 1.18 + 1.19 +const event = ns(); 1.20 + 1.21 +const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/; 1.22 +exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN; 1.23 + 1.24 +// Utility function to access given event `target` object's event listeners for 1.25 +// the specific event `type`. If listeners for this type does not exists they 1.26 +// will be created. 1.27 +const observers = function observers(target, type) { 1.28 + if (!target) throw TypeError("Event target must be an object"); 1.29 + let listeners = event(target); 1.30 + return type in listeners ? listeners[type] : listeners[type] = []; 1.31 +}; 1.32 + 1.33 +/** 1.34 + * Registers an event `listener` that is called every time events of 1.35 + * specified `type` is emitted on the given event `target`. 1.36 + * @param {Object} target 1.37 + * Event target object. 1.38 + * @param {String} type 1.39 + * The type of event. 1.40 + * @param {Function} listener 1.41 + * The listener function that processes the event. 1.42 + */ 1.43 +function on(target, type, listener) { 1.44 + if (typeof(listener) !== 'function') 1.45 + throw new Error(BAD_LISTENER); 1.46 + 1.47 + let listeners = observers(target, type); 1.48 + if (!~listeners.indexOf(listener)) 1.49 + listeners.push(listener); 1.50 +} 1.51 +exports.on = on; 1.52 + 1.53 +/** 1.54 + * Registers an event `listener` that is called only the next time an event 1.55 + * of the specified `type` is emitted on the given event `target`. 1.56 + * @param {Object} target 1.57 + * Event target object. 1.58 + * @param {String} type 1.59 + * The type of the event. 1.60 + * @param {Function} listener 1.61 + * The listener function that processes the event. 1.62 + */ 1.63 +function once(target, type, listener) { 1.64 + on(target, type, function observer(...args) { 1.65 + off(target, type, observer); 1.66 + listener.apply(target, args); 1.67 + }); 1.68 +} 1.69 +exports.once = once; 1.70 + 1.71 +/** 1.72 + * Execute each of the listeners in order with the supplied arguments. 1.73 + * All the exceptions that are thrown by listeners during the emit 1.74 + * are caught and can be handled by listeners of 'error' event. Thrown 1.75 + * exceptions are passed as an argument to an 'error' event listener. 1.76 + * If no 'error' listener is registered exception will be logged into an 1.77 + * error console. 1.78 + * @param {Object} target 1.79 + * Event target object. 1.80 + * @param {String} type 1.81 + * The type of event. 1.82 + * @params {Object|Number|String|Boolean} args 1.83 + * Arguments that will be passed to listeners. 1.84 + */ 1.85 +function emit (target, type, ...args) { 1.86 + let state = observers(target, type); 1.87 + let listeners = state.slice(); 1.88 + let count = listeners.length; 1.89 + let index = 0; 1.90 + 1.91 + // If error event and there are no handlers then print error message 1.92 + // into a console. 1.93 + if (count === 0 && type === 'error') console.exception(args[0]); 1.94 + while (index < count) { 1.95 + try { 1.96 + let listener = listeners[index]; 1.97 + // Dispatch only if listener is still registered. 1.98 + if (~state.indexOf(listener)) 1.99 + listener.apply(target, args); 1.100 + } 1.101 + catch (error) { 1.102 + // If exception is not thrown by a error listener and error listener is 1.103 + // registered emit `error` event. Otherwise dump exception to the console. 1.104 + if (type !== 'error') emit(target, 'error', error); 1.105 + else console.exception(error); 1.106 + } 1.107 + index++; 1.108 + } 1.109 + // Also emit on `"*"` so that one could listen for all events. 1.110 + if (type !== '*') emit(target, '*', type, ...args); 1.111 +} 1.112 +exports.emit = emit; 1.113 + 1.114 +/** 1.115 + * Removes an event `listener` for the given event `type` on the given event 1.116 + * `target`. If no `listener` is passed removes all listeners of the given 1.117 + * `type`. If `type` is not passed removes all the listeners of the given 1.118 + * event `target`. 1.119 + * @param {Object} target 1.120 + * The event target object. 1.121 + * @param {String} type 1.122 + * The type of event. 1.123 + * @param {Function} listener 1.124 + * The listener function that processes the event. 1.125 + */ 1.126 +function off(target, type, listener) { 1.127 + let length = arguments.length; 1.128 + if (length === 3) { 1.129 + let listeners = observers(target, type); 1.130 + let index = listeners.indexOf(listener); 1.131 + if (~index) 1.132 + listeners.splice(index, 1); 1.133 + } 1.134 + else if (length === 2) { 1.135 + observers(target, type).splice(0); 1.136 + } 1.137 + else if (length === 1) { 1.138 + let listeners = event(target); 1.139 + Object.keys(listeners).forEach(type => delete listeners[type]); 1.140 + } 1.141 +} 1.142 +exports.off = off; 1.143 + 1.144 +/** 1.145 + * Returns a number of event listeners registered for the given event `type` 1.146 + * on the given event `target`. 1.147 + */ 1.148 +function count(target, type) { 1.149 + return observers(target, type).length; 1.150 +} 1.151 +exports.count = count; 1.152 + 1.153 +/** 1.154 + * Registers listeners on the given event `target` from the given `listeners` 1.155 + * dictionary. Iterates over the listeners and if property name matches name 1.156 + * pattern `onEventType` and property is a function, then registers it as 1.157 + * an `eventType` listener on `target`. 1.158 + * 1.159 + * @param {Object} target 1.160 + * The type of event. 1.161 + * @param {Object} listeners 1.162 + * Dictionary of listeners. 1.163 + */ 1.164 +function setListeners(target, listeners) { 1.165 + Object.keys(listeners || {}).forEach(key => { 1.166 + let match = EVENT_TYPE_PATTERN.exec(key); 1.167 + let type = match && match[1].toLowerCase(); 1.168 + if (!type) return; 1.169 + 1.170 + let listener = listeners[key]; 1.171 + if (typeof(listener) === 'function') 1.172 + on(target, type, listener); 1.173 + }); 1.174 +} 1.175 +exports.setListeners = setListeners;