我有一个带有大量模型的Backbone集合。
每当在模型上设置特定属性并保存它时,就会触发一系列计算并重新呈现UI。
但是,我希望能够同时在多个模型上设置属性,并且只有在设置完成后才进行保存和重新渲染。当然,我不想为一个操作发出几个http请求,并且绝对不想让接口重新渲染十次。
我希望在Backbone.Collection上找到一个可以解决哪些模型hasChanged()的保存方法,将它们作为json重新组合并发送到后端。然后可以通过集合上的事件触发重新渲染。没有这样的运气。
这似乎是一个非常常见的要求,所以我想知道为什么Backbone没有实现。这是否违反RESTful架构,将多个内容保存到单个端点?如果是这样,那又怎样?让1000个小项目持续存在1000个请求是不切实际的。
那么,唯一的解决方案是使用我自己的save方法来扩充Backbone.Collection,该方法遍历其所有模型并为所有已更改并将其发送到后端的json构建?或者有没有人有一个更整洁的解决方案(或者我只是遗漏了一些东西!)?
答案 0 :(得分:4)
我最终用几种方法来扩充Backbone.Collection来处理这个问题。
saveChangeMethod创建一个要传递给Backbone.sync的虚拟模型。模型中所有骨干的同步方法需要的是url属性和toJSON方法,所以我们可以很容易地解决这个问题。
在内部,模型的toJSON方法只返回其属性的副本(要发送到服务器),因此我们可以愉快地使用只返回模型数组的toJSON方法。 Backbone.sync将此字符串化,这只为我们提供了属性数据。
成功时,saveChanged会触发要处理一次的集合上的事件。已经删除了一些代码,这些代码可以为任何批处理模型中已更改的每个属性触发一次特定事件。
Backbone.Collection.prototype.saveChanged = function () {
var me = this,
changed = me.getChanged(),
dummy = {
url: this.url,
toJSON: function () {
return changed.models;
}
},
options = {
success: function (model, resp, xhr) {
for (var i = 0; i < changed.models.length; i++) {
changed.models[i].chnageSilently();
}
for (var attr in changed.attributes) {
me.trigger("batchchange:" + attr);
}
me.trigger("batchsync", changed);
}
};
return Backbone.sync("update", dummy, options);
}
然后我们只需要集合上的getChanged()方法。这将返回一个具有2个属性的对象,一个已更改模型的数组和一个标记哪些属性已更改的对象:
Backbone.Collection.prototype.getChanged = function () {
var models = [],
changedAttributes = {};
for (var i = 0; i < this.models.length; i++) {
if (this.models[i].hasChanged()) {
_.extend(changedAttributes, this.models[i].changedAttributes());
models.push(this.models[i]);
}
}
return models.length ? {models: models, attributes: changedAttributes} : null;
}
虽然这是对主干“改变模型”范例的预期用途的轻微滥用,但批处理的全部意义在于,当模型被更改时,我们不希望发生任何事情(即任何要触发的事件)。 / p>
因此我们必须将{silent:true}传递给模型的set()方法,因此使用backbone的hasChanged()标记等待保存的模型是有意义的。当然,如果您为了其他目的而静默更改模型,这将是有问题的 - collection.saveChanged()也会保存这些,因此值得考虑设置替代标志。
在任何情况下,如果我们这样做,在保存时,我们需要确保骨干现在认为模型没有改变(没有触发他们的更改事件),所以我们需要手动操作模型,就像它一样没有改变。 saveChanged()方法迭代我们更改的模型,并在模型上调用此changeSilently()方法,这基本上只是Backbone的model.change()方法,没有触发器:
Backbone.Model.prototype.changeSilently = function () {
var options = {},
changing = this._changing;
this._changing = true;
for (var attr in this._silent) this._pending[attr] = true;
this._silent = {};
if (changing) return this;
while (!_.isEmpty(this._pending)) {
this._pending = {};
for (var attr in this.changed) {
if (this._pending[attr] || this._silent[attr]) continue;
delete this.changed[attr];
}
this._previousAttributes = _.clone(this.attributes);
}
this._changing = false;
return this;
}
用法:
model1.set({key: value}, {silent: true});
model2.set({key: value}, {silent: true});
model3.set({key: value}, {silent: true});
collection.saveChanged();
RE。 RESTfulness ..对集合的端点进行PUT来改变其中的一些记录是不对的。从技术上讲,PUT应该取代整个系列,但在我的应用程序实际需要更换整个系列之前,我很乐意采取务实的方法。
答案 1 :(得分:1)
您可以定义新的资源来完成此类行为,您可以将其称为MyModelBatch
。
您需要在服务器端实施一个新资源,该资源能够消化Array
个模型并执行适当的操作:CREATE
,UPDATE
和DESTROY
此外,您还需要在Backbone客户端实现Model
一个属性,这是一个模型数组和一个不使用的特殊url
id
。
关于重新呈现的事情我建议你尝试每个模型都有一个视图,这样就会有模型更改的渲染,但它们将是详细信息重新呈现,无需重复。
答案 2 :(得分:0)
这就是我想出来的。
Backbone.Collection.extend({
saveAll: function(models, key, val, options) {
var attrs, xhr, wait, that = this;
var transport = {
url: this.url,
models: [],
toJSON: function () {
return { models: this.models };
},
trigger: function(){
return that.trigger.apply(that, arguments);
}
};
if(models == null){
models = this.models;
}
// Handle both `"key", value` and `{key: value}` -style arguments.
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options = _.extend({validate: true}, options);
wait = options.wait;
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
if (options.parse === void 0) options.parse = true;
var triggers = [];
_.each(models, function(model){
var attributes = model.attributes;
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
// the model will be valid when the attributes, if any, are set.
if (attrs && !wait) {
if (!model.set(attrs, options)) return false;
} else {
if (!model._validate(attrs, options)) return false;
}
// Set temporary attributes if `{wait: true}`.
if (attrs && wait) {
model.attributes = _.extend({}, attributes, attrs);
}
transport.models.push(model.toJSON());
triggers.push(function(resp){
if(resp.errors){
model.trigger('error', model, resp, options);
} else {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
if (wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
model.trigger('sync', model, resp, options);
}
});
// Restore attributes.
if (attrs && wait) model.attributes = attributes;
});
var success = options.success;
options.success = function(resp) {
_.each(triggers, function(trigger, i){
trigger.call(options.context, resp[i]);
});
if (success) success.call(options.context, models, resp, options);
};
return this.sync('create', transport, options);
}
});