/**
* 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} oScope 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.
* @namespace YAHOO.util
* @class CustomEvent
* @constructor
*/
YAHOO.util.CustomEvent = function(type, oScope, silent, signature) {
/**
* The type of event, returned to subscribers when the event fires
* @property type
* @type string
*/
this.type = type;
/**
* The scope the the event will fire from by default. Defaults to the window
* obj
* @property scope
* @type object
*/
this.scope = oScope || window;
/**
* By default all custom events are logged in the debug build, set silent
* to true to disable debug outpu for this event.
* @property silent
* @type boolean
*/
this.silent = silent;
/**
* 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 {Function} fn The function to execute
* @param {Object} obj An object to be passed along when the event
* fires
* @param {boolean|Object} override If true, the obj passed in becomes
* the execution scope of the listener.
* if an object, that object becomes the
* the execution scope.
*/
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} override If true, the obj passed in becomes
* the execution scope of the listener.
* if an object, that object becomes the
* the execution scope.
*/
subscribe: function(fn, obj, override) {
if (!fn) {
throw new Error("Invalid callback for subscriber to '" + this.type + "'");
}
if (this.subscribeEvent) {
this.subscribeEvent.fire(fn, obj, override);
}
this.subscribers.push( new YAHOO.util.Subscriber(fn, obj, override) );
},
/**
* 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 scope 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() {
var len=this.subscribers.length;
if (!len && this.silent) {
//YAHOO.log('DEBUG no subscribers');
return true;
}
var args=[].slice.call(arguments, 0), ret=true, i, rebuild=false;
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) {
//YAHOO.log('DEBUG rebuilding array');
rebuild=true;
} else {
if (!this.silent) {
YAHOO.log( this.type + "->" + (i+1) + ": " + s, "info", "Event" );
}
var scope = s.getScope(this.scope);
if (this.signature == YAHOO.util.CustomEvent.FLAT) {
var param = null;
if (args.length > 0) {
param = args[0];
}
try {
ret = s.fn.call(scope, param, s.obj);
} catch(e) {
this.lastError = e;
YAHOO.log(this + " subscriber exception: " + e, "error", "Event");
}
} 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 (false === ret) {
if (!this.silent) {
YAHOO.log("Event stopped, sub " + i + " of " + len, "info", "Event");
}
//break;
return false;
}
}
}
// if (rebuild) {
// var newlist=this.,subs=this.subscribers;
// for (i=0,len=subs.length; i<len; i=i+1) {
// // this wasn't doing anything before
// newlist.push(subs[i]);
// }
// this.subscribers=newlist;
// }
return true;
},
/**
* Removes all listeners
* @method unsubscribeAll
* @return {int} The number of listeners unsubscribed
*/
unsubscribeAll: function() {
for (var i=this.subscribers.length-1; i>-1; i--) {
this._delete(i);
}
this.subscribers=[];
return i;
},
/**
* @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 + "', " +
"scope: " + 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} override If true, the obj passed in becomes the execution
* scope of the listener
* @class Subscriber
* @constructor
*/
YAHOO.util.Subscriber = function(fn, obj, override) {
/**
* 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 scope for the event listener is defined when the
* event is created (usually the object which contains the event).
* By setting override to true, the execution scope becomes the custom
* object passed in by the subscriber. If override is an object, that
* object becomes the scope.
* @property override
* @type boolean|object
*/
this.override = override;
};
/**
* Returns the execution scope for this listener. If override was set to true
* the custom obj will be the scope. If override is an object, that is the
* scope, otherwise the default scope will be used.
* @method getScope
* @param {Object} defaultScope the scope to use if this listener does not
* override it.
*/
YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
if (this.override) {
if (this.override === true) {
return this.obj;
} else {
return this.override;
}
}
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 +
", override: " + (this.override || "no") + " }";
};