# BackboneJS best way to rearrange models in a collection while maintaining 0-indexed ordinal property for each model

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; };

## Answers

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!