说我有这些Backbone.js模型:
var Truck = Backbone.Model.extend({});
var truck1 = new Truck();
var truck2 = new Truck();
truck1.set("brand", "Ford");
truck2.set("brand", "Toyota");
truck3.set("brand", "Honda");
truck4.set("brand", "Ford");
然后,假设我们有一个Backbone.js集合:
var TruckList = Backbone.Collection.extend({
model: Truck,
comparator: function(truck) {
return truck.get("brand");
};
});
我是一名汽车收藏家,所以有时间将每辆车添加到我的收藏中:
Trucks = new TruckList();
Trucks.add(truck1);
Trucks.add(truck2);
Trucks.add(truck3);
Trucks.add(truck4);
只关注品牌属性,truck4是truck1的副本。我的收藏中不能有重复项。我需要我的收藏品具有独特的价值观。
我的问题是,如何从Backbone.js集合中删除重复的项目?
我应该使用Underscore.js吗?如果是这样,有人可以提供一个工作/可运行的如何执行此操作的示例。
假设如下:
1.Collection未排序
必须对品牌属性值进行删除
Ajax调用以填充Truck的每个实例。这意味着在添加到集合时,您无权访问Truck属性。
答案 0 :(得分:19)
我会覆盖您的TruckList集合中的add
方法,并使用下划线检测那里的重复项并拒绝重复项。像。的东西。
TruckList.prototype.add = function(truck) {
// Using isDupe routine from @Bill Eisenhauer's answer
var isDupe = this.any(function(_truck) {
return _truck.get('brand') === truck.get('brand');
});
// Up to you either return false or throw an exception or silently ignore
// NOTE: DEFAULT functionality of adding duplicate to collection is to IGNORE and RETURN. Returning false here is unexpected. ALSO, this doesn't support the merge: true flag.
// Return result of prototype.add to ensure default functionality of .add is maintained.
return isDupe ? false : Backbone.Collection.prototype.add.call(this, truck);
}
答案 1 :(得分:7)
实现此目的的最简单方法是确保您添加的模型具有唯一ID。默认情况下,Backbone集合不会添加具有重复ID的模型。
test('Collection should not add duplicate models', 1, function() {
var model1 = {
id: "1234"
};
var model2 = {
id: "1234"
};
this.collection.add([model1, model2]);
equal(1, this.collection.length, "collection length should be one when trying to add two duplicate models");
});
答案 2 :(得分:5)
试试这个。它使用任何下划线方法来检测潜在的重复,然后转出(如果是这样)。当然,你可能想要打扮一下,除了更加健壮之外:
TruckList.prototype.add = function(newTruck) {
var isDupe = this.any(function(truck) {
return truck.get('brand') === newTruck.get('brand');
}
if (isDupe) return;
Backbone.Collection.prototype.add.call(this, truck);
}
顺便说一句,我可能会在卡车上编写一个函数来进行欺骗检查,以便收集对这种情况不太了解。
答案 3 :(得分:5)
var TruckList = Backbone.Collection.extend({
model : Truck,
// Using @Peter Lyons' answer
add : function(truck) {
// Using isDupe routine from @Bill Eisenhauer's answer
var isDupe = this.any(function(_truck) {
return _truck.get('brand') === truck.get('brand');
});
if (isDupe) {
// Up to you either return false or throw an exception or silently
// ignore
return false;
}
Backbone.Collection.prototype.add.call(this, truck);
},
comparator : function(truck) {
return truck.get("brand");
} });
VassilisB的答案很有效,但它会覆盖Backbone Collection的add()行为。因此,当您尝试执行此操作时可能会出现错误:
var truckList = new TruckList([{brand: 'Ford'}, {brand: 'Toyota'}]);
所以,我添加了一些检查以避免这些错误:
var TruckList = Backbone.Collection.extend({
model : Truck,
// Using @Peter Lyons' answer
add : function(trucks) {
// For array
trucks = _.isArray(trucks) ? trucks.slice() : [trucks]; //From backbone code itself
for (i = 0, length = trucks.length; i < length; i++) {
var truck = ((trucks[i] instanceof this.model) ? trucks[i] : new this.model(trucks[i] )); // Create a model if it's a JS object
// Using isDupe routine from @Bill Eisenhauer's answer
var isDupe = this.any(function(_truck) {
return _truck.get('brand') === truck.get('brand');
});
if (isDupe) {
// Up to you either return false or throw an exception or silently
// ignore
return false;
}
Backbone.Collection.prototype.add.call(this, truck);
}
},
comparator : function(truck) {
return truck.get("brand");
}});
答案 4 :(得分:3)
我正在使用同样的问题做FileUpload事情,这就是我的做法(coffeescript):
File = Backbone.Model.extend
validate: (args) ->
result
if !@collection.isUniqueFile(args)
result = 'File already in list'
result
Files = Backbone.Collection.extend
model: File
isUniqueFile: (file) ->
found
for f in @models
if f.get('name') is file.name
found = f
break
if found
false
else
true
......就是这样。在File中自动引用集合对象,并在集合上调用操作时自动调用Validation,在本例中为Add。
答案 5 :(得分:2)
Underscore.js是backbone.js的预先要求,为此提供了一个函数:http://documentcloud.github.com/underscore/#uniq
示例:
_.uniq([1,1,1,1,1,2,3,4,5]); // returns [1,2,3,4,5]
答案 6 :(得分:1)
不确定这是对Backbone还是下划线的更新,但where()
函数在Backbone 0.9.2中可以为您进行匹配:
TruckList.prototype.add = function(truck) {
var matches = this.where({name: truck.get('brand')});
if (matches.length > 0) {
//Up to you either return false or throw an exception or silently ignore
return false;
}
Backbone.Collection.prototype.add.call(this, truck);
}
答案 7 :(得分:1)
我更喜欢像这样覆盖add方法。
var TruckList = Backbone.Collection.extend({
model : Truck,
// Using @Peter Lyons' answer
add : function(truck) {
// Using isDupe routine from @Bill Eisenhauer's answer
var isDupe = this.any(function(_truck) {
return _truck.get('brand') === truck.get('brand');
});
if (isDupe) {
// Up to you either return false or throw an exception or silently
// ignore
return false;
}
Backbone.Collection.prototype.add.call(this, truck);
},
comparator : function(truck) {
return truck.get("brand");
} });
答案 8 :(得分:0)
试试这个......
var TruckList = Backbone.Collection.extend({
model: Truck,
comparator: function(truck) {
return truck.get("brand");
},
wherePartialUnique: function(attrs) {
// this method is really only tolerant of string values. you can't do partial
// matches on arrays, objects, etc. use collection.where for that
if (_.isEmpty(attrs)) return [];
var seen = [];
return this.filter(function(model) {
for (var key in attrs) {
// sometimes keys are empty. that's bad, so let's not include it in a unique result set
// you might want empty keys though, so comment the next line out if you do.
if ( _.isEmpty(model.get(key).trim()) ) return false;
// on to the filtering...
if (model.get(key).toLowerCase().indexOf(attrs[key].toLowerCase()) >= 0) {
if (seen.indexOf( model.get(key) ) >= 0 ) return false;
seen.push(model.get(key));
return true;
} else {
return false;
}
}
return true;
});
}
});
要记住的一些事情:
这是基于backbone.collection.where方法,与该方法不同,它将尝试对集合中的模型属性进行部分匹配。如果你不想这样,你需要修改它才能完全匹配。只是模仿你在原始方法中看到的内容。
它应该能够接受多个属性匹配,所以如果你有foo和bar的模型属性,你应该可以做collection.wherePartialUnique({foo:“你”,bar:“dude”} )。我没有测试过。 :)我只做了一个键/值对。
我还从考虑中删除了空模型属性。我不关心他们,但你可能会这样。
此方法不需要比较器所依赖的唯一模型属性的集合。它更像是一个sql不同的查询,但我不是一个sql人,所以如果这是一个坏的例子,请不要开枪:)
您的收藏品是通过比较器功能进行分类的,因此您对其中某个未被排序的假设是不正确的。
我相信这也符合你的所有目标:
- 收集未排序
- 必须对品牌属性值进行删除
- Ajax调用以填充Truck的每个实例。这意味着在添加到集合时,您无权访问Truck属性。
醇>
答案 9 :(得分:0)
似乎一个优雅的解决方案是使用_.findWhere只要你有一些独特的属性(在你的情况下品牌)。 _.findWhere将返回一个匹配,这是一个JavaScript对象,因此是真实的或未定义的,这是假的。这样您就可以使用单个if语句。
var TruckList = Backbone.Collection.extend({
model: Truck,
add: function (truck) {
if (!this.findWhere({ brand: truck.get('brand') })) {
Backbone.Collection.prototype.add.call(this, truck);
}
}
});
答案 10 :(得分:0)
我真的不满意这个解决方案的公认答案。它包含许多错误。我已经编辑了原始解决方案以突出我的顾虑,但我提出以下解决方案,假设您可以弄脏副本的id / cid属性:
TruckList.prototype.add = function(truckToAdd, options) {
// Find duplicate truck by brand:
var duplicateTruck = this.find(function(truck){
return truck.get('brand') === truckToAdd.get('brand');
});
// Make truck an actual duplicate by ID:
// TODO: This modifies truckToAdd's ID. This could be expanded to preserve the ID while also taking into consideration any merge: true options.
if(duplicateTruck !== undefined){
if(duplicateTruck.has('id')){
truckToAdd.set('id', duplicateTruck.get('id'), { silent: true });
}
else {
truckToAdd.cid = duplicateTruck.cid;
}
}
// Allow Backbone to handle the duplicate instead of trying to do it manually.
return Backbone.Collection.prototype.add.call(this, truckToAdd, options);
}
这个唯一的缺陷就是不保留truckToAdd的ID / cid。但是,这确实保留了将项添加到集合的所有预期功能,包括传递merge:true。
答案 11 :(得分:0)
由于以下几个原因,我对提供的答案不满意:
{ merge: true }
是意外的。我提供了一个我认为更强大的解决方案。这个解决方案克隆了给定模型,如果它们在集合中有重复,则更新克隆&#39;用于匹配重复项ID的ID,然后将重复项和非重复项列表传递到原始的add方法,以便它可以发挥其魔力。据我所知,没有意外的副作用。
add: function (models, options) {
var preparedModels;
if (models instanceof Backbone.Collection) {
preparedModels = models.map(this._prepareModelToAdd.bind(this));
}
else if (_.isArray(models)) {
preparedModels = _.map(models, this._prepareModelToAdd.bind(this));
} else if (!_.isNull(models) && !_.isUndefined(models)) {
preparedModels = this._prepareModelToAdd(models);
} else {
preparedModels = models;
}
// Call the original add method using preparedModels which have updated their IDs to match any existing models.
return Backbone.Collection.prototype.add.call(this, preparedModels, options);
},
// Return a copy of the given model's attributes with the id or cid updated to match any pre-existing model.
// If no existing model is found then this function is a no-op.
// NOTE: _prepareModel is reserved by Backbone and should be avoided.
_prepareModelToAdd: function (model) {
// If an existing model was not found then just use the given reference.
var preparedModel = model;
var existingModel = this._getExistingModel(model);
// If an existing model was found then clone the given reference and update its id.
if (!_.isUndefined(existingModel)) {
preparedModel = this._clone(model);
this._copyId(preparedModel, existingModel);
}
return preparedModel;
},
// Try to find an existing model in the collection based on the given model's brand.
_getExistingModel: function (model) {
var brand = model instanceof Backbone.Model ? model.get('brand') : model.brand;
var existingModel = this._getByBrand(brand);
return existingModel;
},
_getByBrand: function (brand) {
return this.find(function (model) {
return model.get('brand') === brand;
});
},
_clone: function (model) {
// Avoid calling model.clone because re-initializing the model could cause side-effects.
// Avoid calling model.toJSON because the method may have been overidden.
return model instanceof Backbone.Model ? _.clone(model.attributes) : _.clone(model);
},
// Copy the model's id or cid onto attributes to ensure Backbone.Collection.prototype.add treats attributes as a duplicate.
_copyId: function (attributes, model) {
if (model.has('id')) {
attributes.id = model.get('id');
} else {
attributes.cid = model.cid;
}
}