/** * @ignore * custom event target for publish and subscribe * @author yiminghe@gmail.com */ KISSY.add('event/custom/target', function (S, BaseEvent, CustomEventObservable) { var Utils = BaseEvent.Utils, splitAndRun = Utils.splitAndRun, undefined = undefined, KS_BUBBLE_TARGETS = '__~ks_bubble_targets'; /** * EventTarget provides the implementation for any object to publish, subscribe and fire to custom events, * and also allows other EventTargets to target the object with events sourced from the other object. * * EventTarget is designed to be used with S.augment to allow events to be listened to and fired by name. * * This makes it possible for implementing code to subscribe to an event that either has not been created yet, * or will not be created at all. * * * * @example * KISSY.use('event',function(S,Event){ * var target = S.mix({}, Event.Target); * target.on('ok',function(){ * document.writeln('ok fired @'+new Date()); * }); * target.fire('ok'); * }); * * * @class KISSY.Event.CustomEvent.Target */ function Target() { } var KS_CUSTOM_EVENTS = '__~ks_custom_events'; Target.prototype = { constructor: Target, isTarget: 1, /** * Get custom event for specified event * @private * @param {String} type event type * @param {Boolean} [create] whether create custom event on fly * @return {KISSY.Event.CustomEvent.CustomEventObservable} */ getCustomEventObservable: function (type, create) { var target = this, customEvent, customEventObservables = target.getCustomEvents(); customEvent = customEventObservables && customEventObservables[type]; if (!customEvent && create) { customEvent = customEventObservables[type] = new CustomEventObservable({ currentTarget: target, type: type }); } return customEvent; }, /** * Fire a custom event by name. * The callback functions will be executed from the context specified when the event was created, * and the {@link KISSY.Event.CustomEvent.Object} created will be mixed with eventData * @method fire * @param {String} type The type of the event * @param {Object} [eventData] The data will be mixed with {@link KISSY.Event.CustomEvent.Object} created * @return {*} If any listen returns false, then the returned value is false. else return the last listener's returned value */ fire: function (type, eventData) { var self = this, ret = undefined, targets = self.getTargets(), hasTargets = targets && targets.length; eventData = eventData || {}; splitAndRun(type, function (type) { var r2, customEventObservable; Utils.fillGroupsForEvent(type, eventData); type = eventData.type; // default bubble true // if bubble false, it must has customEvent structure set already customEventObservable = self.getCustomEventObservable(type); // optimize performance for empty event listener if (!customEventObservable && !hasTargets) { return; } if (customEventObservable) { if (!customEventObservable.hasObserver() && !customEventObservable.defaultFn) { if (customEventObservable.bubbles && !hasTargets || !customEventObservable.bubbles) { return; } } } else { // in case no publish custom event but we need bubble // because bubbles defaults to true! customEventObservable = new CustomEventObservable({ currentTarget: self, type: type }); } r2 = customEventObservable.fire(eventData); if (ret !== false && r2 !== undefined) { ret = r2; } }); return ret; }, /** * Creates a new custom event of the specified type * @method publish * @param {String} type The type of the event * @param {Object} cfg Config params * @param {Boolean} [cfg.bubbles=true] whether or not this event bubbles * @param {Function} [cfg.defaultFn] this event's default action * @chainable */ publish: function (type, cfg) { var customEventObservable, self = this; splitAndRun(type, function (t) { customEventObservable = self.getCustomEventObservable(t, true); S.mix(customEventObservable, cfg) }); return self; }, /** * Registers another EventTarget as a bubble target. * @method addTarget * @param {KISSY.Event.CustomEvent.Target} anotherTarget Another EventTarget instance to add * @chainable */ addTarget: function (anotherTarget) { var self = this, targets = self.getTargets(); if (!S.inArray(anotherTarget, targets)) { targets.push(anotherTarget); } return self; }, /** * Removes a bubble target * @method removeTarget * @param {KISSY.Event.CustomEvent.Target} anotherTarget Another EventTarget instance to remove * @chainable */ removeTarget: function (anotherTarget) { var self = this, targets = self.getTargets(), index = S.indexOf(anotherTarget, targets); if (index != -1) { targets.splice(index, 1); } return self; }, /** * all targets where current target's events bubble to * @private * @return {KISSY.Event.CustomEvent.Target[]} */ getTargets: function () { return this[KS_BUBBLE_TARGETS]||(this[KS_BUBBLE_TARGETS]=[]); }, getCustomEvents:function(){ return this[KS_CUSTOM_EVENTS]||(this[KS_CUSTOM_EVENTS]={}); }, /** * Subscribe a callback function to a custom event fired by this object or from an object that bubbles its events to this object. * @method on * @param {String} type The name of the event * @param {Function} fn The callback to execute in response to the event * @param {Object} [context] this object in callback * @chainable */ on: function (type, fn, context) { var self = this; Utils.batchForType(function (type, fn, context) { var cfg = Utils.normalizeParam(type, fn, context), customEvent; type = cfg.type; customEvent = self.getCustomEventObservable(type, true); if (customEvent) { customEvent.on(cfg); } }, 0, type, fn, context); return self; // chain }, /** * Detach one or more listeners from the specified event * @method detach * @param {String} type The name of the event * @param {Function} [fn] The subscribed function to un-subscribe. if not supplied, all observers will be removed. * @param {Object} [context] The custom object passed to subscribe. * @chainable */ detach: function (type, fn, context) { var self = this; Utils.batchForType(function (type, fn, context) { var cfg = Utils.normalizeParam(type, fn, context), customEvents, customEvent; type = cfg.type; if (type) { customEvent = self.getCustomEventObservable(type, true); if (customEvent) { customEvent.detach(cfg); } } else { customEvents = self.getCustomEvents(); S.each(customEvents, function (customEvent) { customEvent.detach(cfg); }); } }, 0, type, fn, context); return self; // chain } }; return Target; }, { requires: ['event/base', './observable'] }); /* yiminghe: 2012-10-24 - implement defaultFn for custom event yiminghe: 2011-10-17 - implement bubble for custom event */