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!


Need Your Help

My Domain Name dosn't point to my amazon S3 static website

amazon-web-services amazon-s3 amazon-route53

So I am hosting my website on AWS S3 and even though my domain is with Route 53 and I have setup the hosted zones as I have seen in the AWS tutorials. But even though I have done this, it does not ...

file uploading successfully, but 0kb file is uploading on remote server using jsch sftp java

java file upload sftp jsch

I am uploading a file from my local computer to remote server in a simple java application using Jsch, and Sftp protocol.