Ответ 1
Я закончил добавление Backbone.Collection с помощью нескольких методов для этого.
SaveChangeMethod создает фиктивную модель для передачи на Backbone.sync. Все основные методы синхронизации требуют от модели ее свойства url и метода JSON, поэтому мы можем легко сбить это.
Внутри модели метод 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;
}
Несмотря на то, что это незначительное злоупотребление предполагаемой использованием парадигмы измененной модели магистральных сетей, все дело в том, что мы не хотим, чтобы что-либо происходило (т.е. любые события, которые нужно было отключить) при изменении модели.
Поэтому мы должны передать {silent: true} метод model(), поэтому имеет смысл использовать магистраль 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 должен заменить всю коллекцию, хотя до тех пор, пока мое приложение никогда не понадобится заменить всю коллекцию, я с удовольствием принимаю прагматичный подход.