diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2013-03-20 21:37:16 +0100 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2013-03-20 21:37:16 +0100 |
commit | b9016937983f60f88bad84a6e6169fa79e0046ef (patch) | |
tree | eaae700a00d9aea8b50df3aa1961c6b66c158703 | |
parent | Keep track of resets in table view so we don't keep rendering stale rows. (diff) | |
download | zmusic-ng-b9016937983f60f88bad84a6e6169fa79e0046ef.tar.xz zmusic-ng-b9016937983f60f88bad84a6e6169fa79e0046ef.zip |
Update libraries.
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | frontend/js/lib/backbone.js | 1023 | ||||
-rw-r--r-- | frontend/js/lib/jquery.js | 1300 | ||||
-rw-r--r-- | frontend/js/lib/underscore.js | 74 | ||||
-rw-r--r-- | frontend/js/models/ReferenceCountedModel.js | 8 |
5 files changed, 1288 insertions, 1119 deletions
@@ -152,7 +152,7 @@ By far the easiest way to run the application is standalone. Simply execute `bac The collection may be scanned using the admin credentials: - zmusic-ng $ curl http://127.0.0.1:5000/scan?username=ADMIN_USER&password=ADMIN_PASSWORD + zmusic-ng $ curl 'http://127.0.0.1:5000/scan?username=ADMIN_USER&password=ADMIN_PASSWORD' And then the site may be viewed in the browser: diff --git a/frontend/js/lib/backbone.js b/frontend/js/lib/backbone.js index 9682be5..3512d42 100644 --- a/frontend/js/lib/backbone.js +++ b/frontend/js/lib/backbone.js @@ -1,6 +1,6 @@ -// Backbone.js 0.9.10 +// Backbone.js 1.0.0 -// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. +// (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc. // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org @@ -18,14 +18,14 @@ // restored later on, if `noConflict` is used. var previousBackbone = root.Backbone; - // Create a local reference to array methods. + // Create local references to array methods we'll want to use later. var array = []; var push = array.push; var slice = array.slice; var splice = array.splice; // The top-level namespace. All public Backbone classes and modules will - // be attached to this. Exported for both CommonJS and the browser. + // be attached to this. Exported for both the browser and the server. var Backbone; if (typeof exports !== 'undefined') { Backbone = exports; @@ -34,14 +34,15 @@ } // Current version of the library. Keep in sync with `package.json`. - Backbone.VERSION = '0.9.10'; + Backbone.VERSION = '1.0.0'; // Require Underscore, if we're on the server, and it's not already present. var _ = root._; if (!_ && (typeof require !== 'undefined')) _ = require('underscore'); - // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable. - Backbone.$ = root.jQuery || root.Zepto || root.ender; + // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns + // the `$` variable. + Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$; // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable // to its previous owner. Returns a reference to this Backbone object. @@ -64,45 +65,6 @@ // 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. - var eventsApi = function(obj, action, name, rest) { - if (!name) return true; - if (typeof name === 'object') { - for (var key in name) { - obj[action].apply(obj, [key, name[key]].concat(rest)); - } - } else if (eventSplitter.test(name)) { - var names = name.split(eventSplitter); - for (var i = 0, l = names.length; i < l; i++) { - obj[action].apply(obj, [names[i]].concat(rest)); - } - } else { - return true; - } - }; - - // Optimized internal dispatch function for triggering events. Tries to - // keep the usual cases speedy (most Backbone events have 3 arguments). - var triggerEvents = function(events, args) { - var ev, i = -1, l = events.length; - switch (args.length) { - case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); - return; - case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0]); - return; - case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1]); - return; - case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]); - return; - default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); - } - }; - // A module that can be mixed in to *any object* in order to provide it with // custom events. You may bind with `on` or remove with `off` callback // functions to an event; `trigger`-ing an event fires all callbacks in @@ -115,29 +77,27 @@ // var Events = Backbone.Events = { - // Bind one or more space separated events, or an events map, - // to a `callback` function. Passing `"all"` will bind the callback to - // all events fired. + // Bind an event to a `callback` function. Passing `"all"` will bind + // the callback to all events fired. on: function(name, callback, context) { - if (!(eventsApi(this, 'on', name, [callback, context]) && callback)) return this; + if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; this._events || (this._events = {}); - var list = this._events[name] || (this._events[name] = []); - list.push({callback: callback, context: context, ctx: context || this}); + var events = this._events[name] || (this._events[name] = []); + events.push({callback: callback, context: context, ctx: context || this}); return this; }, - // Bind events to only be triggered a single time. After the first time + // 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) { - if (!(eventsApi(this, 'once', name, [callback, context]) && callback)) return this; + if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; var self = this; var once = _.once(function() { self.off(name, once); callback.apply(this, arguments); }); once._callback = callback; - this.on(name, once, context); - return this; + return this.on(name, once, context); }, // Remove one or many callbacks. If `context` is null, removes all @@ -145,7 +105,7 @@ // callbacks for the event. If `name` is null, removes all bound // callbacks for all events. off: function(name, callback, context) { - var list, ev, events, names, i, l, j, k; + var retain, ev, events, names, i, l, j, k; if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; if (!name && !callback && !context) { this._events = {}; @@ -155,19 +115,18 @@ names = name ? [name] : _.keys(this._events); for (i = 0, l = names.length; i < l; i++) { name = names[i]; - if (list = this._events[name]) { - events = []; + if (events = this._events[name]) { + this._events[name] = retain = []; if (callback || context) { - for (j = 0, k = list.length; j < k; j++) { - ev = list[j]; - if ((callback && callback !== ev.callback && - callback !== ev.callback._callback) || + for (j = 0, k = events.length; j < k; j++) { + ev = events[j]; + if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || (context && context !== ev.context)) { - events.push(ev); + retain.push(ev); } } } - this._events[name] = events; + if (!retain.length) delete this._events[name]; } } @@ -189,35 +148,82 @@ return this; }, - // An inversion-of-control version of `on`. Tell *this* object to listen to - // an event in another object ... keeping track of what it's listening to. - listenTo: function(obj, name, callback) { - var listeners = this._listeners || (this._listeners = {}); - var id = obj._listenerId || (obj._listenerId = _.uniqueId('l')); - listeners[id] = obj; - obj.on(name, typeof name === 'object' ? this : callback, this); - return this; - }, - // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. stopListening: function(obj, name, callback) { var listeners = this._listeners; - if (!listeners) return; - if (obj) { - obj.off(name, typeof name === 'object' ? this : callback, this); - if (!name && !callback) delete listeners[obj._listenerId]; - } else { - if (typeof name === 'object') callback = this; - for (var id in listeners) { - listeners[id].off(name, callback, this); - } - this._listeners = {}; + if (!listeners) return this; + var deleteListener = !name && !callback; + if (typeof name === 'object') callback = this; + if (obj) (listeners = {})[obj._listenerId] = obj; + for (var id in listeners) { + listeners[id].off(name, callback, this); + if (deleteListener) delete this._listeners[id]; } return this; } + }; + // 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. + var eventsApi = function(obj, action, name, rest) { + if (!name) return true; + + // Handle event maps. + if (typeof name === 'object') { + for (var key in name) { + obj[action].apply(obj, [key, name[key]].concat(rest)); + } + return false; + } + + // Handle space separated event names. + if (eventSplitter.test(name)) { + var names = name.split(eventSplitter); + for (var i = 0, l = names.length; i < l; i++) { + obj[action].apply(obj, [names[i]].concat(rest)); + } + return false; + } + + return true; + }; + + // 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). + var triggerEvents = function(events, args) { + var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; + switch (args.length) { + case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; + case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; + case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; + case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; + default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); + } + }; + + var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; + + // 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. + _.each(listenMethods, function(implementation, method) { + Events[method] = function(obj, name, callback) { + var listeners = this._listeners || (this._listeners = {}); + var id = obj._listenerId || (obj._listenerId = _.uniqueId('l')); + listeners[id] = obj; + if (typeof name === 'object') callback = this; + obj[implementation](name, callback, this); + return this; + }; + }); + // Aliases for backwards compatibility. Events.bind = Events.on; Events.unbind = Events.off; @@ -229,15 +235,21 @@ // Backbone.Model // -------------- - // Create a new model, with defined attributes. A client id (`cid`) + // Backbone **Models** are the basic data object in the framework -- + // frequently representing a row in a table in a database on your server. + // A discrete chunk of data and a bunch of useful, related methods for + // performing computations and transformations on that data. + + // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. var Model = Backbone.Model = function(attributes, options) { var defaults; var attrs = attributes || {}; + options || (options = {}); this.cid = _.uniqueId('c'); this.attributes = {}; - if (options && options.collection) this.collection = options.collection; - if (options && options.parse) attrs = this.parse(attrs, options) || {}; + _.extend(this, _.pick(options, modelOptions)); + if (options.parse) attrs = this.parse(attrs, options) || {}; if (defaults = _.result(this, 'defaults')) { attrs = _.defaults({}, attrs, defaults); } @@ -246,12 +258,18 @@ this.initialize.apply(this, arguments); }; + // A list of options to be attached directly to the model, if provided. + var modelOptions = ['url', 'urlRoot', 'collection']; + // Attach all inheritable methods to the Model prototype. _.extend(Model.prototype, Events, { // A hash of attributes whose current and previous value differ. changed: null, + // The value returned during the last failed validation. + validationError: null, + // The default name for the JSON `id` attribute is `"id"`. MongoDB and // CouchDB users may want to set this to `"_id"`. idAttribute: 'id', @@ -265,7 +283,8 @@ return _.clone(this.attributes); }, - // Proxy `Backbone.sync` by default. + // Proxy `Backbone.sync` by default -- but override this if you need + // custom syncing semantics for *this* particular model. sync: function() { return Backbone.sync.apply(this, arguments); }, @@ -286,10 +305,9 @@ return this.get(attr) != null; }, - // ---------------------------------------------------------------------- - - // Set a hash of model attributes on the object, firing `"change"` unless - // you choose to silence it. + // Set a hash of model attributes on the object, firing `"change"`. This is + // the core primitive operation of a model, updating the data and notifying + // anyone who needs to know about the change in state. The heart of the beast. set: function(key, val, options) { var attr, attrs, unset, changes, silent, changing, prev, current; if (key == null) return this; @@ -343,6 +361,8 @@ } } + // You might be wondering why there's a `while` loop here. Changes can + // be recursively nested within `"change"` events. if (changing) return this; if (!silent) { while (this._pending) { @@ -355,14 +375,13 @@ return this; }, - // Remove an attribute from the model, firing `"change"` unless you choose - // to silence it. `unset` is a noop if the attribute doesn't exist. + // Remove an attribute from the model, firing `"change"`. `unset` is a noop + // if the attribute doesn't exist. unset: function(attr, options) { return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, - // Clear all attributes on the model, firing `"change"` unless you choose - // to silence it. + // Clear all attributes on the model, firing `"change"`. clear: function(options) { var attrs = {}; for (var key in this.attributes) attrs[key] = void 0; @@ -406,19 +425,20 @@ return _.clone(this._previousAttributes); }, - // --------------------------------------------------------------------- - // Fetch the model from the server. If the server's representation of the - // model differs from its current attributes, they will be overriden, + // model differs from its current attributes, they will be overridden, // triggering a `"change"` event. fetch: function(options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; + var model = this; var success = options.success; - options.success = function(model, resp, options) { + options.success = function(resp) { if (!model.set(model.parse(resp, options), options)) return false; if (success) success(model, resp, options); + model.trigger('sync', model, resp, options); }; + wrapError(this, options); return this.sync('read', this, options); }, @@ -426,7 +446,7 @@ // If the server returns an attributes hash that differs, the model's // state will be `set` again. save: function(key, val, options) { - var attrs, success, method, xhr, attributes = this.attributes; + var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments. if (key == null || typeof key === 'object') { @@ -452,8 +472,9 @@ // After a successful server-side save, the client is (optionally) // updated with the server-side state. if (options.parse === void 0) options.parse = true; - success = options.success; - options.success = function(model, resp, options) { + var model = this; + var success = options.success; + options.success = function(resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes; var serverAttrs = model.parse(resp, options); @@ -462,9 +483,10 @@ return false; } if (success) success(model, resp, options); + model.trigger('sync', model, resp, options); }; + wrapError(this, options); - // Finish configuring and sending the Ajax request. method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); if (method === 'patch') options.attrs = attrs; xhr = this.sync(method, this, options); @@ -487,15 +509,17 @@ model.trigger('destroy', model, model.collection, options); }; - options.success = function(model, resp, options) { + options.success = function(resp) { if (options.wait || model.isNew()) destroy(); if (success) success(model, resp, options); + if (!model.isNew()) model.trigger('sync', model, resp, options); }; if (this.isNew()) { - options.success(this, null, options); + options.success(); return false; } + wrapError(this, options); var xhr = this.sync('delete', this, options); if (!options.wait) destroy(); @@ -529,39 +553,61 @@ // Check if the model is currently in a valid state. isValid: function(options) { - return !this.validate || !this.validate(this.attributes, options); + return this._validate({}, _.extend(options || {}, { validate: true })); }, // Run validation against the next complete set of model attributes, - // returning `true` if all is well. Otherwise, fire a general - // `"error"` event and call the error callback, if specified. + // returning `true` if all is well. Otherwise, fire an `"invalid"` event. _validate: function(attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; if (!error) return true; - this.trigger('invalid', this, error, options || {}); + this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error})); return false; } }); + // Underscore methods that we want to implement on the Model. + var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; + + // Mix in each Underscore method as a proxy to `Model#attributes`. + _.each(modelMethods, function(method) { + Model.prototype[method] = function() { + var args = slice.call(arguments); + args.unshift(this.attributes); + return _[method].apply(_, args); + }; + }); + // Backbone.Collection // ------------------- - // Provides a standard collection class for our sets of models, ordered - // or unordered. If a `comparator` is specified, the Collection will maintain + // If models tend to represent a single row of data, a Backbone Collection is + // more analagous to a table full of data ... or a small slice or page of that + // table, or a collection of rows that belong together for a particular reason + // -- all of the messages in this particular folder, all of the documents + // belonging to this particular author, and so on. Collections maintain + // indexes of their models, both in order, and for lookup by `id`. + + // Create a new **Collection**, perhaps to contain a specific type of `model`. + // If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. var Collection = Backbone.Collection = function(models, options) { options || (options = {}); + if (options.url) this.url = options.url; if (options.model) this.model = options.model; if (options.comparator !== void 0) this.comparator = options.comparator; - this.models = []; this._reset(); this.initialize.apply(this, arguments); if (models) this.reset(models, _.extend({silent: true}, options)); }; + // Default options for `Collection#set`. + var setOptions = {add: true, remove: true, merge: true}; + var addOptions = {add: true, merge: false, remove: false}; + // Define the Collection's inheritable methods. _.extend(Collection.prototype, Events, { @@ -586,88 +632,118 @@ // Add a model, or list of models to the set. add: function(models, options) { + return this.set(models, _.defaults(options || {}, addOptions)); + }, + + // Remove a model, or a list of models from the set. + remove: function(models, options) { models = _.isArray(models) ? models.slice() : [models]; options || (options = {}); - var i, l, model, attrs, existing, doSort, add, at, sort, sortAttr; - add = []; - at = options.at; - sort = this.comparator && (at == null) && options.sort != false; - sortAttr = _.isString(this.comparator) ? this.comparator : null; + var i, l, index, model; + for (i = 0, l = models.length; i < l; i++) { + model = this.get(models[i]); + if (!model) continue; + delete this._byId[model.id]; + delete this._byId[model.cid]; + index = this.indexOf(model); + this.models.splice(index, 1); + this.length--; + if (!options.silent) { + options.index = index; + model.trigger('remove', model, this, options); + } + this._removeReference(model); + } + return this; + }, + + // Update a collection by `set`-ing a new list of models, adding new ones, + // removing models that are no longer present, and merging models that + // already exist in the collection, as necessary. Similar to **Model#set**, + // the core operation for updating the data contained by the collection. + set: function(models, options) { + options = _.defaults(options || {}, setOptions); + if (options.parse) models = this.parse(models, options); + if (!_.isArray(models)) models = models ? [models] : []; + var i, l, model, attrs, existing, sort; + var at = options.at; + var sortable = this.comparator && (at == null) && options.sort !== false; + var sortAttr = _.isString(this.comparator) ? this.comparator : null; + var toAdd = [], toRemove = [], modelMap = {}; // Turn bare objects into model references, and prevent invalid models // from being added. for (i = 0, l = models.length; i < l; i++) { - if (!(model = this._prepareModel(attrs = models[i], options))) { - this.trigger('invalid', this, attrs, options); - continue; - } + if (!(model = this._prepareModel(models[i], options))) continue; // If a duplicate is found, prevent it from being added and // optionally merge it into the existing model. if (existing = this.get(model)) { + if (options.remove) modelMap[existing.cid] = true; if (options.merge) { - existing.set(attrs === model ? model.attributes : attrs, options); - if (sort && !doSort && existing.hasChanged(sortAttr)) doSort = true; + existing.set(model.attributes, options); + if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; } - continue; - } - // This is a new model, push it to the `add` list. - add.push(model); + // This is a new model, push it to the `toAdd` list. + } else if (options.add) { + toAdd.push(model); - // Listen to added models' events, and index models for lookup by - // `id` and by `cid`. - model.on('all', this._onModelEvent, this); - this._byId[model.cid] = model; - if (model.id != null) this._byId[model.id] = model; + // Listen to added models' events, and index models for lookup by + // `id` and by `cid`. + model.on('all', this._onModelEvent, this); + this._byId[model.cid] = model; + if (model.id != null) this._byId[model.id] = model; + } + } + + // Remove nonexistent models if appropriate. + if (options.remove) { + for (i = 0, l = this.length; i < l; ++i) { + if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); + } + if (toRemove.length) this.remove(toRemove, options); } // See if sorting is needed, update `length` and splice in new models. - if (add.length) { - if (sort) doSort = true; - this.length += add.length; + if (toAdd.length) { + if (sortable) sort = true; + this.length += toAdd.length; if (at != null) { - splice.apply(this.models, [at, 0].concat(add)); + splice.apply(this.models, [at, 0].concat(toAdd)); } else { - push.apply(this.models, add); + push.apply(this.models, toAdd); } } // Silently sort the collection if appropriate. - if (doSort) this.sort({silent: true}); + if (sort) this.sort({silent: true}); if (options.silent) return this; // Trigger `add` events. - for (i = 0, l = add.length; i < l; i++) { - (model = add[i]).trigger('add', model, this, options); + for (i = 0, l = toAdd.length; i < l; i++) { + (model = toAdd[i]).trigger('add', model, this, options); } // Trigger `sort` if the collection was sorted. - if (doSort) this.trigger('sort', this, options); - + if (sort) this.trigger('sort', this, options); return this; }, - // Remove a model, or a list of models from the set. - remove: function(models, options) { - models = _.isArray(models) ? models.slice() : [models]; + // When you have more items than you want to add or remove individually, + // you can reset the entire set with a new list of models, without firing + // any granular `add` or `remove` events. Fires `reset` when finished. + // Useful for bulk operations and optimizations. + reset: function(models, options) { options || (options = {}); - var i, l, index, model; - for (i = 0, l = models.length; i < l; i++) { - model = this.get(models[i]); - if (!model) continue; - delete this._byId[model.id]; - delete this._byId[model.cid]; - index = this.indexOf(model); - this.models.splice(index, 1); - this.length--; - if (!options.silent) { - options.index = index; - model.trigger('remove', model, this, options); - } - this._removeReference(model); + for (var i = 0, l = this.models.length; i < l; i++) { + this._removeReference(this.models[i]); } + options.previousModels = this.models; + this._reset(); + this.add(models, _.extend({silent: true}, options)); + if (!options.silent) this.trigger('reset', this, options); return this; }, @@ -707,8 +783,7 @@ // Get a model from the set by id. get: function(obj) { if (obj == null) return void 0; - this._idAttr || (this._idAttr = this.model.prototype.idAttribute); - return this._byId[obj.id || obj.cid || obj[this._idAttr] || obj]; + return this._byId[obj.id != null ? obj.id : obj.cid || obj]; }, // Get the model at the given index. @@ -716,10 +791,11 @@ return this.models[index]; }, - // Return models with matching attributes. Useful for simple cases of `filter`. - where: function(attrs) { - if (_.isEmpty(attrs)) return []; - return this.filter(function(model) { + // Return models with matching attributes. Useful for simple cases of + // `filter`. + where: function(attrs, first) { + if (_.isEmpty(attrs)) return first ? void 0 : []; + return this[first ? 'find' : 'filter'](function(model) { for (var key in attrs) { if (attrs[key] !== model.get(key)) return false; } @@ -727,13 +803,17 @@ }); }, + // Return the first model with matching attributes. Useful for simple cases + // of `find`. + findWhere: function(attrs) { + return this.where(attrs, true); + }, + // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. sort: function(options) { - if (!this.comparator) { - throw new Error('Cannot sort a set without a comparator'); - } + if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); options || (options = {}); // Run sort based on type of `comparator`. @@ -747,75 +827,36 @@ return this; }, + // Figure out the smallest index at which a model should be inserted so as + // to maintain order. + sortedIndex: function(model, value, context) { + value || (value = this.comparator); + var iterator = _.isFunction(value) ? value : function(model) { + return model.get(value); + }; + return _.sortedIndex(this.models, model, iterator, context); + }, + // Pluck an attribute from each model in the collection. pluck: function(attr) { return _.invoke(this.models, 'get', attr); }, - // Smartly update a collection with a change set of models, adding, - // removing, and merging as necessary. - update: function(models, options) { - options = _.extend({add: true, merge: true, remove: true}, options); - if (options.parse) models = this.parse(models, options); - var model, i, l, existing; - var add = [], remove = [], modelMap = {}; - - // Allow a single model (or no argument) to be passed. - if (!_.isArray(models)) models = models ? [models] : []; - - // Proxy to `add` for this case, no need to iterate... - if (options.add && !options.remove) return this.add(models, options); - - // Determine which models to add and merge, and which to remove. - for (i = 0, l = models.length; i < l; i++) { - model = models[i]; - existing = this.get(model); - if (options.remove && existing) modelMap[existing.cid] = true; - if ((options.add && !existing) || (options.merge && existing)) { - add.push(model); - } - } - if (options.remove) { - for (i = 0, l = this.models.length; i < l; i++) { - model = this.models[i]; - if (!modelMap[model.cid]) remove.push(model); - } - } - - // Remove models (if applicable) before we add and merge the rest. - if (remove.length) this.remove(remove, options); - if (add.length) this.add(add, options); - return this; - }, - - // When you have more items than you want to add or remove individually, - // you can reset the entire set with a new list of models, without firing - // any `add` or `remove` events. Fires `reset` when finished. - reset: function(models, options) { - options || (options = {}); - if (options.parse) models = this.parse(models, options); - for (var i = 0, l = this.models.length; i < l; i++) { - this._removeReference(this.models[i]); - } - options.previousModels = this.models.slice(); - this._reset(); - if (models) this.add(models, _.extend({silent: true}, options)); - if (!options.silent) this.trigger('reset', this, options); - return this; - }, - // Fetch the default set of models for this collection, resetting the - // collection when they arrive. If `update: true` is passed, the response - // data will be passed through the `update` method instead of `reset`. + // collection when they arrive. If `reset: true` is passed, the response + // data will be passed through the `reset` method instead of `set`. fetch: function(options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var success = options.success; - options.success = function(collection, resp, options) { - var method = options.update ? 'update' : 'reset'; + var collection = this; + options.success = function(resp) { + var method = options.reset ? 'reset' : 'set'; collection[method](resp, options); if (success) success(collection, resp, options); + collection.trigger('sync', collection, resp, options); }; + wrapError(this, options); return this.sync('read', this, options); }, @@ -828,7 +869,7 @@ if (!options.wait) this.add(model, options); var collection = this; var success = options.success; - options.success = function(model, resp, options) { + options.success = function(resp) { if (options.wait) collection.add(model, options); if (success) success(model, resp, options); }; @@ -847,14 +888,16 @@ return new this.constructor(this.models); }, - // Reset all internal state. Called when the collection is reset. + // Private method to reset all internal state. Called when the collection + // is first initialized or reset. _reset: function() { this.length = 0; - this.models.length = 0; + this.models = []; this._byId = {}; }, - // Prepare a model or hash of attributes to be added to this collection. + // Prepare a hash of attributes (or other model) to be added to this + // collection. _prepareModel: function(attrs, options) { if (attrs instanceof Model) { if (!attrs.collection) attrs.collection = this; @@ -863,11 +906,14 @@ options || (options = {}); options.collection = this; var model = new this.model(attrs, options); - if (!model._validate(attrs, options)) return false; + if (!model._validate(attrs, options)) { + this.trigger('invalid', this, attrs, options); + return false; + } return model; }, - // Internal method to remove a model's ties to a collection. + // Internal method to sever a model's ties to a collection. _removeReference: function(model) { if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); @@ -885,19 +931,13 @@ if (model.id != null) this._byId[model.id] = model; } this.trigger.apply(this, arguments); - }, - - sortedIndex: function (model, value, context) { - value || (value = this.comparator); - var iterator = _.isFunction(value) ? value : function(model) { - return model.get(value); - }; - return _.sortedIndex(this.models, model, iterator, context); } }); // Underscore methods that we want to implement on the Collection. + // 90% of the core usefulness of Backbone Collections is actually implemented + // right here: var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', @@ -927,6 +967,241 @@ }; }); + // Backbone.View + // ------------- + + // Backbone Views are almost more convention than they are actual code. A View + // is simply a JavaScript object that represents a logical chunk of UI in the + // DOM. This might be a single item, an entire list, a sidebar or panel, or + // even the surrounding frame which wraps your whole app. Defining a chunk of + // UI as a **View** allows you to define your DOM events declaratively, without + // having to worry about render order ... and makes it easy for the view to + // react to specific changes in the state of your models. + + // Creating a Backbone.View creates its initial element outside of the DOM, + // if an existing element is not provided... + var View = Backbone.View = function(options) { + this.cid = _.uniqueId('view'); + this._configure(options || {}); + this._ensureElement(); + this.initialize.apply(this, arguments); + this.delegateEvents(); + }; + + // Cached regex to split keys for `delegate`. + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be merged as properties. + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + // Set up all inheritable **Backbone.View** properties and methods. + _.extend(View.prototype, Events, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + // jQuery delegate for element lookup, scoped to DOM elements within the + // current view. This should be prefered to global lookups where possible. + $: function(selector) { + return this.$el.find(selector); + }, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // **render** is the core function that your view should override, in order + // to populate its element (`this.el`), with the appropriate HTML. The + // convention is for **render** to always return `this`. + render: function() { + return this; + }, + + // Remove this view by taking the element out of the DOM, and removing any + // applicable Backbone.Events listeners. + remove: function() { + this.$el.remove(); + this.stopListening(); + return this; + }, + + // Change the view's element (`this.el` property), including event + // re-delegation. + setElement: function(element, delegate) { + if (this.$el) this.undelegateEvents(); + this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); + this.el = this.$el[0]; + if (delegate !== false) this.delegateEvents(); + return this; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save' + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + // This only works for delegate-able events: not `focus`, `blur`, and + // not `change`, `submit`, and `reset` in Internet Explorer. + delegateEvents: function(events) { + if (!(events || (events = _.result(this, 'events')))) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[events[key]]; + if (!method) continue; + + var match = key.match(delegateEventSplitter); + var eventName = match[1], selector = match[2]; + method = _.bind(method, this); + eventName += '.delegateEvents' + this.cid; + if (selector === '') { + this.$el.on(eventName, method); + } else { + this.$el.on(eventName, selector, method); + } + } + return this; + }, + + // Clears all callbacks previously bound to the view with `delegateEvents`. + // You usually don't need to use this, but may wish to if you have multiple + // Backbone views attached to the same DOM element. + undelegateEvents: function() { + this.$el.off('.delegateEvents' + this.cid); + return this; + }, + + // Performs the initial configuration of a View with a set of options. + // Keys with special meaning *(e.g. model, collection, id, className)* are + // attached directly to the view. See `viewOptions` for an exhaustive + // list. + _configure: function(options) { + if (this.options) options = _.extend({}, _.result(this, 'options'), options); + _.extend(this, _.pick(options, viewOptions)); + this.options = options; + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + if (!this.el) { + var attrs = _.extend({}, _.result(this, 'attributes')); + if (this.id) attrs.id = _.result(this, 'id'); + if (this.className) attrs['class'] = _.result(this, 'className'); + var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); + this.setElement($el, false); + } else { + this.setElement(_.result(this, 'el'), false); + } + } + + }); + + // Backbone.sync + // ------------- + + // Override this function to change the manner in which Backbone persists + // models to the server. You will be passed the type of request, and the + // model in question. By default, makes a RESTful Ajax request + // to the model's `url()`. Some possible customizations could be: + // + // * Use `setTimeout` to batch rapid-fire updates into a single request. + // * Send up the models as XML instead of JSON. + // * Persist models via WebSockets instead of Ajax. + // + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests + // as `POST`, with a `_method` parameter containing the true HTTP method, + // as well as all requests with the body as `application/x-www-form-urlencoded` + // instead of `application/json` with the model in a param named `model`. + // Useful when interfacing with server-side languages like **PHP** that make + // it difficult to read the body of `PUT` requests. + Backbone.sync = function(method, model, options) { + var type = methodMap[method]; + + // Default options, unless specified. + _.defaults(options || (options = {}), { + emulateHTTP: Backbone.emulateHTTP, + emulateJSON: Backbone.emulateJSON + }); + + // Default JSON-request options. + var params = {type: type, dataType: 'json'}; + + // Ensure that we have a URL. + if (!options.url) { + params.url = _.result(model, 'url') || urlError(); + } + + // Ensure that we have the appropriate request data. + if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { + params.contentType = 'application/json'; + params.data = JSON.stringify(options.attrs || model.toJSON(options)); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (options.emulateJSON) { + params.contentType = 'application/x-www-form-urlencoded'; + params.data = params.data ? {model: params.data} : {}; + } + + // For older servers, emulate HTTP by mimicking the HTTP method with `_method` + // And an `X-HTTP-Method-Override` header. + if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { + params.type = 'POST'; + if (options.emulateJSON) params.data._method = type; + var beforeSend = options.beforeSend; + options.beforeSend = function(xhr) { + xhr.setRequestHeader('X-HTTP-Method-Override', type); + if (beforeSend) return beforeSend.apply(this, arguments); + }; + } + + // Don't process data on a non-GET request. + if (params.type !== 'GET' && !options.emulateJSON) { + params.processData = false; + } + + // If we're sending a `PATCH` request, and we're in an old Internet Explorer + // that still has ActiveX enabled by default, override jQuery to use that + // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8. + if (params.type === 'PATCH' && window.ActiveXObject && + !(window.external && window.external.msActiveXFilteringEnabled)) { + params.xhr = function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }; + } + + // Make the request, allowing the user to override any Ajax options. + var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); + model.trigger('request', model, xhr, options); + return xhr; + }; + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'patch': 'PATCH', + 'delete': 'DELETE', + 'read': 'GET' + }; + + // Set the default implementation of `Backbone.ajax` to proxy through to `$`. + // Override this if you'd like to use a different library. + Backbone.ajax = function() { + return Backbone.$.ajax.apply(Backbone.$, arguments); + }; + // Backbone.Router // --------------- @@ -961,14 +1236,19 @@ // route: function(route, name, callback) { if (!_.isRegExp(route)) route = this._routeToRegExp(route); + if (_.isFunction(name)) { + callback = name; + name = ''; + } if (!callback) callback = this[name]; - Backbone.history.route(route, _.bind(function(fragment) { - var args = this._extractParameters(route, fragment); - callback && callback.apply(this, args); - this.trigger.apply(this, ['route:' + name].concat(args)); - this.trigger('route', name, args); - Backbone.history.trigger('route', this, name, args); - }, this)); + var router = this; + Backbone.history.route(route, function(fragment) { + var args = router._extractParameters(route, fragment); + callback && callback.apply(router, args); + router.trigger.apply(router, ['route:' + name].concat(args)); + router.trigger('route', name, args); + Backbone.history.trigger('route', router, name, args); + }); return this; }, @@ -983,6 +1263,7 @@ // routes can be defined at the bottom of the route map. _bindRoutes: function() { if (!this.routes) return; + this.routes = _.result(this, 'routes'); var route, routes = _.keys(this.routes); while ((route = routes.pop()) != null) { this.route(route, this.routes[route]); @@ -1002,9 +1283,13 @@ }, // Given a route, and a URL fragment that it matches, return the array of - // extracted parameters. + // extracted decoded parameters. Empty or unmatched parameters will be + // treated as `null` to normalize cross-browser behavior. _extractParameters: function(route, fragment) { - return route.exec(fragment).slice(1); + var params = route.exec(fragment).slice(1); + return _.map(params, function(param) { + return param ? decodeURIComponent(param) : null; + }); } }); @@ -1012,8 +1297,11 @@ // Backbone.History // ---------------- - // Handles cross-browser history management, based on URL fragments. If the - // browser does not support `onhashchange`, falls back to polling. + // Handles cross-browser history management, based on either + // [pushState](http://diveintohtml5.info/history.html) and real URLs, or + // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) + // and URL fragments. If the browser supports neither (old IE, natch), + // falls back to polling. var History = Backbone.History = function() { this.handlers = []; _.bindAll(this, 'checkUrl'); @@ -1224,230 +1512,6 @@ // Create the default Backbone.history. Backbone.history = new History; - // Backbone.View - // ------------- - - // Creating a Backbone.View creates its initial element outside of the DOM, - // if an existing element is not provided... - var View = Backbone.View = function(options) { - this.cid = _.uniqueId('view'); - this._configure(options || {}); - this._ensureElement(); - this.initialize.apply(this, arguments); - this.delegateEvents(); - }; - - // Cached regex to split keys for `delegate`. - var delegateEventSplitter = /^(\S+)\s*(.*)$/; - - // List of view options to be merged as properties. - var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; - - // Set up all inheritable **Backbone.View** properties and methods. - _.extend(View.prototype, Events, { - - // The default `tagName` of a View's element is `"div"`. - tagName: 'div', - - // jQuery delegate for element lookup, scoped to DOM elements within the - // current view. This should be prefered to global lookups where possible. - $: function(selector) { - return this.$el.find(selector); - }, - - // Initialize is an empty function by default. Override it with your own - // initialization logic. - initialize: function(){}, - - // **render** is the core function that your view should override, in order - // to populate its element (`this.el`), with the appropriate HTML. The - // convention is for **render** to always return `this`. - render: function() { - return this; - }, - - // Remove this view by taking the element out of the DOM, and removing any - // applicable Backbone.Events listeners. - remove: function() { - this.$el.remove(); - this.stopListening(); - return this; - }, - - // Change the view's element (`this.el` property), including event - // re-delegation. - setElement: function(element, delegate) { - if (this.$el) this.undelegateEvents(); - this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); - this.el = this.$el[0]; - if (delegate !== false) this.delegateEvents(); - return this; - }, - - // Set callbacks, where `this.events` is a hash of - // - // *{"event selector": "callback"}* - // - // { - // 'mousedown .title': 'edit', - // 'click .button': 'save' - // 'click .open': function(e) { ... } - // } - // - // pairs. Callbacks will be bound to the view, with `this` set properly. - // Uses event delegation for efficiency. - // Omitting the selector binds the event to `this.el`. - // This only works for delegate-able events: not `focus`, `blur`, and - // not `change`, `submit`, and `reset` in Internet Explorer. - delegateEvents: function(events) { - if (!(events || (events = _.result(this, 'events')))) return; - this.undelegateEvents(); - for (var key in events) { - var method = events[key]; - if (!_.isFunction(method)) method = this[events[key]]; - if (!method) throw new Error('Method "' + events[key] + '" does not exist'); - var match = key.match(delegateEventSplitter); - var eventName = match[1], selector = match[2]; - method = _.bind(method, this); - eventName += '.delegateEvents' + this.cid; - if (selector === '') { - this.$el.on(eventName, method); - } else { - this.$el.on(eventName, selector, method); - } - } - }, - - // Clears all callbacks previously bound to the view with `delegateEvents`. - // You usually don't need to use this, but may wish to if you have multiple - // Backbone views attached to the same DOM element. - undelegateEvents: function() { - this.$el.off('.delegateEvents' + this.cid); - }, - - // Performs the initial configuration of a View with a set of options. - // Keys with special meaning *(model, collection, id, className)*, are - // attached directly to the view. - _configure: function(options) { - if (this.options) options = _.extend({}, _.result(this, 'options'), options); - _.extend(this, _.pick(options, viewOptions)); - this.options = options; - }, - - // Ensure that the View has a DOM element to render into. - // If `this.el` is a string, pass it through `$()`, take the first - // matching element, and re-assign it to `el`. Otherwise, create - // an element from the `id`, `className` and `tagName` properties. - _ensureElement: function() { - if (!this.el) { - var attrs = _.extend({}, _.result(this, 'attributes')); - if (this.id) attrs.id = _.result(this, 'id'); - if (this.className) attrs['class'] = _.result(this, 'className'); - var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); - this.setElement($el, false); - } else { - this.setElement(_.result(this, 'el'), false); - } - } - - }); - - // Backbone.sync - // ------------- - - // Map from CRUD to HTTP for our default `Backbone.sync` implementation. - var methodMap = { - 'create': 'POST', - 'update': 'PUT', - 'patch': 'PATCH', - 'delete': 'DELETE', - 'read': 'GET' - }; - - // Override this function to change the manner in which Backbone persists - // models to the server. You will be passed the type of request, and the - // model in question. By default, makes a RESTful Ajax request - // to the model's `url()`. Some possible customizations could be: - // - // * Use `setTimeout` to batch rapid-fire updates into a single request. - // * Send up the models as XML instead of JSON. - // * Persist models via WebSockets instead of Ajax. - // - // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests - // as `POST`, with a `_method` parameter containing the true HTTP method, - // as well as all requests with the body as `application/x-www-form-urlencoded` - // instead of `application/json` with the model in a param named `model`. - // Useful when interfacing with server-side languages like **PHP** that make - // it difficult to read the body of `PUT` requests. - Backbone.sync = function(method, model, options) { - var type = methodMap[method]; - - // Default options, unless specified. - _.defaults(options || (options = {}), { - emulateHTTP: Backbone.emulateHTTP, - emulateJSON: Backbone.emulateJSON - }); - - // Default JSON-request options. - var params = {type: type, dataType: 'json'}; - - // Ensure that we have a URL. - if (!options.url) { - params.url = _.result(model, 'url') || urlError(); - } - - // Ensure that we have the appropriate request data. - if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { - params.contentType = 'application/json'; - params.data = JSON.stringify(options.attrs || model.toJSON(options)); - } - - // For older servers, emulate JSON by encoding the request into an HTML-form. - if (options.emulateJSON) { - params.contentType = 'application/x-www-form-urlencoded'; - params.data = params.data ? {model: params.data} : {}; - } - - // For older servers, emulate HTTP by mimicking the HTTP method with `_method` - // And an `X-HTTP-Method-Override` header. - if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { - params.type = 'POST'; - if (options.emulateJSON) params.data._method = type; - var beforeSend = options.beforeSend; - options.beforeSend = function(xhr) { - xhr.setRequestHeader('X-HTTP-Method-Override', type); - if (beforeSend) return beforeSend.apply(this, arguments); - }; - } - - // Don't process data on a non-GET request. - if (params.type !== 'GET' && !options.emulateJSON) { - params.processData = false; - } - - var success = options.success; - options.success = function(resp) { - if (success) success(model, resp, options); - model.trigger('sync', model, resp, options); - }; - - var error = options.error; - options.error = function(xhr) { - if (error) error(model, xhr, options); - model.trigger('error', model, xhr, options); - }; - - // Make the request, allowing the user to override any Ajax options. - var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); - model.trigger('request', model, xhr, options); - return xhr; - }; - - // Set the default implementation of `Backbone.ajax` to proxy through to `$`. - Backbone.ajax = function() { - return Backbone.$.ajax.apply(Backbone.$, arguments); - }; - // Helpers // ------- @@ -1495,4 +1559,13 @@ throw new Error('A "url" property or function must be specified'); }; + // Wrap an optional error callback with a fallback error event. + var wrapError = function (model, options) { + var error = options.error; + options.error = function(resp) { + if (error) error(model, resp, options); + model.trigger('error', model, resp, options); + }; + }; + }).call(this); diff --git a/frontend/js/lib/jquery.js b/frontend/js/lib/jquery.js index 6f4bdf1..af8b3b8 100644 --- a/frontend/js/lib/jquery.js +++ b/frontend/js/lib/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v2.0.0b1 + * jQuery JavaScript Library v2.0.0b2 * http://jquery.com/ * * Includes Sizzle.js @@ -9,10 +9,15 @@ * Released under the MIT license * http://jquery.org/license * - * Date: 2013-1-14 + * Date: 2013-3-1 */ (function( window, undefined ) { -"use strict"; + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +//"use strict"; var // A central reference to the root jQuery(document) rootjQuery, @@ -20,9 +25,14 @@ var // The deferred used on DOM ready readyList, + // Support: IE9 + // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined` + core_strundefined = typeof undefined, + // Use the correct document accordingly with window argument (sandbox) - document = window.document, location = window.location, + document = window.document, + docElem = document.documentElement, // Map over jQuery in case of overwrite _jQuery = window.jQuery, @@ -36,7 +46,7 @@ var // List of deleted data cache ids, so we can reuse them core_deletedIds = [], - core_version = "2.0.0b1", + core_version = "2.0.0b2", // Save a reference to some core methods core_concat = core_deletedIds.concat, @@ -77,8 +87,9 @@ var }, // The ready event handler and self cleanup method - DOMContentLoaded = function() { - document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + completed = function() { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); jQuery.ready(); }; @@ -347,6 +358,9 @@ jQuery.extend = jQuery.fn.extend = function() { }; jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + noConflict: function( deep ) { if ( window.$ === jQuery ) { window.$ = _$; @@ -421,6 +435,7 @@ jQuery.extend({ if ( obj == null ) { return String( obj ); } + // Support: Safari <=5.1 (functionish RegExp) return typeof obj === "object" || typeof obj === "function" ? class2type[ core_toString.call(obj) ] || "object" : typeof obj; @@ -436,7 +451,7 @@ jQuery.extend({ } // Support: Firefox >16 - // The try/catch supresses exceptions thrown when attempting to access + // The try/catch suppresses exceptions thrown when attempting to access // the "constructor" property of certain host objects, ie. |window.location| try { if ( obj.constructor && @@ -785,13 +800,13 @@ jQuery.ready.promise = function( obj ) { // Handle it asynchronously to allow scripts the opportunity to delay ready setTimeout( jQuery.ready ); - // Standards-based browsers support DOMContentLoaded } else { + // Use the handy event callback - document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + document.addEventListener( "DOMContentLoaded", completed, false ); // A fallback to window.onload, that will always work - window.addEventListener( "load", jQuery.ready, false ); + window.addEventListener( "load", completed, false ); } } return readyList.promise( obj ); @@ -960,13 +975,15 @@ jQuery.Callbacks = function( options ) { } return this; }, - // Control if a given callback is in the list + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. has: function( fn ) { - return jQuery.inArray( fn, list ) > -1; + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); }, // Remove all callbacks from the list empty: function() { list = []; + firingLength = 0; return this; }, // Have the list do nothing anymore @@ -1157,43 +1174,37 @@ jQuery.extend({ return deferred.promise(); } }); -jQuery.support = (function() { +jQuery.support = (function( support ) { + var input = document.createElement("input"), + fragment = document.createDocumentFragment(), + div = document.createElement("div"), + select = document.createElement("select"), + opt = select.appendChild( document.createElement("option") ); - var support, a, select, opt, input, fragment, - div = document.createElement("div"); - - div.innerHTML = "<a>a</a><input type='checkbox'/>"; - - // Support tests won't run in some limited or non-browser environments - a = div.getElementsByTagName("a")[ 0 ]; - if ( !a ) { - return {}; + // Finish early in limited environments + if ( !input.type ) { + return support; } - // First batch of tests - select = document.createElement("select"); - opt = select.appendChild( document.createElement("option") ); - input = div.getElementsByTagName("input")[ 0 ]; + input.type = "checkbox"; - a.style.cssText = "float:left;opacity:.5"; - support = { - // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) - checkOn: !!input.value, + // Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere) + support.checkOn = input.value === ""; - // Must access the parent to make an option select properly - // Support: IE9, IE10 - optSelected: opt.selected, + // Must access the parent to make an option select properly + // Support: IE9, IE10 + support.optSelected = opt.selected; - // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode - boxModel: document.compatMode === "CSS1Compat", + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + support.boxModel = document.compatMode === "CSS1Compat"; - // Will be defined later - reliableMarginRight: true, - boxSizingReliable: true, - pixelPosition: false - }; + // Will be defined later + support.reliableMarginRight = true; + support.boxSizingReliable = true; + support.pixelPosition = false; // Make sure checked status is properly cloned + // Support: IE9, IE10 input.checked = true; support.noCloneChecked = input.cloneNode( true ).checked; @@ -1203,25 +1214,24 @@ jQuery.support = (function() { support.optDisabled = !opt.disabled; // Check if an input maintains its value after becoming a radio + // Support: IE9, IE10, Opera input = document.createElement("input"); input.value = "t"; - input.setAttribute( "type", "radio" ); + input.type = "radio"; support.radioValue = input.value === "t"; // #11217 - WebKit loses check when the name is after the checked attribute input.setAttribute( "checked", "t" ); input.setAttribute( "name", "t" ); - fragment = document.createDocumentFragment(); fragment.appendChild( input ); - // WebKit doesn't clone checked state correctly in fragments + // old WebKit doesn't clone checked state correctly in fragments support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; // Support: Firefox 17+ - // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php - div.setAttribute( "onfocusin", "t" ); - support.focusinBubbles = "onfocusin" in window || div.attributes.onfocusin.expando === false; + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + support.focusinBubbles = "onfocusin" in window; div.style.backgroundClip = "content-box"; div.cloneNode( true ).style.backgroundClip = ""; @@ -1229,9 +1239,9 @@ jQuery.support = (function() { // Run tests that need a body at doc ready jQuery(function() { - var container, marginDiv, tds, + var container, marginDiv, divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", - body = document.getElementsByTagName("body")[0]; + body = document.getElementsByTagName("body")[ 0 ]; if ( !body ) { // Return for frameset docs that don't have a body @@ -1245,8 +1255,9 @@ jQuery.support = (function() { body.appendChild( container ).appendChild( div ); div.innerHTML = ""; div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; - support.boxSizing = ( div.offsetWidth === 4 ); - support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + support.boxSizing = div.offsetWidth === 4; + support.doesNotIncludeMarginInBodyOffset = body.offsetTop !== 1; // Use window.getComputedStyle because jsdom on node.js will break without it. if ( window.getComputedStyle ) { @@ -1267,246 +1278,225 @@ jQuery.support = (function() { } body.removeChild( container ); - - // Null elements to avoid leaks in IE - container = div = tds = marginDiv = null; }); - // Null elements to avoid leaks in IE - select = fragment = opt = a = input = null; - return support; -})(); +})( {} ); -var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, +/* + Implementation Summary + + 1. Enforce API surface and semantic compatibility with 1.9.x branch + 2. Improve the module's maintainability by reducing the storage + paths to a single mechanism. + 3. Use the same single mechanism to support "private" and "user" data. + 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) + 5. Avoid exposing implementation details on user objects (eg. expando properties) + 6. Provide a clear path for implementation upgrade to WeakMap in 2014 +*/ +var data_user, data_priv, + rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, rmultiDash = /([A-Z])/g; - -function internalData( elem, name, data, pvt /* Internal Use Only */ ){ - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, +function Data() { + this.cache = {}; + this.expando = jQuery.expando + Math.random(); +} - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; +Data.uid = 1; - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; - } else { - id = internalKey; - } - } +Data.prototype = { + key: function( owner ) { + var descriptor = {}, + // Check if the owner object already has a cache key + unlock = owner[ this.expando ]; - if ( !cache[ id ] ) { - cache[ id ] = {}; + // If not, create one + if ( !unlock ) { + unlock = Data.uid++; + descriptor[ this.expando ] = { value: unlock }; + + // Secure it in a non-enumerable, non-writable property + try { + Object.defineProperties( owner, descriptor ); - // Avoids exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; + // Support: Android<4 + // Fallback to a less secure definition + } catch ( e ) { + descriptor[ this.expando ] = unlock; + jQuery.extend( owner, descriptor ); + } } - } - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + // Ensure the cache object + if ( !this.cache[ unlock ] ) { + this.cache[ unlock ] = {}; } - } - thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( getByName ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; + return unlock; + }, + set: function( owner, data, value ) { + var prop, + // There may be an unlock assigned to this node, + // if there is no entry for this "owner", create one inline + // and set the unlock as though an owner entry had always existed + unlock = this.key( owner ), + cache = this.cache[ unlock ]; - // Test for null|undefined property data - if ( ret == null ) { + // Handle: [ owner, key, value ] args + if ( typeof data === "string" ) { + cache[ data ] = value; - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; + // Handle: [ owner, { properties } ] args + } else { + // Support an expectation from the old data system where plain + // objects used to initialize would be set to the cache by + // reference, instead of having properties and values copied. + // Note, this will kill the connection between + // "this.cache[ unlock ]" and "cache" + if ( jQuery.isEmptyObject( cache ) ) { + this.cache[ unlock ] = data; + // Otherwise, copy the properties one-by-one to the cache object + } else { + for ( prop in data ) { + cache[ prop ] = data[ prop ]; + } + } } - } else { - ret = thisCache; - } - - return ret; -} -function internalRemoveData( elem, name, pvt /* For internal use only */ ){ - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, i, l, - - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { + return this; + }, + get: function( owner, key ) { + // Either a valid cache is found, or will be created. + // New caches will be created and the unlock returned, + // allowing direct access to the newly created + // empty data object. + var cache = this.cache[ this.key( owner ) ]; + + return key === undefined ? + cache : cache[ key ]; + }, + access: function( owner, key, value ) { + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ((key && typeof key === "string") && value === undefined) ) { + return this.get( owner, key ); + } + + // [*]When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, name, + unlock = this.key( owner ), + cache = this.cache[ unlock ]; - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split(" "); - } - } - } else { + if ( key === undefined ) { + this.cache[ unlock ] = {}; + } else { + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { // If "name" is an array of keys... // When data is initially created, via ("key", "val") signature, // keys will be converted to camelCase. // Since there is no way to tell _how_ a key was added, remove // both plain key and camelCase key. #12786 // This will only penalize the array argument path. - name = name.concat( jQuery.map( name, jQuery.camelCase ) ); - } - - for ( i = 0, l = name.length; i < l; i++ ) { - delete thisCache[ name[i] ]; + name = key.concat( key.map( jQuery.camelCase ) ); + } else { + // Try the string as a key before any manipulation + if ( key in cache ) { + name = [ key ]; + } else { + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + name = jQuery.camelCase( key ); + name = name in cache ? + [ name ] : ( name.match( core_rnotwhite ) || [] ); + } } - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { - return; + i = name.length; + while ( i-- ) { + delete cache[ name[i] ]; } } + }, + hasData: function( owner ) { + return !jQuery.isEmptyObject( + this.cache[ this.key( owner ) ] + ); + }, + discard: function( owner ) { + delete this.cache[ this.key( owner ) ]; } +}; - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject( cache[ id ] ) ) { - return; - } - } - - // Destroy the cache - if ( isNode ) { - jQuery.cleanData( [ elem ], true ); - - // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) - } else if ( jQuery.support.deleteExpando || cache != cache.window ) { - delete cache[ id ]; - - // When all else fails, null - } else { - cache[ id ] = null; - } +// This will be used by remove()/cleanData() in manipulation to sever +// remaining references to node objects. One day we'll replace the dual +// arrays with a WeakMap and this won't be an issue. +// (Splices the data objects out of the internal cache arrays) +function data_discard( owner ) { + data_user.discard( owner ); + data_priv.discard( owner ); } -jQuery.extend({ - cache: {}, +// These may be used throughout the jQuery core codebase +data_user = new Data(); +data_priv = new Data(); - // Unique for each copy of jQuery on the page - // Non-digits removed to match rinlinejQuery - expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true +jQuery.extend({ + // This is no longer relevant to jQuery core, but must remain + // supported for the sake of jQuery 1.9.x API surface compatibility. + acceptData: function() { + return true; }, hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - return !!elem && !isEmptyDataObject( elem ); + return data_user.hasData( elem ) || data_priv.hasData( elem ); }, data: function( elem, name, data ) { - return internalData( elem, name, data, false ); + return data_user.access( elem, name, data ); }, removeData: function( elem, name ) { - return internalRemoveData( elem, name, false ); + return data_user.remove( elem, name ); }, - // For internal use only. + // TODO: Replace all calls to _data and _removeData with direct + // calls to + // + // data_priv.access( elem, name, data ); + // + // data_priv.remove( elem, name ); + // _data: function( elem, name, data ) { - return internalData( elem, name, data, true ); - }, - - _removeData: function( elem, name ) { - return internalRemoveData( elem, name, true ); + return data_priv.access( elem, name, data ); }, - // A method for determining if a DOM node can handle the data expando - acceptData: function( elem ) { - var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; - - // nodes accept data unless otherwise specified; rejection can be conditional - return !noData || noData !== true && elem.getAttribute("classid") === noData; + _removeData: function( elem, name ) { + return data_priv.remove( elem, name ); } }); @@ -1520,20 +1510,19 @@ jQuery.fn.extend({ // Gets all values if ( key === undefined ) { if ( this.length ) { - data = jQuery.data( elem ); + data = data_user.get( elem ); - if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { attrs = elem.attributes; for ( ; i < attrs.length; i++ ) { name = attrs[i].name; - if ( !name.indexOf( "data-" ) ) { + if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.substring(5) ); - dataAttr( elem, name, data[ name ] ); } } - jQuery._data( elem, "parsedAttrs", true ); + data_priv.set( elem, "hasDataAttrs", true ); } } @@ -1543,37 +1532,78 @@ jQuery.fn.extend({ // Sets multiple values if ( typeof key === "object" ) { return this.each(function() { - jQuery.data( this, key ); + data_user.set( this, key ); }); } return jQuery.access( this, function( value ) { + var data, + camelKey = jQuery.camelCase( key ); + // Get the Data... if ( value === undefined ) { - // Try to fetch any internally stored data first - return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + + // Attempt to get data from the cache + // with the key as-is + data = data_user.get( elem, key ); + if ( data !== undefined ) { + return data; + } + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key, undefined ); + if ( data !== undefined ) { + return data; + } + + // As a last resort, attempt to find + // the data by checking AGAIN, but with + // a camelCased key. + data = data_user.get( elem, camelKey ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return undefined; } + // Set the data... this.each(function() { - jQuery.data( this, key, value ); + // First, attempt to store a copy or reference of any + // data that might've been store with a camelCased key. + var data = data_user.get( this, camelKey ); + + // For HTML5 data-* attribute interop, we have to + // store property names with dashes in a camelCase form. + // This might not apply to all properties...* + data_user.set( this, camelKey, value ); + + // *... In the case of properties that might ACTUALLY + // have dashes, we need to also store a copy of that + // unchanged property. + if ( /-/.test( key ) && data !== undefined ) { + data_user.set( this, key, value ); + } }); }, null, value, arguments.length > 1, null, true ); }, removeData: function( key ) { return this.each(function() { - jQuery.removeData( this, key ); + data_user.remove( this, key ); }); } }); function dataAttr( elem, key, data ) { + var name; + // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - + name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { @@ -1583,37 +1613,18 @@ function dataAttr( elem, key, data ) { data === "null" ? null : // Only convert to a number if it doesn't change the string +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; + rbrace.test( data ) ? + JSON.parse( data ) : data; } catch( e ) {} // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - + data_user.set( elem, key, data ); } else { data = undefined; } } - return data; } - -// checks a cache object for emptiness -function isEmptyDataObject( obj ) { - var name; - for ( name in obj ) { - - // if the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { - continue; - } - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} jQuery.extend({ queue: function( elem, type, data ) { var queue; @@ -1897,7 +1908,7 @@ jQuery.fn.extend({ } // Toggle whole class name - } else if ( type === "undefined" || type === "boolean" ) { + } else if ( type === core_strundefined || type === "boolean" ) { if ( this.className ) { // store className if set jQuery._data( this, "__className__", this.className ); @@ -2059,7 +2070,7 @@ jQuery.extend({ } // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { + if ( typeof elem.getAttribute === core_strundefined ) { return jQuery.prop( elem, name, value ); } @@ -2092,7 +2103,7 @@ jQuery.extend({ // In IE9+, Flash objects don't have .getAttribute (#12945) // Support: IE9+ - if ( typeof elem.getAttribute !== "undefined" ) { + if ( typeof elem.getAttribute !== core_strundefined ) { ret = elem.getAttribute( name ); } @@ -2285,9 +2296,9 @@ jQuery.event = { var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, - // Don't attach events to noData or text/comment nodes (but allow plain objects) - elemData = elem.nodeType !== 3 && elem.nodeType !== 8 && jQuery._data( elem ); + elemData = data_priv.get( elem ); + // Don't attach events to noData or text/comment nodes (but allow plain objects) if ( !elemData ) { return; } @@ -2312,7 +2323,7 @@ jQuery.event = { eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; @@ -2327,6 +2338,11 @@ jQuery.event = { tmp = rtypenamespace.exec( types[t] ) || []; type = origType = tmp[1]; namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } // If event changes its type, use the special event handlers for the changed type special = jQuery.event.special[ type ] || {}; @@ -2391,7 +2407,7 @@ jQuery.event = { var j, origCount, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, - elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + elemData = data_priv.hasData( elem ) && data_priv.get( elem ); if ( !elemData || !(events = elemData.events) ) { return; @@ -2463,8 +2479,8 @@ jQuery.event = { var i, cur, tmp, bubbleType, ontype, handle, special, eventPath = [ elem || document ], - type = event.type || event, - namespaces = event.namespace ? event.namespace.split(".") : []; + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; cur = tmp = elem = elem || document; @@ -2491,7 +2507,8 @@ jQuery.event = { event : new jQuery.Event( type, typeof event === "object" && event ); - event.isTrigger = true; + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; event.namespace = namespaces.join("."); event.namespace_re = event.namespace ? new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : @@ -2742,10 +2759,18 @@ jQuery.event = { } // Create a writable copy of the event object and normalize some properties - var i, prop, + var i, prop, copy, + type = event.type, originalEvent = event, - fixHook = jQuery.event.fixHooks[ event.type ] || {}, - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; event = new jQuery.Event( originalEvent ); @@ -2816,7 +2841,8 @@ jQuery.event = { var e = jQuery.extend( new jQuery.Event(), event, - { type: type, + { + type: type, isSimulated: true, originalEvent: {} } @@ -3071,35 +3097,11 @@ jQuery.fn.extend({ if ( elem ) { return jQuery.event.trigger( type, data, elem, true ); } - }, - - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -}); - -jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; - - if ( rkeyEvent.test( name ) ) { - jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; - } - - if ( rmouseEvent.test( name ) ) { - jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; } }); /*! * Sizzle CSS Selector Engine - * Copyright 2012 jQuery Foundation and other contributors + * Copyright 2013 jQuery Foundation and other contributors * Released under the MIT license * http://sizzlejs.com/ */ @@ -3111,8 +3113,9 @@ var i, getText, isXML, compile, - hasDuplicate, outermostContext, + recompare, + sortInput, // Local document vars setDocument, @@ -3123,7 +3126,6 @@ var i, rbuggyMatches, matches, contains, - sortOrder, // Instance-specific data expando = "sizzle" + -(new Date()), @@ -3134,6 +3136,8 @@ var i, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), + hasDuplicate = false, + sortOrder = function() { return 0; }, // General-purpose constants strundefined = typeof undefined, @@ -3142,6 +3146,7 @@ var i, // Array methods arr = [], pop = arr.pop, + push_native = arr.push, push = arr.push, slice = arr.slice, // Use a stripped-down indexOf if we can't use a native one @@ -3208,7 +3213,7 @@ var i, rsibling = /[\x20\t\r\n\f]*[+~]/, - rnative = /\{\s*\[native code\]\s*\}/, + rnative = /^[^{]+\{\s*\[native code/, // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, @@ -3233,17 +3238,32 @@ var i, String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }; -// Use a stripped-down slice if we can't use a native one +// Optimize for push.apply( _, NodeList ) try { - slice.call( docElem.childNodes, 0 )[0].nodeType; + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { - slice = function( i ) { - var elem, - results = []; - for ( ; (elem = this[i]); i++ ) { - results.push( elem ); + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; } - return results; }; } @@ -3292,7 +3312,7 @@ function assert( fn ) { var div = document.createElement("div"); try { - return fn( div ); + return !!fn( div ); } catch (e) { return false; } finally { @@ -3352,12 +3372,12 @@ function Sizzle( selector, context, results, seed ) { // Speed-up: Sizzle("TAG") } else if ( match[2] ) { - push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); + push.apply( results, context.getElementsByTagName( selector ) ); return results; // Speed-up: Sizzle(".CLASS") } else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) { - push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); + push.apply( results, context.getElementsByClassName( m ) ); return results; } } @@ -3393,9 +3413,9 @@ function Sizzle( selector, context, results, seed ) { if ( newSelector ) { try { - push.apply( results, slice.call( newContext.querySelectorAll( - newSelector - ), 0 ) ); + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); return results; } catch(qsaError) { } finally { @@ -3469,13 +3489,17 @@ setDocument = Sizzle.setDocument = function( node ) { return div.getElementsByClassName("e").length === 2; }); - // Check if getElementById returns elements by name // Check if getElementsByName privileges form controls or returns elements by ID + // If so, assume (for broader support) that getElementById returns elements by name support.getByName = assert(function( div ) { // Inject content div.id = expando + 0; - div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>"; - docElem.insertBefore( div, docElem.firstChild ); + // Support: Windows 8 Native Apps + // Assigning innerHTML with "name" attributes throws uncatchable exceptions + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx + div.appendChild( document.createElement("a") ).setAttribute( "name", expando ); + div.appendChild( document.createElement("i") ).setAttribute( "name", expando ); + docElem.appendChild( div ); // Test var pass = doc.getElementsByName && @@ -3483,7 +3507,6 @@ setDocument = Sizzle.setDocument = function( node ) { doc.getElementsByName( expando ).length === 2 + // buggy browsers will return more than the correct 0 doc.getElementsByName( expando + 0 ).length; - support.getIdNotName = !doc.getElementById( expando ); // Cleanup docElem.removeChild( div ); @@ -3491,6 +3514,14 @@ setDocument = Sizzle.setDocument = function( node ) { return pass; }); + // Support: Webkit<537.32 + // Detached nodes confoundingly follow *each other* + support.sortDetached = assert(function( div1 ) { + return div1.compareDocumentPosition && + // Should return 1, but Webkit returns 4 (following) + (div1.compareDocumentPosition( document.createElement("div") ) & 1); + }); + // IE6/7 return modified attributes Expr.attrHandle = assert(function( div ) { div.innerHTML = "<a href='#'></a>"; @@ -3508,7 +3539,7 @@ setDocument = Sizzle.setDocument = function( node ) { }; // ID find and filter - if ( support.getIdNotName ) { + if ( support.getByName ) { Expr.find["ID"] = function( id, context ) { if ( typeof context.getElementById !== strundefined && !documentIsXML ) { var m = context.getElementById( id ); @@ -3559,7 +3590,7 @@ setDocument = Sizzle.setDocument = function( node ) { // Filter out possible comments if ( tag === "*" ) { - for ( ; (elem = results[i]); i++ ) { + while ( (elem = results[i++]) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } @@ -3658,7 +3689,7 @@ setDocument = Sizzle.setDocument = function( node ) { } rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = new RegExp( rbuggyMatches.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); // Element contains another // Purposefully does not implement inclusive descendent @@ -3687,26 +3718,38 @@ setDocument = Sizzle.setDocument = function( node ) { // Document order sorting sortOrder = docElem.compareDocumentPosition ? function( a, b ) { - var compare; + // Flag for duplicate removal if ( a === b ) { hasDuplicate = true; return 0; } - if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) { - if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) { - if ( a === doc || contains( preferredDoc, a ) ) { + var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); + + if ( compare ) { + // Disconnected nodes + if ( compare & 1 || + (recompare && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || contains(preferredDoc, a) ) { return -1; } - if ( b === doc || contains( preferredDoc, b ) ) { + if ( b === doc || contains(preferredDoc, b) ) { return 1; } - return 0; + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; } + return compare & 4 ? -1 : 1; } + // Not directly comparable, sort on existence of method return a.compareDocumentPosition ? -1 : 1; } : function( a, b ) { @@ -3717,15 +3760,11 @@ setDocument = Sizzle.setDocument = function( node ) { ap = [ a ], bp = [ b ]; - // The nodes are identical, we can exit early + // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; return 0; - // Fallback to using sourceIndex (in IE) if it's available on both nodes - } else if ( a.sourceIndex && b.sourceIndex ) { - return ( ~b.sourceIndex || MAX_NEGATIVE ) - ( contains( preferredDoc, a ) && ~a.sourceIndex || MAX_NEGATIVE ); - // Parentless nodes are either documents or disconnected } else if ( !aup || !bup ) { return a === doc ? -1 : @@ -3764,12 +3803,6 @@ setDocument = Sizzle.setDocument = function( node ) { 0; }; - // Always assume the presence of duplicates if sort doesn't - // pass them to our comparison function (as in Google Chrome). - hasDuplicate = false; - [0, 0].sort( sortOrder ); - support.detectDuplicates = hasDuplicate; - return document; }; @@ -3842,16 +3875,19 @@ Sizzle.error = function( msg ) { Sizzle.uniqueSort = function( results ) { var elem, duplicates = [], - i = 1, - j = 0; + j = 0, + i = 0; // Unless we *know* we can detect duplicates, assume their presence hasDuplicate = !support.detectDuplicates; + // Compensate for sort limitations + recompare = !support.sortDetached; + sortInput = !support.sortStable && results.slice( 0 ); results.sort( sortOrder ); if ( hasDuplicate ) { - for ( ; (elem = results[i]); i++ ) { - if ( elem === results[ i - 1 ] ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { j = duplicates.push( i ); } } @@ -3863,12 +3899,27 @@ Sizzle.uniqueSort = function( results ) { return results; }; +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns Returns -1 if a precedes b, 1 if a follows b + */ function siblingCheck( a, b ) { - var cur = a && b && a.nextSibling; + var cur = b && a, + diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } - for ( ; cur; cur = cur.nextSibling ) { - if ( cur === b ) { - return -1; + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } } } @@ -4078,9 +4129,9 @@ Expr = Sizzle.selectors = { operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.substr( result.length - check.length ) === check : + operator === "$=" ? check && result.slice( -check.length ) === check : operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; }; }, @@ -4498,7 +4549,7 @@ function toSelector( tokens ) { function addCombinator( matcher, combinator, base ) { var dir = combinator.dir, - checkNonElements = base && combinator.dir === "parentNode", + checkNonElements = base && dir === "parentNode", doneName = done++; return combinator.first ? @@ -4741,8 +4792,8 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { contextBackup = outermostContext, // We must always have either seed elements or context elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), - // Nested matchers should use non-integer dirruns - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E); + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); if ( outermost ) { outermostContext = context !== document && context; @@ -4750,9 +4801,11 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { } // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below for ( ; (elem = elems[i]) != null; i++ ) { if ( byElement && elem ) { - for ( j = 0; (matcher = elementMatchers[j]); j++ ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { if ( matcher( elem, context, xml ) ) { results.push( elem ); break; @@ -4779,10 +4832,10 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { } // Apply set filters to unmatched elements - // `i` starts as a string, so matchedCount would equal "00" if there are no elements matchedCount += i; if ( bySet && i !== matchedCount ) { - for ( j = 0; (matcher = setMatchers[j]); j++ ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { matcher( unmatched, setMatched, context, xml ); } @@ -4875,7 +4928,7 @@ function select( selector, context, results, seed ) { context.nodeType === 9 && !documentIsXML && Expr.relative[ tokens[1].type ] ) { - context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0]; + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; if ( !context ) { return results; } @@ -4884,7 +4937,8 @@ function select( selector, context, results, seed ) { } // Fetch a seed set for right-to-left matching - for ( i = matchExpr["needsContext"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) { + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { token = tokens[i]; // Abort if we hit a combinator @@ -4902,7 +4956,7 @@ function select( selector, context, results, seed ) { tokens.splice( i, 1 ); selector = seed.length && toSelector( tokens ); if ( !selector ) { - push.apply( results, slice.call( seed, 0 ) ); + push.apply( results, seed ); return results; } @@ -4930,12 +4984,20 @@ Expr.pseudos["nth"] = Expr.pseudos["eq"]; // Easy API for creating new setFilters function setFilters() {} -Expr.filters = setFilters.prototype = Expr.pseudos; +setFilters.prototype = Expr.filters = Expr.pseudos; Expr.setFilters = new setFilters(); +// Check sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + // Initialize with the default document setDocument(); +// Always assume the presence of duplicates if sort doesn't +// pass them to our comparison function (as in Google Chrome). +[0, 0].sort( sortOrder ); +support.detectDuplicates = hasDuplicate; + // Override sizzle attribute retrieval Sizzle.attr = jQuery.attr; jQuery.find = Sizzle; @@ -4982,7 +5044,7 @@ jQuery.fn.extend({ } // Needed because $( selector, context ) becomes $( context ).find( selector ) - matched = this.pushStack( jQuery.unique( matched ) ); + matched = this.pushStack( l > 1 ? jQuery.unique( matched ) : matched ); matched.selector = ( this.selector ? this.selector + " " : "" ) + selector; return matched; }, @@ -5030,14 +5092,18 @@ jQuery.fn.extend({ 0; for ( ; i < l; i++ ) { - cur = this[ i ]; + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : - while ( cur && cur.ownerDocument && cur !== context ) { - if ( pos ? pos.index( cur ) > -1 : jQuery.find.matchesSelector( cur, selectors ) ) { - matched.push( cur ); + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + cur = matched.push( cur ); break; } - cur = cur.parentElement; } } @@ -5082,44 +5148,46 @@ jQuery.fn.extend({ } }); -jQuery.fn.andSelf = jQuery.fn.addBack; +function sibling( cur, dir ) { + while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} + + return cur; +} jQuery.each({ parent: function( elem ) { - return elem.parentElement; + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { - return jQuery.dir( elem, "parentElement" ); + return jQuery.dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentElement", until ); + return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { - return elem.nextElementSibling; + return sibling( elem, "nextSibling" ); }, prev: function( elem ) { - return elem.previousElementSibling; + return sibling( elem, "previousSibling" ); }, nextAll: function( elem ) { - return jQuery.dir( elem, "nextElementSibling" ); + return jQuery.dir( elem, "nextSibling" ); }, prevAll: function( elem ) { - return jQuery.dir( elem, "previousElementSibling" ); + return jQuery.dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextElementSibling", until ); + return jQuery.dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousElementSibling", until ); + return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { - var children = elem.children; - - // documentFragment or document does not have children property - return children ? jQuery.merge( [], children ) : jQuery.sibling( elem.firstChild ); + return jQuery.sibling( elem.firstChild ); }, contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? @@ -5164,14 +5232,17 @@ jQuery.extend({ }, dir: function( elem, dir, until ) { - var cur = elem[ dir ], - matched = []; + var matched = [], + truncate = until !== undefined; - while ( cur && ( !until || !jQuery( cur ).is( until ) ) ) { - matched.push( cur ); - cur = cur[ dir ]; + while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } } - return matched; }, @@ -5243,15 +5314,17 @@ var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^> // Support: IE 9 option: [ 1, "<select multiple='multiple'>", "</select>" ], - tr: [ 1, "<table>", "</table>" ], - td: [ 3, "<table><tr>", "</tr></table>" ], + thead: [ 1, "<table>", "</table>" ], + tr: [ 2, "<table><tbody>", "</tbody></table>" ], + td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], + _default: [ 0, "", "" ] }; // Support: IE 9 wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead = wrapMap.col = wrapMap.tr; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.col = wrapMap.thead; wrapMap.th = wrapMap.td; jQuery.fn.extend({ @@ -5467,21 +5540,17 @@ jQuery.fn.extend({ value = jQuery( value ).not( this ).detach(); } - return this.domManip( [ value ], true, function( elem ) { - var next = this.nextSibling, - parent = this.parentNode; - - if ( parent && this.nodeType === 1 || this.nodeType === 11 ) { - - jQuery( this ).remove(); + return value !== "" ? + this.domManip( [ value ], true, function( elem ) { + var next = this.nextSibling, + parent = this.parentNode; - if ( next ) { - next.parentNode.insertBefore( elem, next ); - } else { - parent.appendChild( elem ); + if ( parent ) { + jQuery( this ).remove(); + parent.insertBefore( elem, next ); } - } - }); + }) : + this.remove(); }, detach: function( selector ) { @@ -5535,7 +5604,9 @@ jQuery.fn.extend({ // Keep references to cloned scripts for later restoration if ( hasScripts ) { - core_push.apply( scripts, getAll( node, "script" ) ); + // Support: QtWebKit + // jQuery.merge because core_push.apply(_, arraylike) throws + jQuery.merge( scripts, getAll( node, "script" ) ); } } @@ -5558,7 +5629,7 @@ jQuery.fn.extend({ for ( i = 0; i < hasScripts; i++ ) { node = scripts[ i ]; if ( rscriptType.test( node.type || "" ) && - !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { if ( node.src ) { // Hope ajax is available... @@ -5601,7 +5672,9 @@ jQuery.each({ elems = i === last ? this : this.clone( true ); jQuery( insert[ i ] )[ original ]( elems ); - core_push.apply( ret, elems ); + // Support: QtWebKit + // .get() because core_push.apply(_, arraylike) throws + core_push.apply( ret, elems.get() ); } return this.pushStack( ret ); @@ -5665,7 +5738,9 @@ jQuery.extend({ // Add nodes directly if ( jQuery.type( elem ) === "object" ) { - core_push.apply( nodes, elem.nodeType ? [ elem ] : elem ); + // Support: QtWebKit + // jQuery.merge because core_push.apply(_, arraylike) throws + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); // Convert non-html into a text node } else if ( !rhtml.test( elem ) ) { @@ -5686,7 +5761,9 @@ jQuery.extend({ tmp = tmp.firstChild; } - core_push.apply( nodes, tmp.childNodes ); + // Support: QtWebKit + // jQuery.merge because core_push.apply(_, arraylike) throws + jQuery.merge( nodes, tmp.childNodes ); // Remember the top-level container tmp = fragment.firstChild; @@ -5735,11 +5812,9 @@ jQuery.extend({ }, cleanData: function( elems, /* internal */ acceptData ) { - var id, data, elem, type, + var data, elem, type, l = elems.length, i = 0, - internalKey = jQuery.expando, - cache = jQuery.cache, special = jQuery.event.special; for ( ; i < l; i++ ) { @@ -5747,8 +5822,7 @@ jQuery.extend({ if ( acceptData || jQuery.acceptData( elem ) ) { - id = elem[ internalKey ]; - data = id && cache[ id ]; + data = data_priv.access( elem ); if ( data ) { for ( type in data.events ) { @@ -5760,14 +5834,10 @@ jQuery.extend({ jQuery.removeEvent( elem, type, data.handle ); } } - - // Remove cache only if it was not already removed by jQuery.event.remove - if ( cache[ id ] ) { - delete cache[ id ]; - delete elem[ internalKey ]; - } } } + // Discard any remaining `private` and `user` data + data_discard( elem ); } } }); @@ -5787,7 +5857,6 @@ function restoreScript( elem ) { if ( match ) { elem.type = match[ 1 ]; - } else { elem.removeAttribute("type"); } @@ -5801,38 +5870,49 @@ function setGlobalEval( elems, refElements ) { i = 0; for ( ; i < l; i++ ) { - jQuery._data( elems[ i ], "globalEval", !refElements || jQuery._data( refElements[ i ], "globalEval" ) ); + data_priv.set( + elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) + ); } } function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + if ( dest.nodeType !== 1 ) { return; } - var i, l, type, - oldData = jQuery._data( src ), - curData = jQuery._data( dest, oldData ), - events = oldData.events; + // 1. Copy private data: events, handlers, etc. + if ( data_priv.hasData( src ) ) { + pdataOld = data_priv.access( src ); + pdataCur = jQuery.extend( {}, pdataOld ); + events = pdataOld.events; - if ( events ) { - delete curData.handle; - curData.events = {}; + data_priv.set( dest, pdataCur ); - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } } } } - // make the cloned public data object a copy from the original - if ( curData.data ) { - curData.data = jQuery.extend( {}, curData.data ); + // 2. Copy user data + if ( data_user.hasData( src ) ) { + udataOld = data_user.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + data_user.set( dest, udataCur ); } } + function getAll( context, tag ) { var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : @@ -5912,7 +5992,7 @@ function getStyles( elem ) { } function showHide( elements, show ) { - var elem, + var display, elem, hidden, values = [], index = 0, length = elements.length; @@ -5922,11 +6002,13 @@ function showHide( elements, show ) { if ( !elem.style ) { continue; } + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; if ( show ) { // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not - if ( !values[ index ] && elem.style.display === "none" ) { + if ( !values[ index ] && display === "none" ) { elem.style.display = ""; } @@ -5936,8 +6018,15 @@ function showHide( elements, show ) { if ( elem.style.display === "" && isHidden( elem ) ) { values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); } - } else if ( !values[ index ] && !isHidden( elem ) ) { - jQuery._data( elem, "olddisplay", jQuery.css( elem, "display" ) ); + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } + } } } @@ -6072,7 +6161,7 @@ jQuery.extend({ value += "px"; } - // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // Fixes #8908, it can be done more correctly by specifying setters in cssHooks, // but it would mean to define eight (for every problematic property) identical functions if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { style[ name ] = "inherit"; @@ -6121,7 +6210,7 @@ jQuery.extend({ } // Return, converting to number if forced or a qualifier was provided and val looks numeric - if ( extra ) { + if ( extra === "" || extra ) { num = parseFloat( val ); return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; } @@ -6390,7 +6479,9 @@ jQuery(function() { if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.hidden = function( elem ) { - return elem.offsetWidth === 0 && elem.offsetHeight === 0; + // Support: Opera <= 12.12 + // Opera reports offsetWidths and offsetHeights less than zero on some elements + return elem.offsetWidth <= 0 && elem.offsetHeight <= 0; }; jQuery.expr.filters.visible = function( elem ) { @@ -6428,7 +6519,7 @@ jQuery.each({ var r20 = /%20/g, rbracket = /\[\]$/, rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset)$/i, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, rsubmittable = /^(?:input|select|textarea|keygen)/i; jQuery.fn.extend({ @@ -6524,6 +6615,21 @@ function buildParams( prefix, obj, traditional, add ) { add( prefix, obj ); } } +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; +}); + +jQuery.fn.hover = function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); +}; var // Document location ajaxLocParts, @@ -6784,7 +6890,8 @@ jQuery.extend({ responseFields: { xml: "responseXML", - text: "responseText" + text: "responseText", + json: "responseJSON" }, // Data converters @@ -6792,7 +6899,7 @@ jQuery.extend({ converters: { // Convert anything to text - "* text": window.String, + "* text": String, // Text to html (true = no transformation) "text html": true, @@ -7130,13 +7237,19 @@ jQuery.extend({ // Set readyState jqXHR.readyState = status > 0 ? 4 : 0; + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + // Get response data if ( responses ) { response = ajaxHandleResponses( s, jqXHR, responses ); } + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + // If successful, handle type chaining - if ( status >= 200 && status < 300 || status === 304 ) { + if ( isSuccess ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { @@ -7150,17 +7263,19 @@ jQuery.extend({ } } - // If not modified - if ( status === 304 ) { - isSuccess = true; + // if no content + if ( status === 204 ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { statusText = "notmodified"; - // If we have data + // If we have data, let's convert it } else { - isSuccess = ajaxConvert( s, response ); - statusText = isSuccess.state; - success = isSuccess.data; - error = isSuccess.error; + statusText = response.state; + success = response.data; + error = response.error; isSuccess = !error; } } else { @@ -7220,7 +7335,6 @@ jQuery.extend({ }); /* Handles responses to an ajax request: - * - sets all responseXXX fields accordingly * - finds the right dataType (mediates between content-type and expected dataType) * - returns the corresponding response */ @@ -7228,15 +7342,7 @@ function ajaxHandleResponses( s, jqXHR, responses ) { var ct, type, finalDataType, firstDataType, contents = s.contents, - dataTypes = s.dataTypes, - responseFields = s.responseFields; - - // Fill responseXXX fields - for ( type in responseFields ) { - if ( type in responses ) { - jqXHR[ responseFields[type] ] = responses[ type ]; - } - } + dataTypes = s.dataTypes; // Remove auto dataType and get content-type in the process while( dataTypes[ 0 ] === "*" ) { @@ -7285,20 +7391,14 @@ function ajaxHandleResponses( s, jqXHR, responses ) { } } -// Chain conversions given the request and the original response -function ajaxConvert( s, response ) { - - var conv, conv2, current, tmp, +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, converters = {}, - i = 0, // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(), - prev = dataTypes[ 0 ]; - - // Apply the dataFilter if provided - if ( s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } + dataTypes = s.dataTypes.slice(); // Create converters map with lowercased keys if ( dataTypes[ 1 ] ) { @@ -7307,14 +7407,32 @@ function ajaxConvert( s, response ) { } } - // Convert to each sequential dataType, tolerating list modification - for ( ; (current = dataTypes[++i]); ) { + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { // There's only work to do if current dataType is non-auto - if ( current !== "*" ) { + if ( current === "*" ) { + + current = prev; // Convert response if prev dataType is non-auto and differs from current - if ( prev !== "*" && prev !== current ) { + } else if ( prev !== "*" && prev !== current ) { // Seek a direct converter conv = converters[ prev + " " + current ] || converters[ "* " + current ]; @@ -7324,7 +7442,7 @@ function ajaxConvert( s, response ) { for ( conv2 in converters ) { // If conv2 outputs current - tmp = conv2.split(" "); + tmp = conv2.split( " " ); if ( tmp[ 1 ] === current ) { // If prev can be converted to accepted input @@ -7338,9 +7456,8 @@ function ajaxConvert( s, response ) { // Otherwise, insert the intermediate dataType } else if ( converters[ conv2 ] !== true ) { current = tmp[ 0 ]; - dataTypes.splice( i--, 0, current ); + dataTypes.unshift( tmp[ 1 ] ); } - break; } } @@ -7351,7 +7468,7 @@ function ajaxConvert( s, response ) { if ( conv !== true ) { // Unless errors are allowed to bubble, catch and return them - if ( conv && s["throws"] ) { + if ( conv && s[ "throws" ] ) { response = conv( response ); } else { try { @@ -7362,9 +7479,6 @@ function ajaxConvert( s, response ) { } } } - - // Update prev for next iteration - prev = current; } } @@ -7386,71 +7500,42 @@ jQuery.ajaxSetup({ } }); -// Handle cache's special case and global +// Handle cache's special case and crossDomain jQuery.ajaxPrefilter( "script", function( s ) { if ( s.cache === undefined ) { s.cache = false; } if ( s.crossDomain ) { s.type = "GET"; - s.global = false; } }); // Bind script tag hack transport -jQuery.ajaxTransport( "script", function(s) { - +jQuery.ajaxTransport( "script", function( s ) { // This transport only deals with cross domain requests if ( s.crossDomain ) { - - var script, - head = document.head || jQuery("head")[0] || document.documentElement; - + var script, callback; return { - - send: function( _, callback ) { - - script = document.createElement("script"); - - script.async = true; - - if ( s.scriptCharset ) { - script.charset = s.scriptCharset; - } - - script.src = s.url; - - // Attach handlers for all browsers - script.onload = script.onreadystatechange = function( _, isAbort ) { - - if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { - - // Handle memory leak in IE - script.onload = script.onreadystatechange = null; - - // Remove the script - if ( script.parentNode ) { - script.parentNode.removeChild( script ); - } - - // Dereference the script - script = null; - - // Callback if not abort - if ( !isAbort ) { - callback( 200, "success" ); + send: function( _, complete ) { + script = jQuery("<script>").prop({ + async: true, + charset: s.scriptCharset, + src: s.url + }).on( + "load error", + callback = function( evt ) { + script.remove(); + callback = null; + if ( evt ) { + complete( evt.type === "error" ? 404 : 200, evt.type ); } } - }; - - // Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending - // Use native DOM manipulation to avoid our domManip AJAX trickery - head.insertBefore( script, head.firstChild ); + ); + document.head.appendChild( script[ 0 ] ); }, - abort: function() { - if ( script ) { - script.onload( undefined, true ); + if ( callback ) { + callback(); } } }; @@ -7944,7 +8029,7 @@ function defaultPrefilter( elem, props, opts ) { if ( opts.overflow ) { style.overflow = "hidden"; - anim.done(function() { + anim.always(function() { style.overflow = opts.overflow[ 0 ]; style.overflowX = opts.overflow[ 1 ]; style.overflowY = opts.overflow[ 2 ]; @@ -8067,11 +8152,11 @@ Tween.propHooks = { return tween.elem[ tween.prop ]; } - // passing a non empty string as a 3rd parameter to .css will automatically + // passing an empty string as a 3rd parameter to .css will automatically // attempt a parseFloat and fallback to a string if the parse fails // so, simple values such as "10px" are parsed to Float. // complex values such as "rotate(1rad)" are returned as is. - result = jQuery.css( tween.elem, tween.prop, "auto" ); + result = jQuery.css( tween.elem, tween.prop, "" ); // Empty strings, null, undefined and "auto" are converted to 0. return !result || result === "auto" ? 0 : result; }, @@ -8376,8 +8461,8 @@ jQuery.fn.offset = function( options ) { } var docElem, win, - box = { top: 0, left: 0 }, elem = this[ 0 ], + box = { top: 0, left: 0 }, doc = elem && elem.ownerDocument; if ( !doc ) { @@ -8393,38 +8478,40 @@ jQuery.fn.offset = function( options ) { // If we don't have gBCR, just use 0,0 rather than error // BlackBerry 5, iOS 3 (original iPhone) - if ( typeof elem.getBoundingClientRect !== "undefined" ) { + if ( typeof elem.getBoundingClientRect !== core_strundefined ) { box = elem.getBoundingClientRect(); } win = getWindow( doc ); return { - top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ), - left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 ) + top: box.top + win.pageYOffset - docElem.clientTop, + left: box.left + win.pageXOffset - docElem.clientLeft }; }; jQuery.offset = { setOffset: function( elem, options, i ) { - var position = jQuery.css( elem, "position" ); + var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition, + position = jQuery.css( elem, "position" ), + curElem = jQuery( elem ), + props = {}; - // set position first, in-case top/left are set even on static elem + // Set position first, in-case top/left are set even on static elem if ( position === "static" ) { elem.style.position = "relative"; } - var curElem = jQuery( elem ), - curOffset = curElem.offset(), - curCSSTop = jQuery.css( elem, "top" ), - curCSSLeft = jQuery.css( elem, "left" ), - calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, - props = {}, curPosition = {}, curTop, curLeft; + curOffset = curElem.offset(); + curCSSTop = jQuery.css( elem, "top" ); + curCSSLeft = jQuery.css( elem, "left" ); + calculatePosition = ( position === "absolute" || position === "fixed" ) && ( curCSSTop + curCSSLeft ).indexOf("auto") > -1; - // need to be able to calculate position if either top or left is auto and position is either absolute or fixed + // Need to be able to calculate position if either top or left is auto and position is either absolute or fixed if ( calculatePosition ) { curPosition = curElem.position(); curTop = curPosition.top; curLeft = curPosition.left; + } else { curTop = parseFloat( curCSSTop ) || 0; curLeft = parseFloat( curCSSLeft ) || 0; @@ -8443,6 +8530,7 @@ jQuery.offset = { if ( "using" in options ) { options.using.call( elem, props ); + } else { curElem.css( props ); } @@ -8458,13 +8546,14 @@ jQuery.fn.extend({ } var offsetParent, offset, - parentOffset = { top: 0, left: 0 }, - elem = this[ 0 ]; + elem = this[ 0 ], + parentOffset = { top: 0, left: 0 }; - // fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent + // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent if ( jQuery.css( elem, "position" ) === "fixed" ) { - // we assume that getBoundingClientRect is available when computed position is fixed + // We assume that getBoundingClientRect is available when computed position is fixed offset = elem.getBoundingClientRect(); + } else { // Get *real* offsetParent offsetParent = this.offsetParent(); @@ -8476,26 +8565,26 @@ jQuery.fn.extend({ } // Add offsetParent borders - parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ); + parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ); parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true ); } // Subtract parent offsets and element margins - // note: when an element has margin: auto the offsetLeft and marginLeft - // are the same in Safari causing offset.left to incorrectly be 0 return { - top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), - left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true) + top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), + left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true ) }; }, offsetParent: function() { return this.map(function() { - var offsetParent = this.offsetParent || document.documentElement; + var offsetParent = this.offsetParent || docElem; + while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) { offsetParent = offsetParent.offsetParent; } - return offsetParent || document.documentElement; + + return offsetParent || docElem; }); } }); @@ -8503,22 +8592,20 @@ jQuery.fn.extend({ // Create scrollLeft and scrollTop methods jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) { - var top = /Y/.test( prop ); + var top = "pageYOffset" === prop; jQuery.fn[ method ] = function( val ) { return jQuery.access( this, function( elem, method, val ) { var win = getWindow( elem ); if ( val === undefined ) { - return win ? (prop in win) ? win[ prop ] : - win.document.documentElement[ method ] : - elem[ method ]; + return win ? win[ prop ] : elem[ method ]; } if ( win ) { win.scrollTo( - !top ? val : jQuery( win ).scrollLeft(), - top ? val : jQuery( win ).scrollTop() + !top ? val : window.pageXOffset, + top ? val : window.pageYOffset ); } else { @@ -8529,11 +8616,7 @@ jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( me }); function getWindow( elem ) { - return jQuery.isWindow( elem ) ? - elem : - elem.nodeType === 9 ? - elem.defaultView || elem.parentWindow : - false; + return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView; } // Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { @@ -8579,24 +8662,29 @@ jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { // Limit scope pollution from any deprecated API // (function() { +jQuery.fn.andSelf = jQuery.fn.addBack; + // })(); -// Expose jQuery to the global object -window.jQuery = window.$ = jQuery; - -// Expose jQuery as an AMD module, but only for AMD loaders that -// understand the issues with loading multiple versions of jQuery -// in a page that all might call define(). The loader will indicate -// they have special allowances for multiple jQuery versions by -// specifying define.amd.jQuery = true. Register as a named module, -// since jQuery can be concatenated with other files that may use define, -// but not use a proper concatenation script that understands anonymous -// AMD modules. A named AMD is safest and most robust way to register. -// Lowercase jquery is used because AMD module names are derived from -// file names, and jQuery is normally delivered in a lowercase file name. -// Do this after creating the global so that if an AMD module wants to call -// noConflict to hide this version of jQuery, it will work. -if ( typeof define === "function" && define.amd && define.amd.jQuery ) { - define( "jquery", [], function () { return jQuery; } ); +if ( typeof module === "object" && typeof module.exports === "object" ) { + // Expose jQuery as module.exports in loaders that implement the Node + // module pattern (including browserify). Do not create the global, since + // the user will be storing it themselves locally, and globals are frowned + // upon in the Node module world. + module.exports = jQuery; +} else { + // Otherwise expose jQuery to the global object as usual + window.jQuery = window.$ = jQuery; + + // Register as a named AMD module, since jQuery can be concatenated with other + // files that may use define, but not via a proper concatenation script that + // understands anonymous AMD modules. A named AMD is safest and most robust + // way to register. Lowercase jquery is used because AMD module names are + // derived from file names, and jQuery is normally delivered in a lowercase + // file name. Do this after creating the global so that if an AMD module wants + // to call noConflict to hide this version of jQuery, it will work. + if ( typeof define === "function" && define.amd ) { + define( "jquery", [], function () { return jQuery; } ); + } } -})( window ); +})( this ); diff --git a/frontend/js/lib/underscore.js b/frontend/js/lib/underscore.js index 4d83099..32ca0c1 100644 --- a/frontend/js/lib/underscore.js +++ b/frontend/js/lib/underscore.js @@ -1,12 +1,13 @@ -// Underscore.js 1.4.3 -// http://underscorejs.org -// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore may be freely distributed under the MIT license. +// Underscore.js 1.4.4 +// =================== -(function() { +// > http://underscorejs.org +// > (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. +// > Underscore may be freely distributed under the MIT license. - // Baseline setup - // -------------- +// Baseline setup +// -------------- +(function() { // Establish the root object, `window` in the browser, or `global` on the server. var root = this; @@ -64,7 +65,7 @@ } // Current version. - _.VERSION = '1.4.3'; + _.VERSION = '1.4.4'; // Collection Functions // -------------------- @@ -224,8 +225,9 @@ // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); return _.map(obj, function(value) { - return (_.isFunction(method) ? method : value[method]).apply(value, args); + return (isFunc ? method : value[method]).apply(value, args); }); }; @@ -235,10 +237,10 @@ }; // Convenience version of a common use case of `filter`: selecting only objects - // with specific `key:value` pairs. - _.where = function(obj, attrs) { - if (_.isEmpty(attrs)) return []; - return _.filter(obj, function(value) { + // containing specific `key:value` pairs. + _.where = function(obj, attrs, first) { + if (_.isEmpty(attrs)) return first ? null : []; + return _[first ? 'find' : 'filter'](obj, function(value) { for (var key in attrs) { if (attrs[key] !== value[key]) return false; } @@ -246,6 +248,12 @@ }); }; + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.where(obj, attrs, true); + }; + // Return the maximum element or (element-based computation). // Can't optimize arrays of integers longer than 65,535 elements. // See: https://bugs.webkit.org/show_bug.cgi?id=80797 @@ -567,26 +575,23 @@ // Function (ahem) Functions // ------------------ - // Reusable constructor function for prototype setting. - var ctor = function(){}; - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Binding with arguments is also known as `curry`. - // Delegates to **ECMAScript 5**'s native `Function.bind` if available. - // We check for `func.bind` first, to fail fast when `func` is undefined. + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. _.bind = function(func, context) { - var args, bound; if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - if (!_.isFunction(func)) throw new TypeError; - args = slice.call(arguments, 2); - return bound = function() { - if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); - ctor.prototype = func.prototype; - var self = new ctor; - ctor.prototype = null; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (Object(result) === result) return result; - return self; + var args = slice.call(arguments, 2); + return function() { + return func.apply(context, args.concat(slice.call(arguments))); + }; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. + _.partial = function(func) { + var args = slice.call(arguments, 1); + return function() { + return func.apply(this, args.concat(slice.call(arguments))); }; }; @@ -594,7 +599,7 @@ // all callbacks defined on an object belong to it. _.bindAll = function(obj) { var funcs = slice.call(arguments, 1); - if (funcs.length == 0) funcs = _.functions(obj); + if (funcs.length === 0) funcs = _.functions(obj); each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; @@ -1019,7 +1024,7 @@ max = min; min = 0; } - return min + (0 | Math.random() * (max - min + 1)); + return min + Math.floor(Math.random() * (max - min + 1)); }; // List of HTML entities for escaping. @@ -1075,7 +1080,7 @@ // Useful for temporary DOM ids. var idCounter = 0; _.uniqueId = function(prefix) { - var id = '' + ++idCounter; + var id = ++idCounter + ''; return prefix ? prefix + id : id; }; @@ -1110,6 +1115,7 @@ // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. _.template = function(text, data, settings) { + var render; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. @@ -1148,7 +1154,7 @@ source + "return __p;\n"; try { - var render = new Function(settings.variable || 'obj', '_', source); + render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; diff --git a/frontend/js/models/ReferenceCountedModel.js b/frontend/js/models/ReferenceCountedModel.js index c188e9f..0bd49fd 100644 --- a/frontend/js/models/ReferenceCountedModel.js +++ b/frontend/js/models/ReferenceCountedModel.js @@ -29,9 +29,11 @@ var ReferenceCountingCollection = Backbone.Collection.extend({ var model = new this.model(attrs, options); if (model.id in ReferenceCountedModel._ids) model = ReferenceCountedModel._ids[model.id]; - model.take(); - if (!model._validate(attrs, options)) + if (!model._validate(attrs, options)) { + this.trigger('invalid', this, attrs, options); return false; + } + model.take(); return model; }, _removeReference: function(model) { @@ -40,4 +42,4 @@ var ReferenceCountingCollection = Backbone.Collection.extend({ model.off('all', this._onModelEvent, this); model.put(); } -});
\ No newline at end of file +}); |