can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
446 lines (420 loc) • 13.6 kB
JavaScript
// # can/event
//
// Implements a basic event system that can be used with any type of object.
// In addition to adding basic event functionality, it also provides the `can.event` object
// that can be mixed into objects and prototypes.
//
// Most of the time when this is used, it will be used with the mixin:
//
// ```
// var SomeClass = can.Construct("SomeClass");
// can.extend(SomeClass.prototype, can.event);
// ```
steal('can/util/can.js', function (can) {
// ## can.event.addEvent
//
// Adds a basic event listener to an object.
// This consists of storing a cache of event listeners on each object,
// that are iterated through later when events are dispatched.
/**
* @function can.event.addEvent
* @parent can.event.static
* @signature `obj.addEvent( event, handler )`
*
* Add a basic event listener to an object.
*
* @param {String} event The name of the event to listen for.
* @param {Function} handler The handler that will be executed to handle the event.
* @return {Object} this
*
* @signature `can.event.addEvent.call( obj, event, handler )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
can.addEvent = function (event, handler) {
// Initialize event cache.
var allEvents = this.__bindEvents || (this.__bindEvents = {}),
eventList = allEvents[event] || (allEvents[event] = []);
// Add the event
eventList.push({
handler: handler,
name: event
});
return this;
};
// ## can.event.listenTo
//
// Listens to an event without know how bind is implemented.
// The primary use for this is to listen to another's objects event while
// tracking events on the local object (similar to namespacing).
//
// The API was heavily influenced by BackboneJS: http://backbonejs.org/
/**
* @function can.event.listenTo
* @parent can.event.static
* @signature `obj.listenTo( other, event, handler )`
*
* Listens for an event on another object.
* This is similar to concepts like event namespacing, except that the namespace
* is the scope of the calling object.
*
* @param {Object} other The object to listen for events on.
* @param {String} event The name of the event to listen for.
* @param {Function} handler The handler that will be executed to handle the event.
* @return {Object} this
*
* @signature `can.event.listenTo.call( obj, other, event, handler )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
can.listenTo = function (other, event, handler) {
// Initialize event cache
var idedEvents = this.__listenToEvents;
if (!idedEvents) {
idedEvents = this.__listenToEvents = {};
}
// Identify the other object
var otherId = can.cid(other);
var othersEvents = idedEvents[otherId];
// Create a local event cache
if (!othersEvents) {
othersEvents = idedEvents[otherId] = {
obj: other,
events: {}
};
}
var eventsEvents = othersEvents.events[event];
if (!eventsEvents) {
eventsEvents = othersEvents.events[event] = [];
}
// Add the event, both locally and to the other object
eventsEvents.push(handler);
can.bind.call(other, event, handler);
};
// ## can.event.stopListening
//
// Stops listening for events on other objects
/**
* @function can.event.stopListening
* @parent can.event.static
* @signature `obj.stopListening( other, event, handler )`
*
* Stops listening for an event on another object.
*
* @param {Object} other The object to listen for events on.
* @param {String} event The name of the event to listen for.
* @param {Function} handler The handler that will be executed to handle the event.
* @return {Object} this
*
* @signature `can.event.stopListening.call( obj, other, event, handler )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
can.stopListening = function (other, event, handler) {
var idedEvents = this.__listenToEvents,
iterIdedEvents = idedEvents,
i = 0;
if (!idedEvents) {
return this;
}
if (other) {
var othercid = can.cid(other);
(iterIdedEvents = {})[othercid] = idedEvents[othercid];
// you might be trying to listen to something that is not there
if (!idedEvents[othercid]) {
return this;
}
}
// Clean up events on the other object
for (var cid in iterIdedEvents) {
var othersEvents = iterIdedEvents[cid],
eventsEvents;
other = idedEvents[cid].obj;
// Find the cache of events
if (!event) {
eventsEvents = othersEvents.events;
} else {
(eventsEvents = {})[event] = othersEvents.events[event];
}
// Unbind event handlers, both locally and on the other object
for (var eventName in eventsEvents) {
var handlers = eventsEvents[eventName] || [];
i = 0;
while (i < handlers.length) {
if (handler && handler === handlers[i] || !handler) {
can.unbind.call(other, eventName, handlers[i]);
handlers.splice(i, 1);
} else {
i++;
}
}
// no more handlers?
if (!handlers.length) {
delete othersEvents.events[eventName];
}
}
if (can.isEmptyObject(othersEvents.events)) {
delete idedEvents[cid];
}
}
return this;
};
// ## can.event.removeEvent
//
// Removes a basic event listener from an object.
// This removes event handlers from the cache of listened events.
/**
* @function can.event.removeEvent
* @parent can.event.static
* @signature `obj.removeEvent( event, handler )`
*
* Removes a basic event listener from an object.
*
* @param {String} event The name of the event to listen for.
* @param {Function} handler The handler that will be executed to handle the event.
* @param {Function} [__validate] An extra function that can validate an event handler
* as a match. This is an internal parameter and only used
* for `can/event` plugins.
* @return {Object} this
*
* @signature `can.event.removeEvent.call( obj, event, handler )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
can.removeEvent = function (event, fn, __validate) {
if (!this.__bindEvents) {
return this;
}
var events = this.__bindEvents[event] || [],
i = 0,
ev, isFunction = typeof fn === 'function';
while (i < events.length) {
ev = events[i];
// Determine whether this event handler is "equivalent" to the one requested
// Generally this requires the same event/function, but a validation function
// can be included for extra conditions. This is used in some plugins like `can/event/namespace`.
if (__validate ? __validate(ev, event, fn) : isFunction && ev.handler === fn || !isFunction && (ev.cid === fn || !fn)) {
events.splice(i, 1);
} else {
i++;
}
}
return this;
};
// ## can.event.dispatch
//
// Dispatches/triggers a basic event on an object.
/**
* @function can.event.dispatch
* @parent can.event.static
* @signature `obj.dispatch( event, args )`
*
* Dispatches/triggers a basic event on an object.
*
* @param {String|Object} event The event to dispatch
* @param {Array} [args] Additional arguments to pass to event handlers
* @return {Object} event The resulting event object
*
* @signature `can.event.dispatch.call( obj, event, args )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
can.dispatch = function (event, args) {
var events = this.__bindEvents;
if (!events) {
return;
}
var eventName;
// Initialize the event object
if (typeof event === 'string') {
eventName = event;
event = {
type: event
};
} else {
eventName = event.type;
}
// Grab event listeners
var handlers = events[eventName];
if(!handlers) {
return;
} else {
handlers = handlers.slice(0);
}
var passed = [event];
// Execute handlers listening for this event.
if(args) {
passed.push.apply(passed, args);
}
for (var i = 0, len = handlers.length; i < len; i++) {
handlers[i].handler.apply(this, passed);
}
return event;
};
// ## can.event.one
//
// Adds a basic event listener that listens to an event once and only once.
/**
* @function can.event.one
* @parent can.event.static
* @signature `obj.one( event, handler )`
*
* Adds a basic event listener that listens to an event once and only once.
*
* @param {String} event The name of the event to listen for.
* @param {Function} handler The handler that will be executed to handle the event.
* @return {Object} this
*/
can.one = function(event, handler) {
// Unbind the listener after it has been executed
var one = function() {
can.unbind.call(this, event, one);
return handler.apply(this, arguments);
};
// Bind the altered listener
can.bind.call(this, event, one);
return this;
};
// ## can.event
// Create and export the `can.event` mixin
can.event = {
// Event method aliases
/**
* @function can.event.on
* @parent can.event.static
* @signature `obj.on( event, handler )`
*
* Add a basic event listener to an object.
*
* This is an alias of [can.event.addEvent addEvent].
*
* @signature `can.event.on.call( obj, event, handler )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
on: function() {
if (arguments.length === 0 && can.Control && this instanceof can.Control) {
return can.Control.prototype.on.call(this);
}
else {
return can.addEvent.apply(this, arguments);
}
},
/**
* @function can.event.off
* @parent can.event.static
* @signature `obj.off( event, handler )`
*
* Removes a basic event listener from an object.
*
* This is an alias of [can.event.removeEvent removeEvent].
*
* @signature `can.event.off.call( obj, event, handler )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
off: function() {
if (arguments.length === 0 && can.Control && this instanceof can.Control) {
return can.Control.prototype.off.call(this);
}
else {
return can.removeEvent.apply(this, arguments);
}
},
/**
* @function can.event.bind
* @parent can.event.static
* @signature `obj.bind( event, handler )`
*
* Add a basic event listener to an object.
*
* This is an alias of [can.event.addEvent addEvent].
*
* @signature `can.event.bind.call( obj, event, handler )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
bind: can.addEvent,
/**
* @function can.event.unbind
* @parent can.event.static
* @signature `obj.unbind( event, handler )`
*
* Removes a basic event listener from an object.
*
* This is an alias of [can.event.removeEvent removeEvent].
*
* @signature `can.event.unbind.call( obj, event, handler )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
unbind: can.removeEvent,
/**
* @function can.event.delegate
* @parent can.event.static
* @signature `obj.delegate( selector, event, handler )`
*
* Provides a compatibility layer for adding delegate event listeners.
* This doesn't actually implement delegates, but rather allows
* logic that assumes a delegate to still function.
*
* Therefore, this is essentially an alias of [can.event.addEvent addEvent] with the selector ignored.
*
* @param {String} selector The **ignored** selector to use for the delegate.
* @param {String} event The name of the event to listen for.
* @param {Function} handler The handler that will be executed to handle the event.
* @return {Object} this
*
* @signature `can.event.delegate.call( obj, selector, event, handler )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
delegate: function(selector, event, handler) {
return can.addEvent.call(this, event, handler);
},
/**
* @function can.event.undelegate
* @parent can.event.static
* @signature `obj.undelegate( selector, event, handler )`
*
* Provides a compatibility layer for removing delegate event listeners.
* This doesn't actually implement delegates, but rather allows
* logic that assumes a delegate to still function.
*
* Therefore, this is essentially an alias of [can.event.removeEvent removeEvent] with the selector ignored.
*
* @param {String} selector The **ignored** selector to use for the delegate.
* @param {String} event The name of the event to listen for.
* @param {Function} handler The handler that will be executed to handle the event.
* @return {Object} this
*
* @signature `can.event.undelegate.call( obj, selector, event, handler )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
undelegate: function(selector, event, handler) {
return can.removeEvent.call(this, event, handler);
},
/**
* @function can.event.trigger
* @parent can.event.static
* @signature `obj.trigger( event, args )`
*
* Dispatches/triggers a basic event on an object.
* This is an alias of [can.event.dispatch dispatch].
*
* @signature `can.event.trigger.call( obj, event, args )`
*
* This syntax can be used for objects that don't include the `can.event` mixin.
*/
trigger: can.dispatch,
// Normal can/event methods
one: can.one,
addEvent: can.addEvent,
removeEvent: can.removeEvent,
listenTo: can.listenTo,
stopListening: can.stopListening,
dispatch: can.dispatch
};
return can.event;
});