mirror of
https://github.com/jashkenas/backbone.git
synced 2026-04-30 03:00:06 -04:00
Rearrange and comment Events
This commit is contained in:
245
backbone.js
245
backbone.js
@@ -76,127 +76,110 @@
|
||||
// object.on('expand', function(){ alert('expanded'); });
|
||||
// object.trigger('expand');
|
||||
//
|
||||
var Events = Backbone.Events = {
|
||||
|
||||
// Bind an event to a `callback` function. Passing `"all"` will bind
|
||||
// the callback to all events fired.
|
||||
on: function(name, callback, context) {
|
||||
this._events = eventsApi(onApi, this._events || {}, name, callback, context, this);
|
||||
return this;
|
||||
},
|
||||
|
||||
// Bind an event to only be triggered a single time. After the first time
|
||||
// the callback is invoked, it will be removed.
|
||||
once: function(name, callback, context) {
|
||||
name = onceMap(name, callback, _.bind(this.off, this));
|
||||
return this.on(name, callback, context);
|
||||
},
|
||||
|
||||
// Remove one or many callbacks. If `context` is null, removes all
|
||||
// callbacks with that function. If `callback` is null, removes all
|
||||
// callbacks for the event. If `name` is null, removes all bound
|
||||
// callbacks for all events.
|
||||
off: function(name, callback, context) {
|
||||
if (!this._events) return this;
|
||||
this._events = eventsApi(offApi, this._events, name, callback, context);
|
||||
|
||||
var listeners = this._listeners;
|
||||
if (listeners) {
|
||||
var ids = context != null ? [context._listenId] : _.keys(listeners);
|
||||
for (var i = 0, length = ids.length; i < length; i++) {
|
||||
var listener = listeners[ids[i]];
|
||||
if (!listener) break;
|
||||
internalStopListening(listener, this, name, callback);
|
||||
}
|
||||
if (_.isEmpty(listeners)) this._listeners = void 0;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// Trigger one or many events, firing all bound callbacks. Callbacks are
|
||||
// passed the same arguments as `trigger` is, apart from the event name
|
||||
// (unless you're listening on `"all"`, which will cause your callback to
|
||||
// receive the true name of the event as the first argument).
|
||||
trigger: function(name) {
|
||||
if (!this._events) return this;
|
||||
var args = slice.call(arguments, 1);
|
||||
|
||||
// Use `eventsApi` to normalize `name` into the proper event names.
|
||||
eventsApi(triggerApi, this, name, triggerSentinel, args);
|
||||
return this;
|
||||
},
|
||||
|
||||
// Inversion-of-control versions of `on` and `once`. Tell *this* object to
|
||||
// listen to an event in another object ... keeping track of what it's
|
||||
// listening to.
|
||||
listenTo: function(obj, name, callback) {
|
||||
if (!obj) return this;
|
||||
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
|
||||
var listeningTo = this._listeningTo || (this._listeningTo = {});
|
||||
var listening = listeningTo[id];
|
||||
|
||||
if (!listening) {
|
||||
listening = listeningTo[id] = {obj: obj, events: {}};
|
||||
id = this._listenId || (this._listenId = _.uniqueId('l'));
|
||||
var listeners = obj._listeners || (obj._listeners = {});
|
||||
listeners[id] = this;
|
||||
}
|
||||
|
||||
// Bind callbacks on obj, and keep track of them on listening.
|
||||
obj.on(name, callback, this);
|
||||
listening.events = eventsApi(onApi, listening.events, name, callback);
|
||||
return this;
|
||||
},
|
||||
|
||||
listenToOnce: function(obj, name, callback) {
|
||||
name = onceMap(name, callback, _.bind(this.stopListening, this, obj));
|
||||
return this.listenTo(obj, name, callback);
|
||||
},
|
||||
|
||||
// Tell this object to stop listening to either specific events ... or
|
||||
// to every object it's currently listening to.
|
||||
stopListening: function(obj, name, callback) {
|
||||
if (this._listeningTo) internalStopListening(this, obj, name, callback, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
var Events = Backbone.Events = {};
|
||||
|
||||
// Regular expression used to split event strings.
|
||||
var eventSplitter = /\s+/;
|
||||
|
||||
// Implement fancy features of the Events API such as multiple event
|
||||
// names `"change blur"` and jQuery-style event maps `{change: action}`
|
||||
// in terms of the existing API. Consider this just a special purpose
|
||||
// reduce function.
|
||||
var eventsApi = function(api, events, name, callback, context, ctx) {
|
||||
// Iterates over the standard `event, callback` (as well as the fancy multiple
|
||||
// space-separated events `"change blur", callback` and jQuery-style event
|
||||
// maps `{event: callback}`), reducing them by manipulating `events`.
|
||||
// Passes a normalized (single event name and callback), as well as the `context`
|
||||
// and `ctx` arguments to `iteratee`.
|
||||
var eventsApi = function(iteratee, memo, name, callback, context, ctx) {
|
||||
var i = 0, names, length;
|
||||
if (name && typeof name === 'object') {
|
||||
// Handle event maps.
|
||||
for (names = _.keys(name), length = names.length; i < length; i++) {
|
||||
events = api(events, names[i], name[names[i]], context, ctx);
|
||||
memo = iteratee(memo, names[i], name[names[i]], context, ctx);
|
||||
}
|
||||
} else if (name && eventSplitter.test(name)) {
|
||||
// Handle space separated event names.
|
||||
for (names = name.split(eventSplitter), length = names.length; i < length; i++) {
|
||||
events = api(events, names[i], callback, context, ctx);
|
||||
memo = iteratee(memo, names[i], callback, context, ctx);
|
||||
}
|
||||
} else {
|
||||
events = api(events, name, callback, context, ctx);
|
||||
memo = iteratee(memo, name, callback, context, ctx);
|
||||
}
|
||||
return events;
|
||||
return memo;
|
||||
};
|
||||
|
||||
// Handles actually adding the event handler.
|
||||
// Bind an event to a `callback` function. Passing `"all"` will bind
|
||||
// the callback to all events fired.
|
||||
Events.on = function(name, callback, context) {
|
||||
this._events = eventsApi(onApi, this._events || {}, name, callback, context, this);
|
||||
return this;
|
||||
};
|
||||
|
||||
// Inversion-of-control versions of `on`. Tell *this* object to listen to
|
||||
// an event in another object... keeping track of what it's listening to.
|
||||
Events.listenTo = function(obj, name, callback) {
|
||||
if (!obj) return this;
|
||||
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
|
||||
var listeningTo = this._listeningTo || (this._listeningTo = {});
|
||||
var listening = listeningTo[id];
|
||||
|
||||
// This object is not listening to any other events on `obj` yet.
|
||||
// Setup the necessary references to track the listening callbacks.
|
||||
if (!listening) {
|
||||
listening = listeningTo[id] = {obj: obj, events: {}};
|
||||
id = this._listenId || (this._listenId = _.uniqueId('l'));
|
||||
var listeners = obj._listeners || (obj._listeners = {});
|
||||
listeners[id] = this;
|
||||
}
|
||||
|
||||
// Bind callbacks on obj, and keep track of them on listening.
|
||||
obj.on(name, callback, this);
|
||||
listening.events = eventsApi(onApi, listening.events, name, callback);
|
||||
return this;
|
||||
};
|
||||
|
||||
// The reducing API that adds a callback to the `events` object.
|
||||
var onApi = function(events, name, callback, context, ctx) {
|
||||
if (callback) {
|
||||
var handlers = events[name] || [];
|
||||
events[name] = handlers.concat({callback: callback, context: context, ctx: context || ctx});
|
||||
var handlers = events[name] || (events[name] = []);
|
||||
handlers.push({callback: callback, context: context, ctx: context || ctx});
|
||||
}
|
||||
return events;
|
||||
};
|
||||
|
||||
// Handles removing any event handlers that are no longer wanted.
|
||||
// Remove one or many callbacks. If `context` is null, removes all
|
||||
// callbacks with that function. If `callback` is null, removes all
|
||||
// callbacks for the event. If `name` is null, removes all bound
|
||||
// callbacks for all events.
|
||||
Events.off = function(name, callback, context) {
|
||||
if (!this._events) return this;
|
||||
this._events = eventsApi(offApi, this._events, name, callback, context);
|
||||
|
||||
var listeners = this._listeners;
|
||||
if (listeners) {
|
||||
// Listeners always bind themselves as the context, so if `context`
|
||||
// is passed, narrow down the search to just that listener.
|
||||
var ids = context != null ? [context._listenId] : _.keys(listeners);
|
||||
|
||||
for (var i = 0, length = ids.length; i < length; i++) {
|
||||
var listener = listeners[ids[i]];
|
||||
|
||||
// Bail out if listener isn't listening.
|
||||
if (!listener) break;
|
||||
|
||||
// Tell each listener to stop, without infinitely calling `#off`.
|
||||
internalStopListening(listener, this, name, callback);
|
||||
}
|
||||
if (_.isEmpty(listeners)) this._listeners = void 0;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
// Tell this object to stop listening to either specific events ... or
|
||||
// to every object it's currently listening to.
|
||||
Events.stopListening = function(obj, name, callback) {
|
||||
// Use an internal stopListening, telling it to call off on `obj`.
|
||||
if (this._listeningTo) internalStopListening(this, obj, name, callback, true);
|
||||
return this;
|
||||
};
|
||||
|
||||
// The reducing API that removes a callback from the `events` object.
|
||||
var offApi = function(events, name, callback, context) {
|
||||
// Remove all callbacks for all events.
|
||||
if (!events || !name && !context && !callback) return;
|
||||
@@ -204,9 +187,9 @@
|
||||
var names = name ? [name] : _.keys(events);
|
||||
for (var i = 0, length = names.length; i < length; i++) {
|
||||
name = names[i];
|
||||
var handlers = events[name];
|
||||
|
||||
// Bail out if there are no events stored.
|
||||
var handlers = events[name];
|
||||
if (!handlers) break;
|
||||
|
||||
// Find any remaining events.
|
||||
@@ -231,7 +214,7 @@
|
||||
delete events[name];
|
||||
}
|
||||
}
|
||||
return _.isEmpty(events) ? void 0 : events;
|
||||
if (!_.isEmpty(events)) return events;
|
||||
};
|
||||
|
||||
var internalStopListening = function(listener, obj, name, callback, offEvents) {
|
||||
@@ -258,27 +241,23 @@
|
||||
if (_.isEmpty(listeningTo)) listener._listeningTo = void 0;
|
||||
};
|
||||
|
||||
// When triggering with an `{event: value}` object, `value` should be
|
||||
// prepended to the arguments passed onto the event callbacks.
|
||||
var triggerSentinel = {};
|
||||
// Bind an event to only be triggered a single time. After the first time
|
||||
// the callback is invoked, it will be removed.
|
||||
Events.once = function(name, callback, context) {
|
||||
// Map the event into a `{event: once}` object.
|
||||
var events = onceMap(name, callback, _.bind(this.off, this));
|
||||
return this.on(events, void 0, context);
|
||||
};
|
||||
|
||||
// Handles triggering the appropriate event callbacks.
|
||||
var triggerApi = function(obj, name, sentinel, args) {
|
||||
if (obj._events) {
|
||||
// Prepend `sentinel` onto args when trigger was called with
|
||||
// an object.
|
||||
if (sentinel !== triggerSentinel) args = [sentinel].concat(args);
|
||||
|
||||
var events = obj._events[name];
|
||||
var allEvents = obj._events.all;
|
||||
if (events) triggerEvents(events, args);
|
||||
if (allEvents) triggerEvents(allEvents, [name].concat(args));
|
||||
}
|
||||
return obj;
|
||||
// Inversion-of-control versions of `once`.
|
||||
Events.listenToOnce = function(obj, name, callback) {
|
||||
// Map the event into a `{event: once}` object.
|
||||
var events = onceMap(name, callback, _.bind(this.stopListening, this, obj));
|
||||
return this.listenTo(obj, events);
|
||||
};
|
||||
|
||||
// Reduces the event callbacks into a map of `{event: onceWrapper}`.
|
||||
// `offer` is used to unbind the `onceWrapper` after it as been called.
|
||||
// `offer` unbinds the `onceWrapper` after it as been called.
|
||||
var onceMap = function(name, callback, offer) {
|
||||
return eventsApi(function(map, name, callback, offer) {
|
||||
if (callback) {
|
||||
@@ -292,6 +271,38 @@
|
||||
}, {}, name, callback, offer);
|
||||
};
|
||||
|
||||
// Trigger one or many events, firing all bound callbacks. Callbacks are
|
||||
// passed the same arguments as `trigger` is, apart from the event name
|
||||
// (unless you're listening on `"all"`, which will cause your callback to
|
||||
// receive the true name of the event as the first argument).
|
||||
Events.trigger = function(name) {
|
||||
if (!this._events) return this;
|
||||
var args = slice.call(arguments, 1);
|
||||
|
||||
// Pass `triggerSentinel` as "callback" param. If `name` is an object,
|
||||
// it `triggerApi` will be passed the property's value instead.
|
||||
eventsApi(triggerApi, this, name, triggerSentinel, args);
|
||||
return this;
|
||||
};
|
||||
|
||||
// A known sentinel to detect triggering with a `{event: value}` object.
|
||||
var triggerSentinel = {};
|
||||
|
||||
// Handles triggering the appropriate event callbacks.
|
||||
var triggerApi = function(obj, name, sentinel, args) {
|
||||
if (obj._events) {
|
||||
// If `sentinel` is not the trigger sentinel, trigger was called
|
||||
// with a `{event: value}` object, and it is `value`.
|
||||
if (sentinel !== triggerSentinel) args = [sentinel].concat(args);
|
||||
|
||||
var events = obj._events[name];
|
||||
var allEvents = obj._events.all;
|
||||
if (events) triggerEvents(events, args);
|
||||
if (allEvents) triggerEvents(allEvents, [name].concat(args));
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
// A difficult-to-believe, but optimized internal dispatch function for
|
||||
// triggering events. Tries to keep the usual cases speedy (most internal
|
||||
// Backbone events have 3 arguments).
|
||||
|
||||
Reference in New Issue
Block a user