Search code examples
backbone.jshandlebars.js

Add custom property to every item of a collection


I'm trying to add a custom property to every item of a collection, but it doesn't show up in the template.

I have a lot of quotations which have a client_id. Now I want to fetch the client by the client_id and add it to the collection entry. In general, it works when inspecting the populated object with console.log, but it doesn't show up in the template.

That's how I tried it:

sprocket.QuotationsView = Backbone.View.extend({
    id: 'content-inner',

    initialize: function(options) {
      // instantiate Collection
      this.collection = new Quotations();

      // compile Handlebars template
      this.tpl = Handlebars.compile(this.template);
    },

    render: function() {
      var self = this;
      var obj = this.el;

      // get quotations and set data to handlebars template
      $.when(this.collection.fetch()).then(function(quotations) {

        $.each(quotations, function(i, quotation) {

          var loadContact = new Contact({id: quotation.contact_id}).fetch();
          $.when(loadContact).then(function(contact) {
            quotations[i]['contact'] = contact;
          });

        });

        $(obj).html(self.tpl(quotations));

      // if response is empty (no quotations in database), set empty template
      }, function() {
        $(obj).html(self.tpl);
      });

      return this;
    }
});

My template looks like this:

<div>
  {{#if .}}
    {{#each .}}
      {{number}} <!-- this works -->
      {{contact}} <!-- this doesn't work -->
      {{contact.name}} <!-- this doesn't work too -->
    {{/each}}
  {{/if}}
</div>

Solution

  • That is because the callback that actually changes the data inside the Quotation.attribute.contact (ie. your quotations[i]['contact'] = contact; line) is being executed after fetching the Contact which happens to be after the template is being rendered.

    $.each(quotations, function(i, quotation) {
    
      var loadContact = new Contact({id: quotation.contact_id}).fetch();
      // This is the callback that is being executed after the backend responds
      $.when(loadContact).then(function(contact) {
         quotations[i]['contact'] = contact;
      });
    
    });
    
    // This is the template rendering which is before the server has time to respond
    $(obj).html(self.tpl(quotations));
    

    Render instead the template after all Contacts are being fetched and added to Quotations.

    A quick way to solve this:

    1. Make the loop which load all the Contacts inside a function that contains a callback.
    2. Call the callback after all Contacts have loaded.
    3. The callback should render the template.

    This is a personal opinion and in no way an answer to this question: I don't like the data logic with the backend and the view rendering and logic in the same class. I use Backbone.Marionette to split the View and the Controller in two different entities loosely coupled by events. Only the Controller is aware of the View and only the View is aware of the DOM.