Search code examples
javascriptbackbone.jsbackbone-views

backbone.js subview not shown on initial render


I have a view:

App.Views.Rebill = App.Views.baseView.extend({
    view: 'requests._rebill_line',
    tag: 'div',
    className: 'row row-spaced',

    render: function() {
        var self = this;
        App.get_view(self.view).done(function(data){
            var view = Mustache.render(data, {
                text: self.model.get('text'),
                cost: self.model.get('cost')
            });

            self.$el.append(view);

            var $selectVat = self.$el.find('select[name="vat"]');
            var vatPicker = new App.Views.VatPicker({
                model: self.model,
                el: $selectVat
            });
            vatPicker.render();
        });
        return self;
    }
});

this view is created in the parent:

addRebillLine: function(rebillModel){
    var self = this;
    var rebillView = new App.Views.Rebill({
        model: rebillModel
    });
    self.$el.after(rebillView.render().$el);
},

I have linked this to a button, which creates an empty model, and calls the addRebillLine function. This works fine, the new Rebill view appears in the DOM when I click the button.

However, on rendering the parent view, I run through a json array, create a model with each line, and call the addRebillLine function with that model. This runs, but the Rebill views are not added to the DOM.

Further up, the parent view is itself a child view, and is attached to its parent like so:

this.$el.find('[data-role="repair-items"]').append(itemView.render().$el);

The parent and grandparent views are rendered synchronously, and the child view asynchronously (App.get_view() is basically a call to $.ajax, hence the .done())

The wierd thing is that I do the same thing with the App.Views.VatPicker view, in several other places, and that works just fine. The only difference is that I pass an element to attach to into the VatPicker view. But if I pass the parent $el element in, and run this.$parent.after(self.$el) in my done() callback, that doesn't work either.


Solution

  • When you call this line:

    this.$el.find('[data-role="repair-items"]').append(itemView.render().$el);
    

    it assumes your render code is synchronous (as it should be) , but you render code is not. When render return the $el is not set yet, this is a problem in your design.

    You should solve this design issue by making your templates available when the view needs them. Don't invent the wheel here, take a look at the TodoMVC backbone example.

    If you want your templates to be loaded aysnc, use requirejs.