42 |
42 |
43 // Create a local reference to a common array method we'll want to use later. |
43 // Create a local reference to a common array method we'll want to use later. |
44 var slice = Array.prototype.slice; |
44 var slice = Array.prototype.slice; |
45 |
45 |
46 // Current version of the library. Keep in sync with `package.json`. |
46 // Current version of the library. Keep in sync with `package.json`. |
47 Backbone.VERSION = '1.4.0'; |
47 Backbone.VERSION = '1.4.1'; |
48 |
48 |
49 // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns |
49 // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns |
50 // the `$` variable. |
50 // the `$` variable. |
51 Backbone.$ = $; |
51 Backbone.$ = $; |
52 |
52 |
514 } |
514 } |
515 unset ? delete current[attr] : current[attr] = val; |
515 unset ? delete current[attr] : current[attr] = val; |
516 } |
516 } |
517 |
517 |
518 // Update the `id`. |
518 // Update the `id`. |
519 if (this.idAttribute in attrs) this.id = this.get(this.idAttribute); |
519 if (this.idAttribute in attrs) { |
|
520 var prevId = this.id; |
|
521 this.id = this.get(this.idAttribute); |
|
522 this.trigger('changeId', this, prevId, options); |
|
523 } |
520 |
524 |
521 // Trigger all relevant attribute changes. |
525 // Trigger all relevant attribute changes. |
522 if (!silent) { |
526 if (!silent) { |
523 if (changes.length) this._pending = options; |
527 if (changes.length) this._pending = options; |
524 for (var i = 0; i < changes.length; i++) { |
528 for (var i = 0; i < changes.length; i++) { |
992 // Get a model from the set by id, cid, model object with id or cid |
996 // Get a model from the set by id, cid, model object with id or cid |
993 // properties, or an attributes object that is transformed through modelId. |
997 // properties, or an attributes object that is transformed through modelId. |
994 get: function(obj) { |
998 get: function(obj) { |
995 if (obj == null) return void 0; |
999 if (obj == null) return void 0; |
996 return this._byId[obj] || |
1000 return this._byId[obj] || |
997 this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj)] || |
1001 this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj, obj.idAttribute)] || |
998 obj.cid && this._byId[obj.cid]; |
1002 obj.cid && this._byId[obj.cid]; |
999 }, |
1003 }, |
1000 |
1004 |
1001 // Returns `true` if the model is in the collection. |
1005 // Returns `true` if the model is in the collection. |
1002 has: function(obj) { |
1006 has: function(obj) { |
1096 comparator: this.comparator |
1100 comparator: this.comparator |
1097 }); |
1101 }); |
1098 }, |
1102 }, |
1099 |
1103 |
1100 // Define how to uniquely identify models in the collection. |
1104 // Define how to uniquely identify models in the collection. |
1101 modelId: function(attrs) { |
1105 modelId: function(attrs, idAttribute) { |
1102 return attrs[this.model.prototype.idAttribute || 'id']; |
1106 return attrs[idAttribute || this.model.prototype.idAttribute || 'id']; |
1103 }, |
1107 }, |
1104 |
1108 |
1105 // Get an iterator of all models in this collection. |
1109 // Get an iterator of all models in this collection. |
1106 values: function() { |
1110 values: function() { |
1107 return new CollectionIterator(this, ITERATOR_VALUES); |
1111 return new CollectionIterator(this, ITERATOR_VALUES); |
1132 if (!attrs.collection) attrs.collection = this; |
1136 if (!attrs.collection) attrs.collection = this; |
1133 return attrs; |
1137 return attrs; |
1134 } |
1138 } |
1135 options = options ? _.clone(options) : {}; |
1139 options = options ? _.clone(options) : {}; |
1136 options.collection = this; |
1140 options.collection = this; |
1137 var model = new this.model(attrs, options); |
1141 |
|
1142 var model; |
|
1143 if (this.model.prototype) { |
|
1144 model = new this.model(attrs, options); |
|
1145 } else { |
|
1146 // ES class methods didn't have prototype |
|
1147 model = this.model(attrs, options); |
|
1148 } |
|
1149 |
1138 if (!model.validationError) return model; |
1150 if (!model.validationError) return model; |
1139 this.trigger('invalid', this, model.validationError, options); |
1151 this.trigger('invalid', this, model.validationError, options); |
1140 return false; |
1152 return false; |
1141 }, |
1153 }, |
1142 |
1154 |
1152 this.length--; |
1164 this.length--; |
1153 |
1165 |
1154 // Remove references before triggering 'remove' event to prevent an |
1166 // Remove references before triggering 'remove' event to prevent an |
1155 // infinite loop. #3693 |
1167 // infinite loop. #3693 |
1156 delete this._byId[model.cid]; |
1168 delete this._byId[model.cid]; |
1157 var id = this.modelId(model.attributes); |
1169 var id = this.modelId(model.attributes, model.idAttribute); |
1158 if (id != null) delete this._byId[id]; |
1170 if (id != null) delete this._byId[id]; |
1159 |
1171 |
1160 if (!options.silent) { |
1172 if (!options.silent) { |
1161 options.index = index; |
1173 options.index = index; |
1162 model.trigger('remove', model, this, options); |
1174 model.trigger('remove', model, this, options); |
1175 }, |
1187 }, |
1176 |
1188 |
1177 // Internal method to create a model's ties to a collection. |
1189 // Internal method to create a model's ties to a collection. |
1178 _addReference: function(model, options) { |
1190 _addReference: function(model, options) { |
1179 this._byId[model.cid] = model; |
1191 this._byId[model.cid] = model; |
1180 var id = this.modelId(model.attributes); |
1192 var id = this.modelId(model.attributes, model.idAttribute); |
1181 if (id != null) this._byId[id] = model; |
1193 if (id != null) this._byId[id] = model; |
1182 model.on('all', this._onModelEvent, this); |
1194 model.on('all', this._onModelEvent, this); |
1183 }, |
1195 }, |
1184 |
1196 |
1185 // Internal method to sever a model's ties to a collection. |
1197 // Internal method to sever a model's ties to a collection. |
1186 _removeReference: function(model, options) { |
1198 _removeReference: function(model, options) { |
1187 delete this._byId[model.cid]; |
1199 delete this._byId[model.cid]; |
1188 var id = this.modelId(model.attributes); |
1200 var id = this.modelId(model.attributes, model.idAttribute); |
1189 if (id != null) delete this._byId[id]; |
1201 if (id != null) delete this._byId[id]; |
1190 if (this === model.collection) delete model.collection; |
1202 if (this === model.collection) delete model.collection; |
1191 model.off('all', this._onModelEvent, this); |
1203 model.off('all', this._onModelEvent, this); |
1192 }, |
1204 }, |
1193 |
1205 |
1197 // in other collections are ignored. |
1209 // in other collections are ignored. |
1198 _onModelEvent: function(event, model, collection, options) { |
1210 _onModelEvent: function(event, model, collection, options) { |
1199 if (model) { |
1211 if (model) { |
1200 if ((event === 'add' || event === 'remove') && collection !== this) return; |
1212 if ((event === 'add' || event === 'remove') && collection !== this) return; |
1201 if (event === 'destroy') this.remove(model, options); |
1213 if (event === 'destroy') this.remove(model, options); |
1202 if (event === 'change') { |
1214 if (event === 'changeId') { |
1203 var prevId = this.modelId(model.previousAttributes()); |
1215 var prevId = this.modelId(model.previousAttributes(), model.idAttribute); |
1204 var id = this.modelId(model.attributes); |
1216 var id = this.modelId(model.attributes, model.idAttribute); |
1205 if (prevId !== id) { |
1217 if (prevId != null) delete this._byId[prevId]; |
1206 if (prevId != null) delete this._byId[prevId]; |
1218 if (id != null) this._byId[id] = model; |
1207 if (id != null) this._byId[id] = model; |
|
1208 } |
|
1209 } |
1219 } |
1210 } |
1220 } |
1211 this.trigger.apply(this, arguments); |
1221 this.trigger.apply(this, arguments); |
1212 } |
1222 } |
1213 |
1223 |
1259 // Construct a value depending on what kind of values should be iterated. |
1269 // Construct a value depending on what kind of values should be iterated. |
1260 var value; |
1270 var value; |
1261 if (this._kind === ITERATOR_VALUES) { |
1271 if (this._kind === ITERATOR_VALUES) { |
1262 value = model; |
1272 value = model; |
1263 } else { |
1273 } else { |
1264 var id = this._collection.modelId(model.attributes); |
1274 var id = this._collection.modelId(model.attributes, model.idAttribute); |
1265 if (this._kind === ITERATOR_KEYS) { |
1275 if (this._kind === ITERATOR_KEYS) { |
1266 value = id; |
1276 value = id; |
1267 } else { // ITERATOR_KEYSVALUES |
1277 } else { // ITERATOR_KEYSVALUES |
1268 value = [id, model]; |
1278 value = [id, model]; |
1269 } |
1279 } |
1710 |
1720 |
1711 // Convert a route string into a regular expression, suitable for matching |
1721 // Convert a route string into a regular expression, suitable for matching |
1712 // against the current location hash. |
1722 // against the current location hash. |
1713 _routeToRegExp: function(route) { |
1723 _routeToRegExp: function(route) { |
1714 route = route.replace(escapeRegExp, '\\$&') |
1724 route = route.replace(escapeRegExp, '\\$&') |
1715 .replace(optionalParam, '(?:$1)?') |
1725 .replace(optionalParam, '(?:$1)?') |
1716 .replace(namedParam, function(match, optional) { |
1726 .replace(namedParam, function(match, optional) { |
1717 return optional ? match : '([^/?]+)'; |
1727 return optional ? match : '([^/?]+)'; |
1718 }) |
1728 }) |
1719 .replace(splatParam, '([^?]*?)'); |
1729 .replace(splatParam, '([^?]*?)'); |
1720 return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); |
1730 return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); |
1721 }, |
1731 }, |
1722 |
1732 |
1723 // Given a route, and a URL fragment that it matches, return the array of |
1733 // Given a route, and a URL fragment that it matches, return the array of |
1724 // extracted decoded parameters. Empty or unmatched parameters will be |
1734 // extracted decoded parameters. Empty or unmatched parameters will be |