Search code examples
javascriptbackbone.jsunderscore.js

Rendering Backbone.js Collection as a select list


I'm trying to render a Backbone.js collection as a select list using an Underscore.js template, and the list isn't getting populated. The select element is displaying, but there are no options.

I have confirmed that I'm able to pass individual properties into my template and render them as label elements, so the issue must be how I'm trying to handle the collection.

Here's my Backbone code:

Rate = Backbone.Model.extend({
    duration : null
});

Rates = Backbone.Collection.extend({
    initialize: function (model, options) {
    }
});

AppView = Backbone.View.extend({
    el: $('#rate-editor-container'),
    initialize: function () {
      this.rates = new Rates(null, { view: this } );

      this.rates.add(new Rate ({ duration: "Not Set" }));
      this.rates.add(new Rate ({ duration: "Weekly" }));
      this.rates.add(new Rate ({ duration: "Monthly" }));

      this.render();
    },
    render: function() {
      var rate_select_template = _.template($("#rate_select_template").html(), {rates: this.rates, labelValue: 'Something' });
      $('#rate-editor-container').html(rate_select_template);
    },
});

var appview = new AppView();

And my template:

<script type="text/template" id="rate_select_template">
  <select id="rate-selector"></select>
  <% _(rates).each(function(rate) { %>
    <option value="<%= rate.duration %>"><%= rate.duration %></option>
  <% }); %>
</script>

<div id="rate-editor-container"></div>

Any suggestions?


Solution

  • You have a couple different problems.

    1. Your template is trying to put the <option> elements after the <select> instead of inside it. This will produce invalid HTML and the browser will butcher that once you get anything out of your template.
    2. rates is a Backbone collection so it already has access to Underscore's each; wrapping it as _(rates) just confuses Underscore and prevents any iterations from happening.
    3. Inside the iteration, rate is a Backbone model instance so it won't have a duration property, you have to say rate.get('duration').

    Your template should look more like this:

    <script type="text/template" id="rate_select_template">
        <select id="rate-selector">
            <% rates.each(function(rate) { %>
                <option value="<%= rate.get('duration') %>"><%= rate.get('duration') %></option>
            <% }); %>
        </select>
    </script>
    

    Demo: http://jsfiddle.net/ambiguous/AEqjn/

    Alternatively, you can just fix the nesting error in your template to produce valid HTML:

    <script type="text/template" id="rate_select_template">
        <select id="rate-selector">
            <% _(rates).each(function(rate) { %>
                <option value="<%= rate.duration %>"><%= rate.duration %></option>
            <% }); %>
        </select>
    </script>
    

    and use toJSON() in your view to feed raw data to your template rather than the collection itself:

    var rate_select_template = _.template($("#rate_select_template").html(), {
        rates: this.rates.toJSON(),
        labelValue: 'Something'
    });
    

    Demo: http://jsfiddle.net/ambiguous/VAxFW/

    I think the latter one is what you were aiming for as that would a more standard approach to working with templates in Backbone.