I have a inherited a system that utilizes a Backbone collection of files that needs to be sorted based on a series of values. The order variable is saved when you slide different files into a position in the view. It mostly works but there are rare times when it is not sorted correctly based on values in the files_order list. I found that using the unsigned right shift came the closest to working but the code is returning odd results as a number of the files in the collection are not in the files_order.
For filecollectionid = 1, the system result is:
36528,36524,36529,35526,36523,36525,36530,36531
The correct order should be:
36528,36523,36524,36525,35526,36529,36530,36531
I am assuming the unsigned right shift of variables that do not exist in the list are causing the problem. Any help would be greatly appreciated.
collection = new Backbone.Collection([
{ id: 36523, name: "File 1", filecollectionid: 1 },
{ id: 36524, name: "File 2", filecollectionid: 1 },
{ id: 36525, name: "File 3", filecollectionid: 1 },
{ id: 36526, name: "File 4", filecollectionid: 1 },
{ id: 36528, name: "File 5", filecollectionid: 1 },
{ id: 36529, name: "File 6", filecollectionid: 1 },
{ id: 36530, name: "File 7", filecollectionid: 1 },
{ id: 36531, name: "File 8", filecollectionid: 1 },
{ id: 36476, name: "Video 1", filecollectionid: 6 },
{ id: 36520, name: "Video 2", filecollectionid: 6 },
{ id: 36527, name: "Video 3", filecollectionid: 6 }
]);
sections = {
"id": 1,
"files_order" : [36503,36513,36505,36506,36507,36508,36509,36510,36511,36521,36528,36522,35523,36524]
}
collection.sort(function(a,b) {
// Lets group files by the file collection id first
if(a.filecollectionid != b.filecollectionid){
return a.filecollectionid - b.filecollectionid;
}
// Lets try to use the files_order variable
try {
// Get the files_order field
var files_order = _.find(sections, function(section) {
// They should both have the same filecollectionid
return a.filecollectionid == section.id;
}).files_order;
files_order = _.map(JSON.parse(files_order.toString()), function(item) {
return parseInt(item);
});
// use unsigned right shift to keep the original and add the unadded to the end
return (files_order.indexOf(a.id) >>> 0) - (files_order.indexOf(b.id) >>> 0);
} catch(e) {
// If no sorting order this really should be oldest first and newest last.
a = a.id;
b = b.id;
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
}
});
2
Answers
Assuming you can use current versions of JS:
I will start by pointing out the mistakes in your code. Then, I will explain the unexpected results. Finally, I will show an approach that works.
The mistakes
Here:
you probably meant an array of objects:
but I guess this mistake was only in your question and not in your actual code, because you would be getting worse results otherwise.
Here:
You pass a comparator function directly to
Collection#sort
. However,Collection#sort
is different fromArray#sort
; it does not expect you to pass a comparator function. You can only pass an optional object with options. The comparator is supposed to be a permanent property of the collection.Here (and elsewhere in the function body):
you are trying to extract
.filecollectionid
directly froma
andb
. However, those are instances ofBackbone.Model
, not plain objects. The properties you are looking for are embedded ina.attributes
andb.attributes
. You are supposed to access them using the.get
and.set
methods.Here:
your code is doing something very different from what you seemed to expect. When you call
.toString()
on an array, it will stringify all values and concatenate them with commas, but it will not put square brackets around the entire list. For example,[123, 'abc', undefined].toString()
evaluates to123,abc,
. Even if the string looks like36529,36524
, this is not valid JSON, so theJSON.parse
call will throw an exception.There are also some parts of your code that are not technically wrong, but which are just not very efficient or more complicated than necessary. I will address those in the solution. But first: the reason for your unexpected results.
Why you get the wrong order
Your first problem is that your collection does not have a
comparator
property and you cannot pass it toCollection#sort
, so the.sort
method does not know how to compare the files. This results in it simply sticking to the input order.If this were not the case, the comparator function would still give you the wrong result. There are two reasons for this. Firstly, models do not have a
.filecollectionid
property, so this attribute is disregarded in the comparison.Secondly, the overal structure of your comparator function is like this:
The
try
block never completes, because your call toJSON.parse
always throws. Hence, files are never compared by theirfilecollectionid
or by thefiles_order
, but always by theid
.Solution
We can solve multiple problems at once by using
_.invert
. This function takes an object or array and returns a "flipped" version where the keys become values and vice versa. For yourfiles_order
, that means it turns[36503, 36513]
into{'36503': 0, '36513': 1}
. Conveniently, this means it no longer matters whther theid
s in the array are encoded as numbers or as strings. You do not need any parsing and looking up the order of an id is fast, especially if you cache the inverted object.In the snippet below, I demonstrate how you can use
_.invert
to your advantage. I also demonstrate how you can stop relying ontry
/catch
and show you a few nice Underscore tricks along the way. I encourage you to look up the documentation of each of these functions.