javascript - Sondeo de una colección con Backbone.js
ajax polling (4)
Haz una colección duplicada. Cógelo. Compara los dos para encontrar los deltas. Aplicarlos.
/*
* 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; });
},
Estoy tratando de mantener actualizada la colección Backbone.js con lo que está sucediendo en el servidor.
Mi código es similar al siguiente:
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);
Lo que sucede es que cuando se obtienen los comentarios, se invoca la refresh
, los mismos comentarios en la parte inferior de CommentListView
que es lo que esperaría del código anterior.
Lo que me gustaría saber es cuál es la mejor manera de "actualizar" la vista, sin perder ningún "estado local".
Lo que desea hacer es actualizar la colección cada pocos segundos y anexar los nuevos comentarios. Mi sugerencia es tratar con ese problema en su back-end. Envíe la última marca de tiempo de su último comentario y solicite al servidor el delta a partir de esta fecha solamente.
Para hacerlo, en tu colección:
CommentCollection = Backbone.Collection.extend({
url: function(){
return "/comments?from_time=" + this.last().get("created_at");
},
comparator: function(comment){
return comment.get("created_at");
}
});
En su back-end, consulte su base de datos según el parámetro from_time. Su código de cliente no cambia para actualizar la vista.
Si no desea cambiar el código de su servidor por algún motivo, agregue esta línea en la función addAll:
addAll: function(){
$(this.el).empty();
this.collection.each(this.addOne);
}
O simplemente use la adición mucho más simple al método de búsqueda de la red troncal:
this.fetch({ update: true });
Cuando los datos del modelo vuelvan del servidor, la colección se restablecerá (eficientemente), a menos que pase {update: true}, en cuyo caso utilizará la actualización para (inteligentemente) fusionar los modelos obtenidos. - Documentación de backbone
:-)
Backbone.Collection.merge ([opciones])
Sobre la base de la respuesta de @Jeb anterior, he encapsulado este comportamiento en una extensión Backbone que puede copiar y pegar en un archivo .js e incluir en su página (después de incluir la biblioteca Backbone).
Proporciona un método llamado merge
para objetos Backbone.Collection. En lugar de restablecer completamente la colección existente (como lo hace fetch
), compara la respuesta del servidor a la colección existente y combina sus diferencias.
- Agrega modelos que están en la respuesta, pero no en la colección existente.
- Elimina los modelos que están en la colección existente, pero no en la respuesta.
- Finalmente, actualiza los atributos de los modelos encontrados en la colección AND existente en la respuesta.
Todos los eventos previstos se activan para agregar, eliminar y actualizar modelos.
El hash de opciones toma success
y las devoluciones de error
que se pasarán (collection, response)
como argumentos, y proporciona una tercera opción de devolución de llamada llamada complete
que se ejecuta independientemente del éxito o error (principalmente útil para los escenarios de sondeo).
Activa eventos llamados "merge: success" y "merge: error".
Aquí está la extensión:
// 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; });
};
Escenario de uso simple:
var MyCollection = Backbone.Collection.extend({
...
});
var collection = new MyCollection();
collection.merge();
Error al manejar el escenario de uso:
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";
}
};