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.
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.
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
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
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:
then in the view add an event to the events object:
then add the sortThingsByColumn function to the view. This calls the function and re-renders:
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
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
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
posted by d. z. wang at 4:03 PM on November 12, 2012
Best answer: Actually you would only need one collection function.
And one view function:
Check what
posted by urbanwhaleshark at 4:13 PM on November 12, 2012
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
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
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
posted by steinsaltz at 6:23 PM on November 12, 2012
Best answer: In the collection add the following under
Then change the comparator to:
Any good?
posted by urbanwhaleshark at 6:27 PM on November 12, 2012
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
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
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
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
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
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]
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.
posted by steinsaltz at 3:20 PM on November 12, 2012