I have a problem I'm playing with here, I have a collection of BackboneJS models, each model has a 'ordinal' property that tracks its order in the collection.
Here is my play-data
var ex_group_test_data = [{
title: 'PRE EXERCISE',
id: 0,
ordinal: 1,
group_items: [{
id: 0,
ordinal: 0,
title: 'item 1'
},{
id: 1,
ordinal: 1,
title: 'item 2'
}]
},{
title: 'MAIN PART',
id: 1,
ordinal: 0,
group_items: [{
id: 2,
ordinal: 0,
title: 'item 3',
description: 'testing descrip'
},{
id: 3,
ordinal: 1,
title: 'item 4'
}]
},{
title: 'POST EXERCISE BS',
id: 2,
ordinal: 2,
group_items: [{
id: 2,
ordinal: 0,
title: 'item 5',
description: 'testing descrip'
},{
id: 3,
ordinal: 1,
title: 'item 6'
}]
}];
And here is the gist of my backbone collection
Collections.Exercise_Groups = Backbone.Collection.extend({
model: Models.Exercise_Group,
comparator: function(model){
return model.get('ordinal');
},
initialize: function(){
return this;
}
Starting simple, I want to be able to take a model and move it +1 or -1 ordinal and maintain 0-indexing of all models in the collection.
Eventually I want to bring this to a level where I can drop in models or remove them from any position and still maintain my 0-indexing, or take a model and move it +/- X positions.
Anyone have an recommended ways of accomplishing this?
EDIT 1
I've worked out a solution, I might want to optimize this tomorrow after I actually get some sleep. It maintains the 0-indexing of the 'ordinals' of my models in my collection, whether i'm moving a model forwards or backwards relative to it's original position.
EDIT 2 Err actually it has errors on fringe-cases.
/**
* Move model to a specified location.
*
* @param int [model id]
* @param int [mew item position]
* @return this
*/
move_to: function(m_id, new_pos){
//keep within range
if(new_pos < 0)
new_pos = 0;
else if(new_pos > (this.length - 1))
new_pos = this.length - 1;
var model = this.get(m_id),
old_pos = model.get('ordinal');
model.set({
ordinal: new_pos
});
if(new_pos == old_pos){
//trigger associated events
this.sort();
return this;
}
//update indexes of affected models
this.each(function(m){
//ordinal of current model in loop
var m_ordinal = m.get('ordinal');
//skip if this is the model we just updated
if(m.get('id') == m_id)
return;
if(old_pos < new_pos){
//moving down, ordinal is increasing
if(m_ordinal <= new_pos && m_ordinal != 0){
//this is in the range we care about
m.set({
ordinal: m.get('ordinal') - 1
});
}
}else if(old_pos > new_pos){
//moving up, ordinal is decreasing
if(m_ordinal >= new_pos && (m_ordinal != (this.length - 1))){
//this is in the range we care about
m.set({
ordinal: m.get('ordinal') + 1
});
}
}
});
this.sort();
return this;
}
EDIT 3 Okay I think I've fixed all the problems, some simple scope stuff. Here's some code I've tested pretty thoroughly and I believe it works.
/**
* Move model to a specified location.
*
* @param int [model id]
* @param int [mew item position]
* @return this
*/
move_to: function(m_id, new_pos){
//keep within range
if(new_pos < 0)
new_pos = 0;
else if(new_pos > (this.length - 1))
new_pos = this.length - 1;
var model = this.get(m_id),
old_pos = model.get('ordinal');
log('old_pos ' + old_pos);
log('new_pos ' + new_pos);
model.set({
ordinal: new_pos
});
if(old_pos == new_pos){
//trigger associated events
this.sort();
return this;
}
var _this = this;
//update indexes of affected models
this.each(function(m){
//ordinal of current model in loop
var m_ordinal = m.get('ordinal');
//skip if this is the model we just updated
if(m.get('id') == m_id)
return;
if(old_pos < new_pos){
//moving down, ordinal is increasing
if(m_ordinal <= new_pos && !(m_ordinal <= 0)){
//this is in the range we care about
m.set({
ordinal: m.get('ordinal') - 1
});
}
}else if(old_pos > new_pos){
//moving up, ordinal is decreasing
log('p1');
if(m_ordinal >= new_pos && !(m_ordinal >= (_this.length - 1))){
//this is in the range we care about
m.set({
ordinal: m.get('ordinal') + 1
});
}
}
});
this.sort();
return this;
}
EDIT 4
Found another bug, patched it.
Backbone.Collection.prototype.move_to = function(m_id, new_pos) {
//keep within range
if(new_pos < 0)
new_pos = 0;
else if(new_pos > (this.length - 1))
new_pos = this.length - 1;
var model = this.get(m_id),
old_pos = model.get('ordinal');
log('old_pos ' + old_pos);
log('new_pos ' + new_pos);
model.set({
ordinal: new_pos
});
if(old_pos == new_pos){
//trigger associated events
this.sort();
return this;
}
var _this = this;
//update indexes of affected models
this.each(function(m){
log(m.id);
//ordinal of current model in loop
var m_ordinal = m.get('ordinal');
//skip if this is the model we just updated
if(m.get('id') == m_id)
return;
if(old_pos < new_pos){
//moving down, ordinal is increasing
if(m_ordinal <= new_pos && m_ordinal >= old_pos && !(m_ordinal <= 0)){
//this is in the range we care about
m.set({
ordinal: m.get('ordinal') - 1
}, {
silent: true
});
}
}else if(old_pos > new_pos){
//moving up, ordinal is decreasing
log('p1');
if(m_ordinal >= new_pos && m_ordinal <= old_pos && !(m_ordinal >= (_this.length - 1))){
//this is in the range we care about
m.set({
ordinal: m.get('ordinal') + 1
}, {
silent: true
});
}
}
});
this.sort();
return this;
};
If you look at the backbone.js source code, you'll find out that for example the add method supports adding models to certain indexes with
collectionName.add(model, {at: index});
removing from position might need you to make a custom function to the collection, like:
// Your Collection
removeAt: function(options) {
if (options.at) {
this.remove(this.at(options.at));
}
}
for +1 / -1 you can make a custom function and make use of the built-in underscore indexOf-function
// Your Collection
moveUp: function(model) { // I see move up as the -1
var index = this.indexOf(model);
if (index > 0) {
this.remove(model, {silent: true}); // silence this to stop excess event triggers
this.add(model, {at: index-1});
}
}
moveDown: function(model) { // I see move up as the -1
var index = this.indexOf(model);
if (index < this.models.length) {
this.remove(model, {silent: true}); // silence this to stop excess event triggers
this.add(model, {at: index+1});
}
}
This way you can also implement the moveUp and moveDown to the models themselves for more easily readable code!
// Your Model
moveUp: function() {
this.collection.moveUp(this);
}
// And the same for moveDown
But now the index property is not saved in the models themselves. To read the index just use collection.indexOf(model)
, but if you want to store that information in the models at all times, you could bind to the add
and remove
events to update all indexes when changes to the collection are made:
// Your collection
initialize: function(options?) {
...
this.on('add remove', this.updateModelOrdinals);
...
},
...
updateModelOrdinals: function() {
this.each(function(model, index) {
this.model.set('ordinal', index);
});
}
et voilà! Now you should have the functionality you need without reinventing the wheel and still keeping the 0-indexing in place with Backbone's own functionality! Sorry for getting a bit carried off, ask if I went over your head. And read the backbone.js source, you can find seriously useful stuff there!
Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With