Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Collection next and prev #136

Closed
martindrapeau opened this Issue · 13 comments

9 participants

@martindrapeau

I'd like to know the next or previous model in a collection. How can I do that?
Would be nice to have methods next() and prev() on a collection. For example:
collection.next(model) or collection.prev(model)
And if there wasn't a new or previous, return undefined.

@collin

It's pretty simple here is a patch, is this wanted? I can put it into a pull request.

diff --git a/backbone.js b/backbone.js
index ec1ad2d..ae33145 100644
--- a/backbone.js
+++ b/backbone.js
@@ -454,6 +454,16 @@
       return this.models[index];
     },

+    // Get the next model in the collection.
+    next : function(model) {
+      return this.at(this.indexOf(model) + 1);
+    },
+
+    // Get the previous model in the collection.
+    prev : function(model) {
+      return this.at(this.indexOf(model) - 1);
+    },
+
     // Force the collection to re-sort itself. You don't need to call this under normal
     // circumstances, as the set will maintain sort order as each item is added.
     sort : function(options) {
diff --git a/test/collection.js b/test/collection.js
index 8ed704a..1eebe29 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -188,4 +188,13 @@ $(document).ready(function() {
     ok(_.isEqual(col.last().attributes, a.attributes));
   });

+  test("Collection: next", function() {
+    equals(col.next(col.first()).get("label"), "c");
+    equals(col.next(col.last()), undefined);
+  });
+  
+  test("Collection: prev", function() {
+    equals(col.prev(col.last()).get("label"), "b");
+    equals(col.prev(col.first()), undefined);
+  });
 });
@martindrapeau

Looks good I think.

@jashkenas
Owner

Interesting idea -- do you think this should be a method on the collection, or on the model itself?

var next = model.next();

... just a thought.

@martindrapeau

I would think both.

@martindrapeau

Hi Jeremy,
Do you still plan on putting this in or do you want me to do it?
--Martin

@nervetattoo

I would love this and was just checking issues before writing a patch myself.

Also it would be sweet if the insert index used in add()/create() was made accessible somehow. It would make it possible to more easily render a new node into DOM without having to re-render everything or use a second indexOf() call.

@inf0rmer

I'm pretty new to Backbone but I've stumbled upon this need as well. I'm using collin's proposed patch for now and hoping this makes it into the master branch soon enough. As for having the same next/prev logic in the model, how much sense does it really make? I see models as isolated objects that never need to know about their siblings. Isn't this the exact purpose of collections or am I missing something?

@collin

Personally I wouldn't want the models to do this sort of thing. But that's just because of how I use backbone. I often have the same models in more than one collection.

@tbranyen
Collaborator

Yeah I discussed this online with someone last night and its quite easy to create a new collection with no dupes, or resetting the current collection to have no duplicates.

new Collection(_.uniq(previousCollection.models));
// or
previousCollection.reset(_.uniq(previousCollection.models));
@tanepiper

+1 for this - I used the top implementation of this in my own code for now and worked really well for my views, paging through each model in the collection easily

@linssen

You can always get around this using a new model method:

getRelative: function(direction) {
    return this.collection.at(this.collection.indexOf(this) + direction);
}

So if you pass -1 it'll get the previous and 1 will get you the next. It'll return -1 if it doesn't find anything.

@dzdrazil
Backbone.Collection = Backbone.Collection.extend({
    next: function(model) {
        var i = this.at(this.indexOf(model));
        if (undefined === i || i < 0) return false;
        return this.at(this.indexOf(model) + 1);
    },
    prev: function(model) {
        if (undefined === i || i < 1) return false;
        return this.at(this.indexOf(model) - 1);
    }
});

If you use either of the above solutions inside a model's validate function, in particular with next(), it'll run when the model is created. However, since it's not in the collection, indexOf returns -1, and adding the direciton (+1 for next) will return 0- i.e. your model will be incorrectly compared with the first element in the array.
Adding the extra check will cause the function to return false instead of the incorrect element.

It's a little messy to add collection methods to model validation, but in my case I'm building a velocity-time graph with each segment represented by a model, so I need to add some validation to ensure there's no breaking the laws of physics.

@jashkenas
Owner

So, options.index is now available in add and remove events, which will tell you where in the collection the added or removed model is located. Given that it returns an index, not a model reference ... and that this proposed feature gets a bit messy in light of the fact that a model can be in multiple collections at once ... I'm inclined to leave it out, unless a few more specific use cases are raised.

@jashkenas jashkenas closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.