2011-05-11 5 views
23

J'essaie de garder une collection Backbone.js à jour avec ce qui se passe sur le serveur.Sondage d'une collection avec Backbone.js

Mon code est similaire à ce qui suit:

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); 

Ce qui se passe est que lorsque les commentaires sont extraites, refresh est appelé, les mêmes commentaires au bas de la CommentListView -qui est ce que je pense du code ci-dessus.

Ce que je voudrais savoir, c'est quelle est la meilleure façon de "rafraîchir" la vue, sans perdre aucun "état local".

Répondre

16

Ce que vous voulez faire est d'actualiser la collection toutes les quelques secondes et d'ajouter les nouveaux commentaires. Ma suggestion est de traiter ce problème sur votre backend. Envoyez le dernier horodatage de votre dernier commentaire et demandez au serveur le delta à partir de cette date seulement.

Pour ce faire, dans votre collection:

CommentCollection = Backbone.Collection.extend({ 
    url: function(){ 
    return "/comments?from_time=" + this.last().get("created_at"); 
    }, 
    comparator: function(comment){ 
    return comment.get("created_at"); 
    } 
}); 

Dans votre back-end, interrogez votre base de données basée sur le FROM_TIME parameter.Your code client ne change pas de rafraîchir la vue.

Si vous ne voulez pas changer votre code backend pour une raison quelconque ajoutez cette ligne dans la fonction addAll:

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

Faire une collection en double. Récupérez-le(). Comparez les deux pour trouver les deltas. Appliquez-les.

 /* 
     * 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

solution excellente. J'essayais d'accomplir ceci dans la méthode de collection.parse et n'obtenant nulle part. J'espère vraiment que ce comportement est ajouté à l'API de Backbone. Quoi qu'il en soit, voici ma mise en œuvre de l'article TODO pour la mise à jour des modèles existants. '_ (collection.models) .Chaque (fonction (activité) { \t \t \t \t \t \t si (newModelIds.indexOf (activity.id)! = -1) { \t \t \t \t \t \t activité \t. set (newCollection.get (activity.id)); \t \t \t \t \t \t} \t \t \t \t \t}, que); ' –

8

Backbone.Collection.merge ([options])

bâtiment sur @ réponse de Jeb ci-dessus, je l'ai encapsulé ce comportement dans une extension de réseau fédérateur que vous pouvez copier et coller dans un .js fichier et inclure dans votre page (après avoir inclus la bibliothèque Backbone elle-même).

Il fournit une méthode appelée merge pour les objets Backbone.Collection. Plutôt que de réinitialiser complètement la collection existante (comme le fait fetch), elle compare la réponse du serveur à la collection existante et fusionne leurs différences.

  1. Il ajoute modèles qui sont dans la réponse, mais pas dans la collection existante.
  2. Il supprime les modèles qui sont dans la collection existante, mais pas dans la réponse.
  3. Enfin, il met à jour les attributs des modèles trouvés dans la collection existante ET dans la réponse.

Tous les événements attendus sont déclenchés pour l'ajout, la suppression et la mise à jour de modèles.

Le hachage options prend success et error callbacks qui seront transmis (collection, response) comme arguments, et il fournit une troisième option de rappel appelée complete qui est exécuté indépendamment de la réussite ou l'erreur (surtout utile pour les scénarios de vote).

Il déclenche les événements appelés "fusionner: succès" et "fusionner: erreur".

Voici l'extension:

// 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 Scénario d'utilisation:

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

Gestion des erreurs Scénario d'utilisation:

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

Il existe un moyen beaucoup plus simple de gérer cela maintenant en définissant le paramètre d'option de mise à jour de la méthode fetch(). Voir mon autre réponse. –

30

Ou tout simplement utiliser le bien plus simple ajout à la méthode de récupération de colonne vertébrale:

this.fetch({ update: true }); 

Lorsque les déclarations de données de modèle à partir du serveur, la collection sera (efficacement) remis à zéro, à moins que vous passez {mise à jour: true}, Dans ce cas, il utilisera la mise à jour pour fusionner (intelligemment) les modèles récupérés. - Backbone Documentation

:-)

+0

Cela devrait être définitivement upvoted! +1 –

Questions connexes