Rearrange and comment Events

This commit is contained in:
Justin Ridgewell
2015-01-29 23:20:58 -05:00
parent 826b110b45
commit 4018a5683f

View File

@@ -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).