/**
* The CustomEvent class lets you define events for your application
* that can be subscribed to by one or more independent component.
*
* @param {String} type The type of event, which is passed to the callback
* when the event fires
* @param {Object} context The context the event will fire from. "this" will
* refer to this object in the callback. Default value:
* the window object. The listener can override this.
* @param {boolean} silent pass true to prevent the event from writing to
* the debugsystem
* @param {int} signature the signature that the custom event subscriber
* will receive. YAHOO.util.CustomEvent.LIST or
* YAHOO.util.CustomEvent.FLAT. The default is
* YAHOO.util.CustomEvent.LIST.
* @param fireOnce {boolean} If configured to fire once, the custom event
* will only notify subscribers a single time regardless of how many times
* the event is fired. In addition, new subscribers will be notified
* immediately if the event has already been fired.
* @namespace YAHOO.util
* @class CustomEvent
* @constructor
*/
YAHOO.util.CustomEvent = function(type, context, silent, signature, fireOnce) {
/**
* The type of event, returned to subscribers when the event fires
* @property type
* @type string
*/
this.type = type;
/**
* The context the event will fire from by default. Defaults to the window obj.
* @property scope
* @type object
*/
this.scope = context || window;
/**
* By default all custom events are logged in the debug build. Set silent to true
* to disable debug output for this event.
* @property silent
* @type boolean
*/
this.silent = silent;
/**
* If configured to fire once, the custom event will only notify subscribers
* a single time regardless of how many times the event is fired. In addition,
* new subscribers will be notified immediately if the event has already been
* fired.
* @property fireOnce
* @type boolean
* @default false
*/
this.fireOnce = fireOnce;
/**
* Indicates whether or not this event has ever been fired.
* @property fired
* @type boolean
* @default false
*/
this.fired = false;
/**
* For fireOnce events the arguments the event was fired with are stored
* so that new subscribers get the proper payload.
* @property firedWith
* @type Array
*/
this.firedWith = null;
/**
* Custom events support two styles of arguments provided to the event
* subscribers.
* <ul>
* <li>YAHOO.util.CustomEvent.LIST:
* <ul>
* <li>param1: event name</li>
* <li>param2: array of arguments sent to fire</li>
* <li>param3: <optional> a custom object supplied by the subscriber</li>
* </ul>
* </li>
* <li>YAHOO.util.CustomEvent.FLAT
* <ul>
* <li>param1: the first argument passed to fire. If you need to
* pass multiple parameters, use and array or object literal</li>
* <li>param2: <optional> a custom object supplied by the subscriber</li>
* </ul>
* </li>
* </ul>
* @property signature
* @type int
*/
this.signature = signature || YAHOO.util.CustomEvent.LIST;
/**
* The subscribers to this event
* @property subscribers
* @type Subscriber[]
*/
this.subscribers = [];
if (!this.silent) {
YAHOO.log( "Creating " + this, "info", "Event" );
}
var onsubscribeType = "_YUICEOnSubscribe";
// Only add subscribe events for events that are not generated by
// CustomEvent
if (type !== onsubscribeType) {
/**
* Custom events provide a custom event that fires whenever there is
* a new subscriber to the event. This provides an opportunity to
* handle the case where there is a non-repeating event that has
* already fired has a new subscriber.
*
* @event subscribeEvent
* @type YAHOO.util.CustomEvent
* @param fn {Function} The function to execute
* @param obj <Object> An object to be passed along when the event fires.
* Defaults to the custom event.
* @param override <boolean|Object> If true, the obj passed in becomes the
* execution context of the listener. If an object, that object becomes
* the execution context. Defaults to the custom event.
*/
this.subscribeEvent =
new YAHOO.util.CustomEvent(onsubscribeType, this, true);
}
/**
* In order to make it possible to execute the rest of the subscriber
* stack when one thows an exception, the subscribers exceptions are
* caught. The most recent exception is stored in this property
* @property lastError
* @type Error
*/
this.lastError = null;
};
/**
* Subscriber listener sigature constant. The LIST type returns three
* parameters: the event type, the array of args passed to fire, and
* the optional custom object
* @property YAHOO.util.CustomEvent.LIST
* @static
* @type int
*/
YAHOO.util.CustomEvent.LIST = 0;
/**
* Subscriber listener sigature constant. The FLAT type returns two
* parameters: the first argument passed to fire and the optional
* custom object
* @property YAHOO.util.CustomEvent.FLAT
* @static
* @type int
*/
YAHOO.util.CustomEvent.FLAT = 1;
YAHOO.util.CustomEvent.prototype = {
/**
* Subscribes the caller to this event
* @method subscribe
* @param {Function} fn The function to execute
* @param {Object} obj An object to be passed along when the event
* fires.
* @param {boolean|Object} overrideContext If true, the obj passed in
* becomes the execution.
* context of the listener. If an object, that object becomes the execution
* context.
*/
subscribe: function(fn, obj, overrideContext) {
if (!fn) {
throw new Error("Invalid callback for subscriber to '" + this.type + "'");
}
if (this.subscribeEvent) {
this.subscribeEvent.fire(fn, obj, overrideContext);
}
var s = new YAHOO.util.Subscriber(fn, obj, overrideContext);
if (this.fireOnce && this.fired) {
this.notify(s, this.firedWith);
} else {
this.subscribers.push(s);
}
},
/**
* Unsubscribes subscribers.
* @method unsubscribe
* @param {Function} fn The subscribed function to remove, if not supplied
* all will be removed
* @param {Object} obj The custom object passed to subscribe. This is
* optional, but if supplied will be used to
* disambiguate multiple listeners that are the same
* (e.g., you subscribe many object using a function
* that lives on the prototype)
* @return {boolean} True if the subscriber was found and detached.
*/
unsubscribe: function(fn, obj) {
if (!fn) {
return this.unsubscribeAll();
}
var found = false;
for (var i=0, len=this.subscribers.length; i<len; ++i) {
var s = this.subscribers[i];
if (s && s.contains(fn, obj)) {
this._delete(i);
found = true;
}
}
return found;
},
/**
* Notifies the subscribers. The callback functions will be executed
* from the context specified when the event was created, and with the
* following parameters:
* <ul>
* <li>The type of event</li>
* <li>All of the arguments fire() was executed with as an array</li>
* <li>The custom object (if any) that was passed into the subscribe()
* method</li>
* </ul>
* @method fire
* @param {Object*} arguments an arbitrary set of parameters to pass to
* the handler.
* @return {boolean} false if one of the subscribers returned false,
* true otherwise
*/
fire: function() {
this.lastError = null;
var errors = [],
len=this.subscribers.length;
var args=[].slice.call(arguments, 0), ret=true, i, rebuild=false;
if (this.fireOnce) {
if (this.fired) {
YAHOO.log('fireOnce event has already fired: ' + this.type);
return true;
} else {
this.firedWith = args;
}
}
this.fired = true;
if (!len && this.silent) {
//YAHOO.log('DEBUG no subscribers');
return true;
}
if (!this.silent) {
YAHOO.log( "Firing " + this + ", " +
"args: " + args + ", " +
"subscribers: " + len,
"info", "Event" );
}
// make a copy of the subscribers so that there are
// no index problems if one subscriber removes another.
var subs = this.subscribers.slice();
for (i=0; i<len; ++i) {
var s = subs[i];
if (!s || !s.fn) {
rebuild=true;
} else {
ret = this.notify(s, args);
if (false === ret) {
if (!this.silent) {
YAHOO.log("Event stopped, sub " + i + " of " + len, "info", "Event");
}
break;
}
}
}
return (ret !== false);
},
notify: function(s, args) {
var ret, param=null, scope = s.getScope(this.scope),
throwErrors = YAHOO.util.Event.throwErrors;
if (!this.silent) {
YAHOO.log( this.type + "-> " + s, "info", "Event" );
}
if (this.signature == YAHOO.util.CustomEvent.FLAT) {
if (args.length > 0) {
param = args[0];
}
try {
ret = s.fn.call(scope, param, s.obj);
} catch(e) {
this.lastError = e;
// errors.push(e);
YAHOO.log(this + " subscriber exception: " + e, "error", "Event");
if (throwErrors) {
throw e;
}
}
} else {
try {
ret = s.fn.call(scope, this.type, args, s.obj);
} catch(ex) {
this.lastError = ex;
YAHOO.log(this + " subscriber exception: " + ex, "error", "Event");
if (throwErrors) {
throw ex;
}
}
}
return ret;
},
/**
* Removes all listeners
* @method unsubscribeAll
* @return {int} The number of listeners unsubscribed
*/
unsubscribeAll: function() {
var l = this.subscribers.length, i;
for (i=l-1; i>-1; i--) {
this._delete(i);
}
this.subscribers=[];
return l;
},
/**
* @method _delete
* @private
*/
_delete: function(index) {
var s = this.subscribers[index];
if (s) {
delete s.fn;
delete s.obj;
}
// this.subscribers[index]=null;
this.subscribers.splice(index, 1);
},
/**
* @method toString
*/
toString: function() {
return "CustomEvent: " + "'" + this.type + "', " +
"context: " + this.scope;
}
};
/////////////////////////////////////////////////////////////////////
/**
* Stores the subscriber information to be used when the event fires.
* @param {Function} fn The function to execute
* @param {Object} obj An object to be passed along when the event fires
* @param {boolean} overrideContext If true, the obj passed in becomes the execution
* context of the listener
* @class Subscriber
* @constructor
*/
YAHOO.util.Subscriber = function(fn, obj, overrideContext) {
/**
* The callback that will be execute when the event fires
* @property fn
* @type function
*/
this.fn = fn;
/**
* An optional custom object that will passed to the callback when
* the event fires
* @property obj
* @type object
*/
this.obj = YAHOO.lang.isUndefined(obj) ? null : obj;
/**
* The default execution context for the event listener is defined when the
* event is created (usually the object which contains the event).
* By setting overrideContext to true, the execution context becomes the custom
* object passed in by the subscriber. If overrideContext is an object, that
* object becomes the context.
* @property overrideContext
* @type boolean|object
*/
this.overrideContext = overrideContext;
};
/**
* Returns the execution context for this listener. If overrideContext was set to true
* the custom obj will be the context. If overrideContext is an object, that is the
* context, otherwise the default context will be used.
* @method getScope
* @param {Object} defaultScope the context to use if this listener does not
* override it.
*/
YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
if (this.overrideContext) {
if (this.overrideContext === true) {
return this.obj;
} else {
return this.overrideContext;
}
}
return defaultScope;
};
/**
* Returns true if the fn and obj match this objects properties.
* Used by the unsubscribe method to match the right subscriber.
*
* @method contains
* @param {Function} fn the function to execute
* @param {Object} obj an object to be passed along when the event fires
* @return {boolean} true if the supplied arguments match this
* subscriber's signature.
*/
YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {
if (obj) {
return (this.fn == fn && this.obj == obj);
} else {
return (this.fn == fn);
}
};
/**
* @method toString
*/
YAHOO.util.Subscriber.prototype.toString = function() {
return "Subscriber { obj: " + this.obj +
", overrideContext: " + (this.overrideContext || "no") + " }";
};