Search code examples
backbone.jsmustachebackbone-views

How to use backbone with mustache and an unknown number of views?


I'm using mustache to render a series of images. Each image is accompanied by a text input for setting the image's caption, an update button for updating the caption in the database via Ajax, and a delete button for deleting the image from the database (also via Ajax):

{{#logos}}
<div class="logo">
    <div class="logo-input">
        <input type="text" placeholder="Caption" value="{{caption}}" />
    </div>
    <div class="logo-buttons">
        <button>Update</button>
        <button>Delete</button>
    </div>
</div>
{{/logos}}

I'm new to backbone, and I can't figure out how to "associate" each update/delete button with its corresponding logo/caption. What's the best way to do this, considering that the number of images is unknown at run time?


Solution

  • You have two problems:

    1. How do you know which button is pressed?
    2. How do you know which logo you're working with?

    The easiest way to solve the first problem is to attach a class to the buttons:

    <button type="button" class="update">Update</button>
    <button type="button" class="delete">Delete</button>
    

    Then you can bind the events directly to the buttons using the view's events:

    events: {
        'click .update': 'update_caption',
        'click .delete': 'delete_caption'
    }
    

    Also, you should always specify the type attribute when using <button>, the spec says that <button type="button"> is the default but some browsers use <button type="submit"> instead. If you always specify type="button" you don't have to worry about what sort of nonsense the browsers will get up to.

    Now you have to figure out which logo you're working with.

    One way is to keep using a single view and attach a data attribute somewhere easy to find. For example:

    {{#logos}}
        <div class="logo" data-logo="{{id}}">
            <!-- ... -->
        </div>
    {{/logos}}
    

    Then you can do things like this in the click handlers:

    update_caption: function(ev) {
        var id   = $(ev.currentTarget).closest('.logo').data('logo');
        var logo = this.collection.get(id);
        //...
    }
    

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

    Alternatively, you could use one sub-view for each logo. Here you'd have one view per-logo:

    var LogoView = Backbone.View.extend({
        className: 'logo',
        events: {
            'click .update': 'update_caption',
            'click .delete': 'delete_caption'
        },
        //...
    });
    

    and a template without the {{#logos}} loop or the outer per-logo <div>:

    <div class="logo-input">
        <input type="text" placeholder="Caption" value="{{caption}}" />
    </div>
    <div class="logo-buttons">
        <button>Update</button>
        <button>Delete</button>
    </div>
    

    and the click handlers would simply look at this.model:

    update_caption: function() {
        var logo = this.model;
        //...
    }
    

    Then a main view to iterate over the logos and render the subviews:

    var LogosView = Backbone.View.extend({
        render: function() {
            this.collection.each(function(logo) {
                var v = new LogoView({ model: logo });
                this.$el.append(v.render().el);
            }, this);
            return this;
        },
        //...
    });
    

    Demo: http://jsfiddle.net/ambiguous/9A756/