Try out our recently released Firebug extension - Backbone Eye : Understand Backbone application behavior without debugging JavaScript!

Example Hierarchy


The tutorial uses the example below to walk through the process of defining associations, creating object instances and being able to tune-in to various kinds of events which are thrown due to updates in the object graph. This image was generated via code! Check it out here

Hierarchy Definition using Backbone-associations


This section demonstrates how to convert the example into an AssociatedModels representation.

//Convert the associations into model definitions.
var Location = Backbone.AssociatedModel.extend({
    defaults:{
        add1:"",
        add2:null,
        zip:"",
        state:""
    }
});

var Project = Backbone.AssociatedModel.extend({
    relations:[
        {
            type:Backbone.Many,
            key:'locations',
            relatedModel:Location
        }
    ],
    defaults:{
        name:"",
        number:0,
        locations:[]
    }
});


var Department = Backbone.AssociatedModel.extend({
    relations:[
        {
            type:Backbone.Many,
            key:'controls',
            relatedModel:Project
        },
        {
            type:Backbone.Many,
            key:'locations',
            relatedModel:Location
        }
    ],
    defaults:{
        name:'',
        locations:[],
        number:-1,
        controls:[]
    }
});

var Dependent = Backbone.AssociatedModel.extend({
    validate:function (attr) {
        return (attr.sex && attr.sex != "M" && attr.sex != "F") ? "invalid sex value" : undefined;
    },
    defaults:{
        fname:'',
        lname:'',
        sex:'F', //{F,M}
        age:0,
        relationship:'S' //Values {C=Child, P=Parents, S=Spouse}
    }
});

var Employee = Backbone.AssociatedModel.extend({
    relations:[
        {
            type:Backbone.One,
            key:'works_for',
            relatedModel:Department
        },
        {
            type:Backbone.Many,
            key:'dependents',
            relatedModel:Dependent
        },
        {
            type:Backbone.One,
            key:'manager',
            relatedModel:'Employee'
        }
    ],
    validate:function (attr) {
        return (attr.sex && attr.sex != "M" && attr.sex != "F") ? "invalid sex value" : undefined;
    },
    defaults:{
        sex:'M', //{F,M}
        age:0,
        fname:"",
        lname:"",
        works_for:{},
        dependents:[],
        manager:null
    }
});

Hierarchy Instantiation


This section sets-up instances and associations with the model definitions and associations created in the earlier section.
emp = new Employee({
    fname:"John",
    lname:"Smith",
    age:21,
    sex:"M"
});

child1 = new Dependent({
    fname:"Jane",
    lname:"Smith",
    sex:"F",
    relationship:"C"

});

child2 = new Dependent({
    fname:"Barbara",
    lname:"Ruth",
    sex:"F",
    relationship:"C"

});

parent1 = new Dependent({
    fname:"Edgar",
    lname:"Smith",
    sex:"M",
    relationship:"P"

});

loc1 = new Location({
    add1:"P.O Box 3899",
    zip:"94404",
    state:"CA"

});

loc2 = new Location({
    add1:"P.O Box 4899",
    zip:"95502",
    state:"CA"
});

project1 = new Project({
    name:"Project X",
    number:"2"
});

project2 = new Project({
    name:"Project Y",
    number:"2"
});

project2.get("locations").add(loc2);
project1.get("locations").add(loc1);

dept1 = new Department({
    name:"R&D",
    number:"23"
});

dept1.set({locations:[loc1, loc2]});
dept1.set({controls:[project1, project2]});

emp.set({"dependents":[child1, parent1]});

Update related examples


