Sorting things on the fly in Backbone.js
November 12, 2012 3:10 PM   Subscribe

Question about sorting in the Backbone.js framework.

I've been trying to learn the popular Backbone.js framework for Javascript and am having trouble understanding how it deals with sorting.

I'm making a database of plants. The plants can either be sorted by common name, or Latin name. A radio button would let the user toggle between the two.

Backbone sorts collections of models using a function called Comparator. I can get Comparator to do the default when it boots up...a sort-by-English-name...but am having trouble making it toggle over to a new criteria.

After reading Stack Overflow I gather this has something to do with Comparator being evaluated one time when you start up, and then not being willing to re-evaluate in certain situations. Then there's some other talk about recent changes that have improved the "sort by" ability. But I am still having trouble figuring out the best way to use this.

Could anyone help give me an example of a simple way to make the comparator toggle between two different sort-bys? Any help much appreciated.
posted by steinsaltz to Computers & Internet (17 answers total) 1 user marked this as a favorite
 
Response by poster: (or alternately to avoid the comparator and just use the .sortBy method, that would be really great too.)
posted by steinsaltz at 3:20 PM on November 12, 2012


Want to give us an example of how you are declaring and calling the comparator, on gist or somewhere?
posted by doteatop at 3:21 PM on November 12, 2012


Response by poster: Thanks--I tried something like this but the terminology in part 2 confused me a bit.

Right now I've fallen back to a simple

comparator: function (tree) {
return tree.get("name");
}

At one point this alternated between returning "name" and "scientificName," but that didn't seem to work. It would set off the "reset" event, causing the list to be redrawn. But it would always come back sorted by "name."

Then I thought maybe my collection, Trees, could just be resorted using Trees.sortBy(), which seemed simple. But I couldn't figure out how to do this right.
posted by steinsaltz at 3:30 PM on November 12, 2012


Here's how I did it for sorting a table by clicking the column headings.

In the collection add a new function:

sortByColumn: function(colName) {
  this.sortKey = colName;
  this.sort();
}

then in the view add an event to the events object:

events: {
  'click #things thead th': 'sortThingsByColumn'
}

then add the sortThingsByColumn function to the view. This calls the function and re-renders:

sortThingsByColumn: function(event) {
  var column = event.currentTarget.classList[0]
  this.collections.things.sortByColumn(column)
  this.render()
}

So what you probably need to do is add two functions to your collection - sortByLatin and sortByCommon - and a function in the view that is called when a radio event is called and calls one of the collection sort functions depending on the radio value.
posted by urbanwhaleshark at 3:56 PM on November 12, 2012


Everything I know about Javascript and Backbone I learned in the last forty minutes on jsFiddle, but I think the problem here is that the elements are inserted according to the comparator and retrieved according to their position in the array. So elements you insert after changing the comparator will be put where the new comparator says they should go [1] but elements that were already in the collection will not be moved to respect the new ordering.

Here's some code that demonstrates my hypothesis. Note how the chapter titled "AAA" starting on page 10 is inserted at the end, to respect the page ordering, but the three chapters already in the collection remain ordered by title.

var Chapter = Backbone.Model;
var ChaptersCollection = Backbone.Collection.extend({
model : Chapter,
comparator : function(chapter) {
return chapter.get(this.getAttr());
},
getAttr : function () {
alert("Foo");
return "page";
}
});

var chapters = new ChaptersCollection();
chapters.getAttr = function () {
alert("foo");
return "title";
}

chapters.add(new Chapter({page: 9, title: "The End"}));
chapters.add(new Chapter({page: 5, title: "The Middle"}));
chapters.add(new Chapter({page: 1, title: "The Beginning"}));

$("body").append("" + chapters.pluck('title') + "");

chapters.getAttr = function () {
return "page";
}

chapters.add(new Chapter({page:10, title: "AAA"}));

$("body").append("" + chapters.pluck('title') + "");


[1] Although the existing elements will not be consistently ordered according to the new comparator, so where future elements belong is sort of undefined. I don't know exactly what semantics they define in this case.
posted by d. z. wang at 4:02 PM on November 12, 2012


On non-preview, yes, you need to re-construct the collection to respect the new comparator, and apparently urbanwhaleshark is giving you the appropriate function call to do that.
posted by d. z. wang at 4:03 PM on November 12, 2012


Best answer: Actually you would only need one collection function.

sortByType: function(type) {
  this.sortKey = type;
  this.sort();
}

And one view function:

sortThingsByColumn: function(event) {
  var type = event.currentTarget.classList[0]
  this.collections.things.sortByType(type)
  this.render()
}

Check what event.currentTarget.classList[0] does as it might not return the radio button value.
posted by urbanwhaleshark at 4:13 PM on November 12, 2012


Response by poster: Thanks, UrbanWhaleShark. I really appreciate it.

I'm trying but keep getting the same result without the needed re-sort.

But that might be becaus I dumbed down your function into into Trees.sortByType(type), not quite familiar enough with the syntax to come up with the correct version of this.collections.things.

Here's the gist. I think maybe the comparator is still doing that thing where it evaluates the expression one time and then doesn't re-do it as needed? Or maybe I just need to figure out how to come up with my version of this.collections.thing? Or create a separate collection?
posted by steinsaltz at 5:45 PM on November 12, 2012


Response by poster: Or it could be that sort() just calls the comparator and we haven't figured out a good use of "sortKey" yet?
posted by steinsaltz at 6:10 PM on November 12, 2012


Response by poster: They're telling me on IRC to use "this.sortBy(function(model) { return model.get('prop'); }"...but as a loop to work with.
posted by steinsaltz at 6:23 PM on November 12, 2012


Best answer: In the collection add the following under model: Tree:

sortKey: 'name',

Then change the comparator to:

comparator: function(item) {
  return this.sortKey === 'name'
    ? -item.get(this.sortKey)
    : item.get(this.sortKey)
},

Any good?
posted by urbanwhaleshark at 6:27 PM on November 12, 2012


Response by poster: That might be just the thing. Let's see ... I'd really rather use your method, if I can crack it...
posted by steinsaltz at 6:29 PM on November 12, 2012


Response by poster: Is my View function going in the individual item view or the app one? The app one I guess.
posted by steinsaltz at 6:34 PM on November 12, 2012


Whatever view does the event checking for your radio button.
posted by urbanwhaleshark at 6:40 PM on November 12, 2012


I've gotta go to sleep, but if you wanted to look at the code I took this from I've uploaded it here. Feel free to nose around.
posted by urbanwhaleshark at 6:49 PM on November 12, 2012


Response by poster: Thanks. Sleep the sleep of someone who has done Good.
posted by steinsaltz at 6:54 PM on November 12, 2012


Response by poster: It worked!!! I am thrilled because I was trying to figure this out all day. Thank you so much.
posted by steinsaltz at 7:14 PM on November 12, 2012 [1 favorite]


« Older Winter coats pulling at the back and arms: A fact...   |   College senior still trying to figure out what I... Newer »
This thread is closed to new comments.