TrippLamb
TrippLamb

Reputation: 1619

Trouble using two models in one view for backbone

I'm having trouble in attempting to use two models with the same view in backbone.js. Somehow the view is being rendered before the triggers are getting triggered to begin the render function.

Here are the models:

APP.Models.FamilyPreferences = Backbone.Model.extend({
    initialize: function( attributes, options ) {
        _.bindAll(this, 'success_handler', 'populate');
        this.url = options.url;
    },
    populate: function(){
        this.fetch({success: this.success_handler});
    },
    success_handler: function(){
        this.trigger("change");
    }
});
APP.Models.Preferences = Backbone.Model.extend({
    initialize: function( attributes, options ) {
        _.bindAll(this, 'success_handler', 'error_handler', 'populate');
        this.url = options.url;
    },
    populate: function(){
        this.fetch({success: this.success_handler, error:this.error_handler}); 
    },
    success_handler: function(){
        this.exists = true;
        this.trigger("change");
    },
    error_handler: function(){
        this.exists = false;
        this.trigger("change");
    }
});

Here is the relevant code from the view:

APP.Views.PreferencesFormView = Backbone.View.extend({
templates: [{name:"preferences_template", file_path:"preferences_form.html"}],

initialize: function(options){
    _.bindAll(this, 'render', 'renderPrereq');
    var family_url = "services/family/" + options.family_id;
    var preferences_url = "services/preferences/familyID/" + options.family_id;
    var ctx = this;
    this.alreadyRendered = false;
    this.modelsCurrentlyLoaded = [];

    this.models = {};

    this.models.family = new APP.Models.FamilyPreferences({}, {url: family_url});
    this.models.family.on('change', function(){ctx.renderPrereq("family");});
    this.models.family.populate();

    this.models.preferences = new APP.Models.Preferences({}, {url:preferences_url});
    this.models.preferences.on('change', function() {ctx.renderPrereq("preferences");});
    this.models.preferences.populate();
},

renderPrereq: function(newmodel){
    var inside = ($.inArray(newmodel, this.modelsCurrentlyLoaded)>-1) ? true : false;
    if (!(inside)){this.modelsCurrentlyLoaded.push(newmodel);}

    var total = Object.keys(this.models).length;
    if(this.modelsCurrentlyLoaded.length == total && !this.alreadyRendered){
        this.alreadyRendered = true;
        this.render();
    }
},
render: function(){

    var family_data = {
        id: this.models.family.attributes.id,
        familyGroup: this.models.family.attributes.groupNum,
        familyId: this.models.family.attributes.familyId,
        probandDob: this.models.family.attributes.childDob,
    }

    var preferences_data = {
        mother: this.models.preferences.attributes[0],
        father: this.models.preferences.attributes[1],
        exists: this.models.preferences.exists
    }

    this.$el.html(this.compiledTemplates.preferences_template(family_data));
    //bunch of javascript making the page work
}
});

The template are getting loaded through another js function elsewhere that to the best of my knowledge is working correctly. Somehow the render function is getting called before the success handlers. I can't figure out how. The only side effect it has is that the Preferences model doesn't get the exists property and is hence undefined which cause all sorts of problems. Also, options.family_id is set correctly. Any help would be appreciated. Thank you in advance.

EDIT: It also seems to be going to renderPrereq up to six times which I also can't figure out.

JSON - Family model

{
    "id": 1,
    "familyId": "family01",
    "collectionDate": "2013-01-01",
    "childDob": "2001-05-‌​06",
    "groupNum": "Two",
    "probands": [],
    "mother": null,
    "father": null,
    "siblings": []
}

JSON - First part of Preferences Model

[{
    "main": "illness-only",
    "obesity": "No",
    "bloodPressure": "No",
    "diabetes": "No",
    "hea‌​rt": "No",
    "alzheimers": "No",
    "parkinsons": "No",
    "mentalHealth": "No",
    "breastOvarianCa‌​ncer": "No",
    "prostateTesticularCancer": "No",
    "otherCancer": "No",
    "childSickleCell": "‌​No",
    "childCysticFibrosis": "No",
    "childMuscularDystrophy": "No",
    "childAutism": "No"
}, 
{
    "main":"more-questions",
    "obesity":"No",
    "bloodPressure":"Yes",
    "diabetes":"No",
    "heart":"Unsure",
    "alzheimers":"No",
    "parkinsons":"Yes",
    "mentalHealth":"No",
    "breastOvarianCancer":"No",
    "prostateTesticularCancer":"No",
    "otherCancer":"No",
    "childSickleCell":"No",
    "childCysticFibrosis":"No",
    "childMuscularDystrophy":"No",
    "childAutism":"No"}]

