Опрос коллекции с помощью Backbone.js
Я пытаюсь сохранить коллекцию Backbone.js актуальной с тем, что происходит на сервере.
Мой код похож на следующий:
var Comment = Backbone.Model.extend({});
var CommentCollection = Backbone.Collection.extend({
model: Comment
});
var CommentView = Backbone.View.extend({ /* ... */ });
var CommentListView = Backbone.View.extend({
initialize: function () {
_.bindAll(this, 'addOne', 'addAll');
this.collection.bind('add', this.addOne);
this.collection.bind('refresh', this.addAll);
},
addOne: function (item) {
var view = new CommentView({model: item});
$(this.el).append(view.render().el);
},
addAll: function () {
this.collection.each(this.addOne);
}
});
var comments = new CommentCollection;
setInterval(function () {
comments.fetch();
}, 5000);
Что происходит, когда вызывается комментарии, вызывается refresh
, те же комментарии в нижней части CommentListView
, что и ожидал от кода выше.
Какой идентификатор должен знать, это лучший способ "обновить" представление, не теряя "локального состояния".
Ответы
Ответ 1
Что вы хотите сделать, это обновить коллекцию каждые несколько секунд и добавить новые комментарии. Мое предложение - решить эту проблему на вашем сервере. Отправьте последнюю метку времени с последнего комментария и спросите сервер только о дельтах с этой даты.
Для этого в вашей коллекции:
CommentCollection = Backbone.Collection.extend({
url: function(){
return "/comments?from_time=" + this.last().get("created_at");
},
comparator: function(comment){
return comment.get("created_at");
}
});
В своем бэкэнд запросите свою базу данных на основе параметра from_time. Ваш код клиента не изменяется для обновления представления.
Если вы не хотите менять код базы данных по какой-либо причине, добавьте эту строку в функцию addAll:
addAll: function(){
$(this.el).empty();
this.collection.each(this.addOne);
}
Ответ 2
Или просто используйте гораздо более простое дополнение к методу получения базовой линии:
this.fetch({ update: true });
Когда данные модели возвращаются с сервера, коллекция будет (эффективно) reset, если вы не передадите {update: true}, и в этом случае она будет использовать обновление (интеллектуально), чтобы объединить извлеченные модели. - Макетная документация
: -)
Ответ 3
Backbone.Collection.merge([параметры])
Основываясь на ответе @Jeb выше, я инкапсулировал это поведение в расширение Backbone, которое вы можете скопировать и вставить в .js файл и включить на свою страницу (после включения самой библиотеки Backbone).
Он предоставляет метод, называемый merge
для объектов Backbone.Collection. Вместо полного сброса существующей коллекции (как это делает fetch
), она сравнивает ответ сервера с существующей коллекцией и объединяет их различия.
- Он добавляет модели, которые находятся в ответе, но не в существующей коллекции.
- Он удаляет модели, которые находятся в существующей коллекции, но не в ответе.
- Наконец, он обновляет атрибуты моделей, найденных в существующей коллекции AND в ответе.
Все ожидаемые события запускаются для добавления, удаления и обновления моделей.
Хеш options принимает success
и error
обратные вызовы, которые будут переданы (collection, response)
в качестве аргументов, и он предоставляет третий параметр обратного вызова complete
, который выполняется независимо от успеха или ошибка (в основном полезная для сценариев опроса).
Он вызывает события, называемые "merge: success" и "merge: error".
Вот расширение:
// Backbone Collection Extensions
// ---------------
// Extend the Collection type with a "merge" method to update a collection
// of models without doing a full reset.
Backbone.Collection.prototype.merge = function(callbacks) {
// Make a new collection of the type of the parameter
// collection.
var me = this;
var newCollection = new me.constructor(me.models, me.options);
this.success = function() { };
this.error = function() { };
this.complete = function() { };
// Set up any callbacks that were provided
if(callbacks != undefined) {
if(callbacks.success != undefined) {
me.success = callbacks.success;
}
if(callbacks.error != undefined) {
me.error = callbacks.error;
}
if(callbacks.complete != undefined) {
me.complete = callbacks.complete;
}
}
// Assign it the model and url of collection.
newCollection.url = me.url;
newCollection.model = me.model;
// Call fetch on the new collection.
return newCollection.fetch({
success: function(model, response) {
// Calc the deltas between the new and original collections.
var modelIds = me.getIdsOfModels(me.models);
var newModelIds = me.getIdsOfModels(newCollection.models);
// If an activity is found in the new collection that isn't in
// the existing one, then add it to the existing collection.
_(newCollection.models).each(function(activity) {
if (_.indexOf(modelIds, activity.id) == -1) {
me.add(activity);
}
}, me);
// If an activity in the existing collection isn't found in the
// new one, remove it from the existing collection.
var modelsToBeRemoved = new Array();
_(me.models).each(function(activity) {
if (_.indexOf(newModelIds, activity.id) == -1) {
modelsToBeRemoved.push(activity);
}
}, me);
if(modelsToBeRemoved.length > 0) {
for(var i in modelsToBeRemoved) {
me.remove(modelsToBeRemoved[i]);
}
}
// If an activity in the existing collection is found in the
// new one, update the existing collection.
_(me.models).each(function(activity) {
if (_.indexOf(newModelIds, activity.id) != -1) {
activity.set(newCollection.get(activity.id));
}
}, me);
me.trigger("merge:success");
me.success(model, response);
me.complete();
},
error: function(model, response) {
me.trigger("merge:error");
me.error(model, response);
me.complete();
}
});
};
Backbone.Collection.prototype.getIdsOfModels = function(models) {
return _(models).map(function(model) { return model.id; });
};
Простой сценарий использования:
var MyCollection = Backbone.Collection.extend({
...
});
var collection = new MyCollection();
collection.merge();
Сценарий использования справки об ошибках:
var MyCollection = Backbone.Collection.extend({
...
});
var collection = new MyCollection();
var jqXHR = collection.merge({
success: function(model, response) {
console.log("Merge succeeded...");
},
error: function(model, response) {
console.log("Merge failed...");
handleError(response);
},
complete: function() {
console.log("Merge attempt complete...");
}
});
function handleError(jqXHR) {
console.log(jqXHR.statusText);
// Direct the user to the login page if the session expires
if(jqXHR.statusText == 'Unauthorized') {
window.location.href = "/login";
}
};
Ответ 4
Сделайте дубликат коллекции. Fetch(). Сравните эти два, чтобы найти дельт. Примените их.
/*
* Update a collection using the changes from previous fetch,
* but without actually performing a fetch on the target
* collection.
*/
updateUsingDeltas: function(collection) {
// Make a new collection of the type of the parameter
// collection.
var newCollection = new collection.constructor();
// Assign it the model and url of collection.
newCollection.url = collection.url;
newCollection.model = collection.model;
// Call fetch on the new collection.
var that = this;
newCollection.fetch({
success: function() {
// Calc the deltas between the new and original collections.
var modelIds = that.getIdsOfModels(collection.models);
var newModelIds = that.getIdsOfModels(newCollection.models);
// If an activity is found in the new collection that isn't in
// the existing one, then add it to the existing collection.
_(newCollection.models).each(function(activity) {
if (modelIds.indexOf(activity.id) == -1) {
collection.add(activity);
}
}, that);
// If an activity in the existing colleciton isn't found in the
// new one, remove it from the existing collection.
_(collection.models).each(function(activity) {
if (newModelIds.indexOf(activity.id) == -1) {
collection.remove(activity);
}
}, that);
// TODO compare the models that are found in both collections,
// but have changed. Maybe just jsonify them and string or md5
// compare.
}
});
},
getIdsOfModels: function(models) {
return _(models).map(function(model) { return model.id; });
},