In this section, we look at scenarios where various kind of updates can be listen-ed to at higher levels.
  • Assign Associated Model instances to other properties
    Backbone-associations allows for assignment of previously instantiated model instances to another model's attributes.
    emp.on('change', function () {
        console.log("Fired emp > change...");
        //emp.hasChanged() === true;
        //emp.hasChanged("works_for") === true;
    });
    emp.on('change:works_for', function () {
        console.log("Fired emp > change:works_for...");
        var changed = emp.changedAttributes();
        //changed['works_for'].toJSON() equals emp.get("works_for").toJSON()
        //emp.previousAttributes()['works_for'].get('name') === "");
        //emp.previousAttributes()['works_for'].get('number') === -1;
        //emp.previousAttributes()['works_for'].get('locations').length === 0;
        //emp.previousAttributes()['works_for'].get('controls').length === 0;
    });
    
    emp.set({works_for:dept1});
    //Console log
    //Fired emp > change:works_for...
    //Fired emp > change...
                    
  • Listen to updates of attributes in nested AssociatedModel instances
    The example shows all the possible ways to listen to changes at various levels within the object hierarchy.
    emp.get('works_for').on('change', function () {
        console.log("Fired emp.works_for > change...");
        //emp.get("works_for").hasChanged() === true;
        //emp.get("works_for").previousa-tributes()["name"] === "R&D";
    });
    emp.get('works_for').on('change:name', function () {
        console.log("Fired emp.works_for > change:name...");
    
    });
    
    emp.on('change:works_for.name', function () {
        console.log("Fired emp > change:works_for.name...");
        //emp.get("works_for").hasChanged() === true;
        //emp.hasChanged() === true;
        //emp.hasChanged("works_for") === true;
        //emp.changedAttributes()['works_for'].toJSON() equals emp.get("works_for").toJSON();
        //emp.get("works_for").previous-attributes()["name"] === "R&D";
        //emp.get("works_for").previous("name") === "R&D";
    });
    
    emp.on('change:works_for', function () {
        console.log("Fired emp > change:works_for...");
        //emp.hasChanged());
        //emp.hasChanged("works_for"));
        //emp.changedAttributes()['works_for'].toJSON() equals emp.get("works_for").toJSON();
        //emp.previous-attributes().works_for.name === "R&D";
    });
    
    emp.on('nested-change', function () {
        console.log("Fired emp > nested-change...");
    });
    
    emp.get('works_for').set({name:"Marketing"});
    
    //Console log
    // @ emp.get('works_for') level
    //Fired emp.works_for > change:name
    //Fired emp.works_for > change...
    
    //@ emp level
    //Fired emp > change:works_for.name...
    //Fired emp > change:works_for...
    //Fired emp > nested-change...
                    
  • Listen to updates to an item within a collection of AssociatedModels
    The example shows all the possible ways to listen to changes at various levels within the object hierarchy.
    //The actual set operation
    emp.get('works_for').get("locations").at(0).set('zip', 94403);
                    
    A simple update like the one above can be listened to at various levels. See the examples below.
    //Listen in @ emp.works_for.locations[0]. Should receive regular backbone events
    emp.get('works_for').get('locations').at(0).on('change:zip', function () {
        console.log("Fired emp.works_for.locations[0] > change:zip...");
    });
    
    emp.get('works_for').get('locations').at(0).on('change', function () {
        console.log("Fired emp.works_for.locations[0] > change...");
    });
    
    //@ emp.get('works_for').get('locations') level
    //Fired emp.works_for.locations[0] > change...
    //Fired emp.works_for.locations[0].zip > change...
                    
    //Listen in @ emp.works_for level
    emp.get('works_for').on('change:locations[0].zip', function () {
        console.log("Fired emp.works_for > change:locations[0].zip...");
    });
    
    emp.get('works_for').on('change:locations[0]', function () {
        console.log("Fired emp.works_for > change:locations[0]...");
    });
    
    emp.get('works_for').on('change:controls[0].locations[0].zip', function () {
        console.log("Fired emp.works_for > change:controls[0].locations[0].zip...");
    });
    
    emp.get('works_for').on('change:controls[0].locations[0]', function () {
        console.log("Fired emp.works_for > change:controls[0].locations[0]...");
    });
    
    emp.get('works_for').on('change:locations[*]', function () {
        console.log("Fired emp.works_for > change:locations[*]...");
    });
    
    emp.get('works_for').on('change:controls[*].locations[*]', function () {
        console.log("Fired emp.works_for > change:controls[*].locations[*]...");
    });
    
    emp.get('works_for').on('change:controls[*].locations[*].zip', function () {
        console.log("Fired emp.works_for > change:works_for.controls[*].locations[*].zip...");
    });
    
    emp.get('works_for').on('nested-change', function () {
        if (arguments[0] == "controls[0].locations[0]")
            console.log("Fired emp.works_for > nested-change:controls[0].locations[0]...");
        if (arguments[0] == "locations[0]")
            console.log("Fired emp.works_for > nested-change:locations[0]...");
    });
    
    //Console log
    //@ emp.get('works_for') level
    //Fired emp.works_for > change:controls[0].locations[0]...
    //Fired emp.works_for > change:controls[0].locations[0].zip...
    //Fired emp.works_for > change:locations[0]...
    //Fired emp.works_for > change:locations[0].zip...
    //Fired emp.works_for > nested-change:controls[0].locations[0]...
    //Fired emp.works_for > nested-change:locations[0]...
    //Fired emp.works_for > change:locations[*]...
    //Fired emp.works_for > change:controls[*].locations[*]...
    //Fired emp.works_for > change:works_for.controls[*].locations[*].zip...
                    
    //Listen in @ emp level
    emp.on('change:works_for.locations[0].zip', function () {
        console.log("Fired emp > change:works_for.locations[0].zip...");
    });
    
    emp.on('change:works_for.locations[0]', function () {
        console.log("Fired emp > change:works_for.locations[0]...");
    });
    
    emp.on('change:works_for.controls[0].locations[0].zip', function () {
        console.log("Fired emp > change:works_for.controls[0].locations[0].zip...");
    });
    
    emp.on('change:works_for.controls[0].locations[0]', function () {
        console.log("Fired emp > change:works_for.controls[0].locations[0]...");
    });
    
    emp.on('nested-change', function () {
    if (arguments[0] == "works_for.controls[0].locations[0]")
        console.log("Fired emp > nested-change:works_for.controls[0].locations[0]...");
    if (arguments[0] == "works_for.locations[0]")
        console.log("Fired emp > nested-change:works_for.locations[0]...");
    });
    
    emp.on('change:works_for.locations[*]', function () {
        console.log("Fired emp > change:works_for.locations[*]...");
    });
    
    emp.on('change:works_for.controls[*].locations[*]', function () {
        console.log("Fired emp > change:works_for.controls[*].locations[*]...");
    });
    
    emp.on('change:works_for.controls[*].locations[*].zip', function () {
        console.log("Fired emp > change:works_for.controls[*].locations[*].zip...");
    });
    //@ emp level
    //Fired emp > change:works_for.controls[0].locations[0]...
    //Fired emp > change:works_for.controls[0].locations[0].zip...
    //Fired emp > change:works_for.locations[0]...
    //Fired emp > change:works_for.locations[0].zip...
    //Fired emp > nested-change:works_for.controls[0].locations[0]...
    //Fired emp > nested-change:works_for.locations[0]...
    //Fired emp > change:works_for.locations[*]...
    //Fired emp > change:works_for.controls[*].locations[*].zip...
    //Fired emp > change:works_for.controls[*].locations[*]...
                

Add-Remove-Reset related examples


Similar to the update example in the earlier section, grandparents (and others) can listen-in to add, remove, reset, sort and destroy operations on any grand-children at any level of depth.
emp.on('add:dependents', function () {
    console.log("Fired emp > add:dependents...");
});
emp.on('remove:dependents', function () {
    console.log("Fired emp > remove:dependents...");
});
emp.on('reset:dependents', function () {
    console.log("Fired emp > reset:dependents...");
});

emp.get('dependents').on('add', function () {
    console.log("Fired emp.dependents add...");
});
emp.get('dependents').on('remove', function () {
    console.log("Fired emp.dependents remove...");
});
emp.get('dependents').on('reset', function () {
    console.log("Fired emp.dependents reset...");
});

emp.get("dependents").add(child2);
emp.get("dependents").remove([child1]);
emp.get("dependents").reset();

//Console log
//Fired emp.dependents add...
//Fired Fired emp.dependents remove...
//Fired emp.dependents reset...

//Fired emp > add:dependents...
//Fired emp > remove:dependents...
//Fired emp > reset:dependents...
                

Next Steps


See real-world recipes contributed by users.