Permalink
Browse files

event dispatcher

  • Loading branch information...
1 parent b09e25e commit 431b8987414e0b6d97a4c9af23925fe956cb6a58 @jpolo committed Jun 17, 2012
Showing with 532 additions and 1 deletion.
  1. +342 −0 event.js
  2. +1 −0 spec/all.js
  3. +188 −0 spec/event_spec.js
  4. +1 −1 spec/hash_spec.js
View
@@ -0,0 +1,342 @@
+/*jslint indent:2, plusplus:false */
+/*global define */
+/*global stj, module_, class_, use_, def_, keyword_, feature_, trait_, mixin_ */
+define("stj/event", [ "stj/class", "stj/validate" ], function () {
+ "use strict";
+
+ module_("event", function ($module) {
+
+ class_("Dispatcher", Object, function ($class) {
+ var
+ slice = Array.prototype.slice,
+ getEvent = function getEvent(self, type) {
+ var
+ dispatcher = self.dispatcher,
+ listeners = dispatcher[type];
+ if (!listeners) {
+ listeners = dispatcher[type] = [];
+ }
+ return listeners;
+ },
+ validate = use_("validate"),
+ validateInstanceOf = validate.isInstanceOf;
+
+ /**
+ *
+ */
+ def_("LIMIT", {
+ value: 10,
+ self: true
+ });
+
+ /**
+ * @constructor
+ * @param {Object=} config
+ * - limit : number of listeners allowed for one event
+ * - strict: error will be thrown if event is not defined with defineEvent
+ * - thisp: default object bound to listeners
+ */
+ def_("initialize", {
+ value: function initialize(config) {
+ this.dispatcher = {};
+ this.limit = $class.LIMIT;
+ this.strict = false;
+ this.thisp = this;
+ if (config) {
+ this.configure(config);
+ }
+ }
+ });
+
+ /**
+ * Destroy the dispatcher
+ */
+ def_("finalize", {
+ value: function finalize() {
+ this.dispatcher = null;
+ }
+ });
+
+ /**
+ * Configure the dispatcher
+ *
+ * @param {Object=} config
+ * @return this
+ */
+ def_("configure", {
+ value: function configure(config) {
+ this.defineEvent('addListener');
+ if (config) {
+ if (typeof config.limit !== "undefined") {
+ this.limit = validateInstanceOf(config.limit, Number);
+ }
+ if (typeof config.strict !== "undefined") {
+ this.strict = !!config.strict;
+ }
+ if (typeof config.thisp !== "undefined") {
+ this.thisp = config.thisp;
+ }
+ }
+ return this;
+ }
+ });
+
+ /**
+ * Add a new event type and return the array of listeners
+ *
+ * @param {String*}
+ * @return {Array}
+ */
+ def_("defineEvent", {
+ value: function defineEvent(type) {
+ var n = arguments.length - 1;
+ if (n >= 0) {
+ getEvent(this, arguments[n]);
+ n -= 1;
+ }
+ return this;
+ }
+ });
+
+ /**
+ * Emit the event `type`
+ *
+ * @param {string} type
+ * @return {boolean} true if one ore more listener found
+ */
+ def_("emit", {
+ value: function emit(type) {
+ var
+ args, useCall,
+ listeners = this.dispatcher[type], thisp,
+ listenersLength, i = 0,
+ handler, listener;
+
+ if (listeners) {
+ useCall = arguments.length <= 3;
+ args = useCall ? arguments : slice.call(arguments, 1);
+ thisp = this.thisp;
+
+ //listeners = slice(listeners);
+ listenersLength = listeners.length;
+
+ while (i < listenersLength) {
+ handler = listeners[i];
+ listener = handler[0];
+
+ //If once remove it
+ if (handler[1]) { //once test
+ listeners.splice(i, 1);
+ }
+
+ // fast cases
+ if (useCall) {
+ listener.call(thisp, args[1], args[2]);
+
+ // slower
+ } else {
+ listener.apply(thisp, args);
+ }
+ i += 1;
+ }
+ return true;
+ } else if (this.strict) {
+ eventNotDefined(type);
+ }
+ return false;
+ }
+ });
+
+ /**
+ * @param {String} type
+ * @return {Array}
+ */
+ def_("getListeners", {
+ value: function getListeners(type) {
+ var listeners = this.dispatcher[type], result, n;
+
+ if (listeners) {
+ n = listeners.length;
+ result = [];
+ while (n--) {
+ result.push(listeners[n][0]);
+ }
+ return result;
+ }
+ }
+ });
+
+ /**
+ * Listen to event named `type`
+ *
+ * @param {string} type
+ * @param {Function} listener
+ * @param {boolean=} _once optional listening once option
+ * @return this
+ */
+ def_("addListener", {
+ value: function addListener(type, listener/*, _once*/) {
+ validateInstanceOf(listener, Function);
+
+ var
+ dispatcher = this.dispatcher,
+ //limit = this.limit,
+ listeners = dispatcher[type],
+ _once = !!arguments[2];
+
+ if (!listeners) {
+ if (this.strict) {
+ eventNotDefined(type);
+ }
+ listeners = getEvent(this, type);
+ }
+
+ // To avoid recursion in the case that type == "newListeners"! Before
+ // adding it to the listeners, first emit "addListener".
+ this.emit('addListener', type, listener);
+
+ // Check for listener leak
+ /*if (!listeners.warned) {
+
+ if (limit && listeners.length > listenerMax) {
+ listeners.warned = true;
+ console.error('(node) warning: possible EventEmitter memory ' +
+ 'leak detected. %d listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.',
+ listeners.length);
+ console.trace();
+ }
+ }*/
+
+ // If we've already got an array, just append.
+ listeners.push([listener, _once]);
+ return this;
+ }
+ });
+
+ /**
+ * Remove `listener` if listening to `type`
+ *
+ * @param {string} type
+ * @param {Function} listener
+ * @return {Function=} the listener if found
+ */
+ def_("removeListener", {
+ value: function removeListener(type, listener) {
+ validateInstanceOf(listener, Function);
+ var
+ listeners = this.dispatcher[type],
+ i;
+ if (listeners) {
+ i = listeners.length - 1;
+ while (i >= 0) {
+ if (listeners[i][0] === listener) {
+ listeners.splice(i, 1);
+ return listener;
+ }
+ i -= 1;
+ }
+ } else if (this.strict) {
+ eventNotDefined(type);
+ }
+ }
+ });
+
+ /**
+ * Remove all listeners for events `type`
+ *
+ * @param {string} type
+ */
+ def_("removeAllListeners", {
+ value: function removeAllListeners(type) {
+ var
+ dispatcher = this.dispatcher,
+ eventName;
+ if (!type) {
+ for (eventName in dispatcher) {
+ if (dispatcher.hasOwnProperty(eventName)) {
+ dispatcher[eventName] = [];
+ }
+ }
+ } else if (dispatcher) {
+ dispatcher[type] = [];
+ }
+ return this;
+ }
+ });
+ });
+ });
+
+ trait_("TListenable", function ($trait) {
+
+ def_("emit", {
+ value: function emit(type) {
+ return this.eventDispatcher().emit(type);
+ }
+ });
+
+ def_("on", {
+ value: function on(type, listener) {
+ this.eventDispatcher().addListener(type, listener, false);
+ return this;
+ }
+ });
+
+ def_("once", {
+ value: function once(type, listener) {
+ this.eventDispatcher().addListener(type, listener, true);
+ return this;
+ }
+ });
+
+ def_("getListeners", {
+ value: function getListeners(type) {
+ return this.eventDispatcher().getListeners(type);
+ }
+ });
+
+ def_("addListener", {
+ value: function addListener(type, listener, _once) {
+ this.eventDispatcher().addListener(type, listener, _once);
+ return this;
+ }
+ });
+
+ def_("removeListener", {
+ value: function removeListener(type, listener) {
+ return this.eventDispatcher().removeListener(type, listener);
+ }
+ });
+
+ def_("removeAllListeners", {
+ value: function removeAllListeners(type) {
+ this.eventDispatcher().removeAllListeners(type);
+ return this;
+ }
+ });
+
+ def_("eventDispatcher", {
+ value: function eventDispatcher() {
+ var
+ dispatcher = this.__dispatcher__,
+ DispatcherClass,
+ config;
+ if (!dispatcher) {
+ config = validateObject(this._getDispatcherConfig() || {});
+ config.thisp = config.thisp || this;
+ DispatcherClass = use_(config["class"] || 'event.Dispatcher');
+ dispatcher = this.__dispatcher__ = new DispatcherClass(config);
+ }
+ return dispatcher;
+ }
+ });
+
+ // -"class"
+ // - strict
+ // - limit
+ // - thisp
+ /*_getDispatcherConfig: function _getDispatcherConfig() {
+ //override this method
+ }*/
+ });
+});
View
@@ -12,6 +12,7 @@ define("stj/spec/all",
"stj/spec/enumerable_spec",
"stj/spec/es5_spec",
"stj/spec/es6_spec",
+ "stj/spec/event_spec",
"stj/spec/feature_spec",
"stj/spec/hash_spec",
"stj/spec/js_spec",
Oops, something went wrong.

0 comments on commit 431b898

Please sign in to comment.