diff --git a/bower.json b/bower.json index 77d7d75e85..50d9df266f 100644 --- a/bower.json +++ b/bower.json @@ -27,7 +27,7 @@ "strengthify": "0.4.2", "underscore": "~1.8.0", "bootstrap": "~3.3.5", - "backbone": "~1.2.1", + "backbone": "~1.2.3", "davclient.js": "https://github.com/evert/davclient.js.git", "es6-promise": "https://github.com/jakearchibald/es6-promise.git#~2.3.0" } diff --git a/core/vendor/backbone/.bower.json b/core/vendor/backbone/.bower.json index 578c8ffb66..38a9c03af7 100644 --- a/core/vendor/backbone/.bower.json +++ b/core/vendor/backbone/.bower.json @@ -1,6 +1,5 @@ { "name": "backbone", - "version": "1.2.1", "main": "backbone.js", "dependencies": { "underscore": ">=1.7.0" @@ -20,14 +19,14 @@ "package.json" ], "homepage": "https://github.com/jashkenas/backbone", - "_release": "1.2.1", + "version": "1.2.3", + "_release": "1.2.3", "_resolution": { "type": "version", - "tag": "1.2.1", - "commit": "938a8ff934fd4de4f0009f68d43f500f5920b490" + "tag": "1.2.3", + "commit": "05fde9e201f7e2137796663081105cd6dad12a98" }, "_source": "git://github.com/jashkenas/backbone.git", - "_target": "~1.2.1", - "_originalSource": "backbone", - "_direct": true + "_target": "~1.2.3", + "_originalSource": "backbone" } \ No newline at end of file diff --git a/core/vendor/backbone/backbone.js b/core/vendor/backbone/backbone.js index 58800425c7..c924965621 100644 --- a/core/vendor/backbone/backbone.js +++ b/core/vendor/backbone/backbone.js @@ -1,4 +1,4 @@ -// Backbone.js 1.2.1 +// Backbone.js 1.2.3 // (c) 2010-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Backbone may be freely distributed under the MIT license. @@ -41,10 +41,10 @@ var previousBackbone = root.Backbone; // Create a local reference to a common array method we'll want to use later. - var slice = [].slice; + var slice = Array.prototype.slice; // Current version of the library. Keep in sync with `package.json`. - Backbone.VERSION = '1.2.1'; + Backbone.VERSION = '1.2.3'; // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns // the `$` variable. @@ -68,8 +68,13 @@ // form param named `model`. Backbone.emulateJSON = false; - // Proxy Underscore methods to a Backbone class' prototype using a - // particular attribute as the data argument + // Proxy Backbone class methods to Underscore functions, wrapping the model's + // `attributes` object or collection's `models` array behind the scenes. + // + // collection.filter(function(model) { return model.get('age') > 10 }); + // collection.each(this.addView); + // + // `Function#apply` can be slow so we use the method's arg count, if we know it. var addMethod = function(length, method, attribute) { switch (length) { case 1: return function() { @@ -79,10 +84,10 @@ return _[method](this[attribute], value); }; case 3: return function(iteratee, context) { - return _[method](this[attribute], iteratee, context); + return _[method](this[attribute], cb(iteratee, this), context); }; case 4: return function(iteratee, defaultVal, context) { - return _[method](this[attribute], iteratee, defaultVal, context); + return _[method](this[attribute], cb(iteratee, this), defaultVal, context); }; default: return function() { var args = slice.call(arguments); @@ -97,12 +102,26 @@ }); }; + // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. + var cb = function(iteratee, instance) { + if (_.isFunction(iteratee)) return iteratee; + if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); + if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; + return iteratee; + }; + var modelMatcher = function(attrs) { + var matcher = _.matches(attrs); + return function(model) { + return matcher(model.attributes); + }; + }; + // Backbone.Events // --------------- // 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 + // a custom event channel. You may bind a callback to an event with `on` or + // remove with `off`; `trigger`-ing an event fires all callbacks in // succession. // // var object = {}; @@ -117,26 +136,25 @@ // Iterates over the standard `event, callback` (as well as the fancy multiple // space-separated events `"change blur", callback` and jQuery-style event - // maps `{event: callback}`), reducing them by manipulating `memo`. - // Passes a normalized single event name and callback, as well as any - // optional `opts`. - var eventsApi = function(iteratee, memo, name, callback, opts) { + // maps `{event: callback}`). + var eventsApi = function(iteratee, events, name, callback, opts) { var i = 0, names; if (name && typeof name === 'object') { // Handle event maps. if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback; for (names = _.keys(name); i < names.length ; i++) { - memo = iteratee(memo, names[i], name[names[i]], opts); + events = eventsApi(iteratee, events, names[i], name[names[i]], opts); } } else if (name && eventSplitter.test(name)) { - // Handle space separated event names. + // Handle space separated event names by delegating them individually. for (names = name.split(eventSplitter); i < names.length; i++) { - memo = iteratee(memo, names[i], callback, opts); + events = iteratee(events, names[i], callback, opts); } } else { - memo = iteratee(memo, name, callback, opts); + // Finally, standard events. + events = iteratee(events, name, callback, opts); } - return memo; + return events; }; // Bind an event to a `callback` function. Passing `"all"` will bind @@ -145,8 +163,7 @@ return internalOn(this, name, callback, context); }; - // An internal use `on` function, used to guard the `listening` argument from - // the public API. + // Guard the `listening` argument from the public API. var internalOn = function(obj, name, callback, context, listening) { obj._events = eventsApi(onApi, obj._events || {}, name, callback, { context: context, @@ -163,7 +180,8 @@ }; // Inversion-of-control versions of `on`. Tell *this* object to listen to - // an event in another object... keeping track of what it's listening to. + // an event in another object... keeping track of what it's listening to + // for easier unbinding later. Events.listenTo = function(obj, name, callback) { if (!obj) return this; var id = obj._listenId || (obj._listenId = _.uniqueId('l')); @@ -231,7 +249,6 @@ // The reducing API that removes a callback from the `events` object. var offApi = function(events, name, callback, options) { - // No events to consider. if (!events) return; var i = 0, listening; @@ -286,9 +303,9 @@ }; // Bind an event to only be triggered a single time. After the first time - // the callback is invoked, it will be removed. When multiple events are - // passed in using the space-separated syntax, the event will fire once for every - // event you passed in, not once for a combination of all events + // the callback is invoked, its listener will be removed. If multiple events + // are passed in using the space-separated syntax, the handler will fire + // once for each event, not once for a combination of all events. Events.once = function(name, callback, context) { // Map the event into a `{event: once}` object. var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this)); @@ -476,9 +493,6 @@ var changed = this.changed; var prev = this._previousAttributes; - // Check for changes of `id`. - if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; - // For each `set` attribute, update or delete the current value. for (var attr in attrs) { val = attrs[attr]; @@ -491,6 +505,9 @@ unset ? delete current[attr] : current[attr] = val; } + // Update the `id`. + this.id = this.get(this.idAttribute); + // Trigger all relevant attribute changes. if (!silent) { if (changes.length) this._pending = options; @@ -713,7 +730,8 @@ }); - // Underscore methods that we want to implement on the Model. + // Underscore methods that we want to implement on the Model, mapped to the + // number of arguments they take. var modelMethods = { keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, omit: 0, chain: 1, isEmpty: 1 }; @@ -746,6 +764,16 @@ var setOptions = {add: true, remove: true, merge: true}; var addOptions = {add: true, remove: false}; + // Splices `insert` into `array` at index `at`. + var splice = function(array, insert, at) { + at = Math.min(Math.max(at, 0), array.length); + var tail = Array(array.length - at); + var length = insert.length; + for (var i = 0; i < tail.length; i++) tail[i] = array[i + at]; + for (i = 0; i < length; i++) array[i + at] = insert[i]; + for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i]; + }; + // Define the Collection's inheritable methods. _.extend(Collection.prototype, Events, { @@ -768,7 +796,9 @@ return Backbone.sync.apply(this, arguments); }, - // Add a model, or list of models to the set. + // Add a model, or list of models to the set. `models` may be Backbone + // Models or raw JavaScript objects to be converted to Models, or any + // combination of the two. add: function(models, options) { return this.set(models, _.extend({merge: false}, options, addOptions)); }, @@ -788,83 +818,88 @@ // 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) { + if (models == null) return; + options = _.defaults({}, options, setOptions); if (options.parse && !this._isModel(models)) models = this.parse(models, options); + var singular = !_.isArray(models); - models = singular ? (models ? [models] : []) : models.slice(); - var id, model, attrs, existing, sort; + models = singular ? [models] : models.slice(); + var at = options.at; if (at != null) at = +at; if (at < 0) at += this.length + 1; + + var set = []; + var toAdd = []; + var toRemove = []; + var modelMap = {}; + + var add = options.add; + var merge = options.merge; + var remove = options.remove; + + var sort = false; var sortable = this.comparator && (at == null) && options.sort !== false; var sortAttr = _.isString(this.comparator) ? this.comparator : null; - var toAdd = [], toRemove = [], modelMap = {}; - var add = options.add, merge = options.merge, remove = options.remove; - var order = !sortable && add && remove ? [] : false; - var orderChanged = false; // Turn bare objects into model references, and prevent invalid models // from being added. + var model; for (var i = 0; i < models.length; i++) { - attrs = models[i]; + model = models[i]; // If a duplicate is found, prevent it from being added and // optionally merge it into the existing model. - if (existing = this.get(attrs)) { - if (remove) modelMap[existing.cid] = true; - if (merge && attrs !== existing) { - attrs = this._isModel(attrs) ? attrs.attributes : attrs; + var existing = this.get(model); + if (existing) { + if (merge && model !== existing) { + var attrs = this._isModel(model) ? model.attributes : model; if (options.parse) attrs = existing.parse(attrs, options); existing.set(attrs, options); - if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; + if (sortable && !sort) sort = existing.hasChanged(sortAttr); + } + if (!modelMap[existing.cid]) { + modelMap[existing.cid] = true; + set.push(existing); } models[i] = existing; // If this is a new, valid model, push it to the `toAdd` list. } else if (add) { - model = models[i] = this._prepareModel(attrs, options); - if (!model) continue; - toAdd.push(model); - this._addReference(model, options); + model = models[i] = this._prepareModel(model, options); + if (model) { + toAdd.push(model); + this._addReference(model, options); + modelMap[model.cid] = true; + set.push(model); + } } - - // Do not add multiple models with the same `id`. - model = existing || model; - if (!model) continue; - id = this.modelId(model.attributes); - if (order && (model.isNew() || !modelMap[id])) { - order.push(model); - - // Check to see if this is actually a new model at this index. - orderChanged = orderChanged || !this.models[i] || model.cid !== this.models[i].cid; - } - - modelMap[id] = true; } - // Remove nonexistent models if appropriate. + // Remove stale models. if (remove) { - for (var i = 0; i < this.length; i++) { - if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); + for (i = 0; i < this.length; i++) { + model = this.models[i]; + if (!modelMap[model.cid]) toRemove.push(model); } if (toRemove.length) this._removeModels(toRemove, options); } // See if sorting is needed, update `length` and splice in new models. - if (toAdd.length || orderChanged) { + var orderChanged = false; + var replace = !sortable && add && remove; + if (set.length && replace) { + orderChanged = this.length != set.length || _.some(this.models, function(model, index) { + return model !== set[index]; + }); + this.models.length = 0; + splice(this.models, set, 0); + this.length = this.models.length; + } else if (toAdd.length) { if (sortable) sort = true; - this.length += toAdd.length; - if (at != null) { - for (var i = 0; i < toAdd.length; i++) { - this.models.splice(at + i, 0, toAdd[i]); - } - } else { - if (order) this.models.length = 0; - var orderedModels = order || toAdd; - for (var i = 0; i < orderedModels.length; i++) { - this.models.push(orderedModels[i]); - } - } + splice(this.models, toAdd, at == null ? this.length : at); + this.length = this.models.length; } // Silently sort the collection if appropriate. @@ -872,10 +907,10 @@ // Unless silenced, it's time to fire all appropriate add/sort events. if (!options.silent) { - var addOpts = at != null ? _.clone(options) : options; - for (var i = 0; i < toAdd.length; i++) { - if (at != null) addOpts.index = at + i; - (model = toAdd[i]).trigger('add', model, this, addOpts); + for (i = 0; i < toAdd.length; i++) { + if (at != null) options.index = at + i; + model = toAdd[i]; + model.trigger('add', model, this, options); } if (sort || orderChanged) this.trigger('sort', this, options); if (toAdd.length || toRemove.length) this.trigger('update', this, options); @@ -944,10 +979,7 @@ // Return models with matching attributes. Useful for simple cases of // `filter`. where: function(attrs, first) { - var matches = _.matches(attrs); - return this[first ? 'find' : 'filter'](function(model) { - return matches(model.attributes); - }); + return this[first ? 'find' : 'filter'](attrs); }, // Return the first model with matching attributes. Useful for simple cases @@ -960,16 +992,19 @@ // 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'); + var comparator = this.comparator; + if (!comparator) throw new Error('Cannot sort a set without a comparator'); options || (options = {}); - // Run sort based on type of `comparator`. - if (_.isString(this.comparator) || this.comparator.length === 1) { - this.models = this.sortBy(this.comparator, this); - } else { - this.models.sort(_.bind(this.comparator, this)); - } + var length = comparator.length; + if (_.isFunction(comparator)) comparator = _.bind(comparator, this); + // Run sort based on type of `comparator`. + if (length === 1 || _.isString(comparator)) { + this.models = this.sortBy(comparator); + } else { + this.models.sort(comparator); + } if (!options.silent) this.trigger('sort', this, options); return this; }, @@ -1058,7 +1093,6 @@ }, // Internal method called by both remove and set. - // Returns removed models, or false if nothing is removed. _removeModels: function(models, options) { var removed = []; for (var i = 0; i < models.length; i++) { @@ -1128,29 +1162,16 @@ // right here: var collectionMethods = { forEach: 3, each: 3, map: 3, collect: 3, reduce: 4, foldl: 4, inject: 4, reduceRight: 4, foldr: 4, find: 3, detect: 3, filter: 3, - select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 2, - contains: 2, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, + select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, + contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, - isEmpty: 1, chain: 1, sample: 3, partition: 3 }; + isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, + sortBy: 3, indexBy: 3}; // Mix in each Underscore method as a proxy to `Collection#models`. addUnderscoreMethods(Collection, collectionMethods, 'models'); - // Underscore methods that take a property name as an argument. - var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy']; - - // Use attributes instead of properties. - _.each(attributeMethods, function(method) { - if (!_[method]) return; - Collection.prototype[method] = function(value, context) { - var iterator = _.isFunction(value) ? value : function(model) { - return model.get(value); - }; - return _[method](this.models, iterator, context); - }; - }); - // Backbone.View // ------------- @@ -1174,7 +1195,7 @@ // Cached regex to split keys for `delegate`. var delegateEventSplitter = /^(\S+)\s*(.*)$/; - // List of view options to be merged as properties. + // List of view options to be set as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; // Set up all inheritable **Backbone.View** properties and methods. @@ -1518,7 +1539,7 @@ // falls back to polling. var History = Backbone.History = function() { this.handlers = []; - _.bindAll(this, 'checkUrl'); + this.checkUrl = _.bind(this.checkUrl, this); // Ensure that `History` can be used outside of the browser. if (typeof window !== 'undefined') { @@ -1611,7 +1632,7 @@ this.options = _.extend({root: '/'}, this.options, options); this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; - this._hasHashChange = 'onhashchange' in window; + this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7); this._useHashChange = this._wantsHashChange && this._hasHashChange; this._wantsPushState = !!this.options.pushState; this._hasPushState = !!(this.history && this.history.pushState); @@ -1730,7 +1751,7 @@ // If the root doesn't match, no routes can match either. if (!this.matchRoot()) return false; fragment = this.fragment = this.getFragment(fragment); - return _.any(this.handlers, function(handler) { + return _.some(this.handlers, function(handler) { if (handler.route.test(fragment)) { handler.callback(fragment); return true;