Headers

Accept: application / json, text / javascript, /; q=0.01
Accept-Encoding:gzip,deflate,sdch 
Accept-Language:en-US,en;q=0.8  
Cache-Control:no-cache Connection:keep-alive     
Cookie:csrftoken=bDIpdvAPBdWF6dZe9BkpsFSF4wiGl2qX 
Host:localhost:8080 Pragma:no-cache  Referer:localhost:8080/CSER / index.html 
User - Agent: Mozilla / 5.0(Macintosh; Intel Mac OS X 10_8_3) AppleWebKit /
537.36(KHTML, like Gecko) Chrome / 27.0.1453.110 Safari / 537.36 X - Requested - With: XMLHttpRequest

Try this code ..

APP.Models.FamilyPreferences = Backbone.Model.extend({
    initialize: function (attributes, options) {
        _.bindAll(this, 'success_handler', 'populate');
        this.url = options.url;
    },
    populate: function () {
        this.fetch();
    },
    // This is not required
    // It will automatically render it as we are listening to the sync event
    success_handler: function () {
        // Not required 
        // sync event will take care of it
    }
});
APP.Models.Preferences = Backbone.Model.extend({
    initialize: function (attributes, options) {
        _.bindAll(this, 'success_handler', 'error_handler', 'populate');
        this.url = options.url;
    },
    populate: function () {
        this.fetch({
            success: this.success_handler,
            error: this.error_handler
        });
    },
    success_handler: function () {
        this.exists = true;
        // Not required 
        // sync event will take care of it
    },
    error_handler: function () {
       // Do something else
    }
});

APP.Views.PreferencesFormView = Backbone.View.extend({
    templates: [{
        name: "preferences_template",
        file_path: "preferences_form.html"
    }],

    initialize: function (options) {
        _.bindAll(this, 'render', 'renderPrereq');
        var family_url = "services/family/" + options.family_id;
        var preferences_url = "services/preferences/familyID/" + options.family_id;
        var ctx = this;
        this.alreadyRendered = false;
        this.modelsCurrentlyLoaded = [];

        this.models = {};
        // Family Model
        this.models.family = new APP.Models.FamilyPreferences({}, {
            url: family_url
        });

        this.listenTo( this.models.family, 'change', function () {
            ctx.renderPrereq("family");
        });
        // This will take care of rendering it on Sync with server
        // No need to triggereing the event explicitly..
        this.listenTo( this.models.family, 'sync', function () {
            ctx.renderPrereq("family");
        });        

        this.models.family.populate();

        // Family Preference Model
        this.models.preferences = new APP.Models.Preferences({}, {
            url: preferences_url
        });

        this.listenTo( this.models.preferences, 'change', function () {
            ctx.renderPrereq("family");
        });
        // This will take care of rendering it on Sync with server
        // No need to triggereing the event explicitly..
        this.listenTo( this.models.preferences, 'sync', function () {
            ctx.renderPrereq("preferences");
        });  
        this.models.preferences.populate();
    },

    renderPrereq: function (newmodel) {
        var inside = ($.inArray(newmodel, this.modelsCurrentlyLoaded) > -1) ? true : false;
        if (!(inside)) {
            this.modelsCurrentlyLoaded.push(newmodel);
        }

        var total = Object.keys(this.models).length;
        if (this.modelsCurrentlyLoaded.length == total && !this.alreadyRendered) {
            this.alreadyRendered = true;
            this.render();
        }
    },
    render: function () {

        var family_data = this.models.family.toJSON());

        var preferences_data = {
            mother: this.models.preferences.attributes[0],
            father: this.models.preferences.attributes[1],
            exists: this.models.preferences.get('exists') ? this.models.preferences.get('exists') : false
        }

        this.$el.html(this.compiledTemplates.preferences_template(family_data);
        //bunch of javascript making the page work
    }
});

Upvotes: 2

Views: 549

Answers (1)

Sushanth --
Sushanth --

Reputation: 55740

Looks like the problem is the way it is constructed... I don't think the render is called before success_handler .

If you look at the renderPrereq method , 2 models on change will call this method on success.

But the fetch methods are async. So you never know which handler will be called first.

And when the change on the model is triggered. This calls the render method which is inside the renderPrereq method.

Upvotes: 1

Related Questions