2011-05-11 20 views
23

Estoy tratando de mantener actualizada la colección de Backbone.js con lo que está sucediendo en el servidor.Sondeo de una colección con Backbone.js

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 los comentarios se recuperan, refresh se llama, las mismas observaciones a la parte inferior de la CommentListView -que es lo que cabe esperar del código de arriba.

Lo que me gustaría saber es cuál es la mejor manera de "actualizar" la vista, sin perder ningún "estado local".

Respuesta

16

Lo que quiere 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 ello, en su 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 backend, consultar su base de datos basado en el código de cliente FROM_TIME parameter.Your no cambia para actualizar la vista.

Si no desea cambiar su código de fondo por cualquier motivo añadir esta línea en la función addAll:

addAll: function(){ 
    $(this.el).empty(); 
    this.collection.each(this.addOne); 
} 
3

hacer una colección que duplicado. 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; }); 
     }, 
+0

solución excelente. Estaba tratando de lograr esto dentro del método collection.parse y llegar a ninguna parte. Realmente espero que este comportamiento se agregue a la API de Backbone. De todos modos, aquí está mi implementación del ítem TODO para actualizar los modelos existentes. '_ (collection.models) .Cada (función (actividad) { \t \t \t \t \t \t si (newModelIds.indexOf (activity.id)! = -1) {actividad \t \t \t \t \t \t \t. conjunto (newCollection.get (activity.id)); \t \t \t \t \t \t} \t \t \t \t \t}, eso); ' –

8

Backbone.Collection.merge ([opciones])

Edificio en @ respuesta de Jeb anterior, he encapsula este comportamiento en una extensión principal que se puede copiar y pegar en un archivo .js archivar e incluir en su página (después de incluir la biblioteca Backbone en sí).

Proporciona un método llamado merge para objetos Backbone.Collection. En lugar de reiniciar por completo la colección existente (como lo hace fetch), compara la respuesta del servidor a la colección existente y combina sus diferencias.

  1. Se añade modelos que se encuentran en la respuesta, pero no en la colección existente.
  2. Es elimina modelos que están en la colección existente, pero no en la respuesta.
  3. Finalmente, actualiza los atributos de los modelos encontrados en la colección existente Y en la respuesta.

eventos Todos los esperados se activan para añadir, eliminar y actualizar los modelos.

El opciones hash tienen successerror y devoluciones de llamada que se pasará (collection, response) como argumentos, y proporciona una tercera opción de devolución de llamada llama complete que se ejecuta independientemente del éxito o error (sobre todo útil para los escenarios de votación).

Activa eventos llamados "merge: success" y "merge: error".

Aquí es 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; }); 
}; 

simple uso Escenario:

var MyCollection = Backbone.Collection.extend({ 
    ... 
}); 
var collection = new MyCollection(); 
collection.merge(); 

Manejo de errores Uso Escenario:

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";       
    } 
}; 
+0

Hay una manera mucho más simple de manejar esto ahora estableciendo el parámetro de opción de actualización del método fetch(). Ver mi otra respuesta. –

30

o simplemente utilizar la adición mucho más sencillo método de captación de la columna vertebral:

this.fetch({ update: true }); 

Cuando los datos del modelo vuelve del servidor, la colección será (eficientemente) restablecer, a menos que pase {update: true}, en cuyo caso utilizará la actualización para (inteligentemente) fusionar los modelos obtenidos. - Backbone Documentation

:-)

+0

¡Esto debería ser definitivamente votato! +1 –

Cuestiones relacionadas