Search code examples
backbone.jsmarionette

marionetteItemView Childview, events not triggered


So this is the code on the ParentView: relevant method:

openChildView: function(){

    var childView = new ChildView({
            container: $container //container where the child map should be appended
    });

    childView.show();
}

this is the code of the ChildView:

    ChildView = Marionette.ItemView.extend({

        template: _.template($(templates_c).filter('#my_child_view_template').html()),

        ui: {
            saveButton: 'button.js-save',
            uploadButton: 'button.js-upload',
            deleteButton: 'button.js-discard'
        },
        events: {
            'click @ui.saveButton': function() {
                log.debug('save');
            },
            'click @ui.uploadButton': function() {
                log.debug('upload');
            },
            'click @ui.deleteButton': function() {
                log.debug('delete');
            }
        },
        show: function() {
            this.render();
        },

        initialize: function(){
            this.container = this.options.container;
            this.id = this.options.id;
        },

        remove: function(){
            // Don't know yet if this is the proper way, but for now it works
            this.container.find('.container-child-view').remove();
        },

        render: function(){
            this.container.append(this.template({
                id: this.id,
                img: '...'
            }));

            log.debug('on render');
            this.bindUIElements();
            this.delegateEvents();

            return this;
        }
    });

this is the code of the template of the ChildView:

<script type="text/x-template" id="my_child_view_template">
    <div class="block container-child-view">
        <div class="block-title">
            <h4><%= id %></h4>
        </div>

        <div class="img">
            <img src="<%= imgUrl %>">
        </div>
        <div class="row text-center">
            <div class="btn-group btn-group-xs">
                <button id="discard" type="button" class="js-discard btn btn-alt btn-warning"><i class="fa fa-times-circle"></i> Cancel</button>
                <button id="upload" type="button" class="js-upload btn btn-alt btn-primary"><i class="fa fa-upload"></i> Upload</button>
                <button id="save" type="button" class="js-save btn btn-alt btn-primary"><i class="fa fa-save"></i> Save</button>
            </div>
        </div>
    </div>
</script>

The childView is being rendered on top of the parentview but no events are triggered on it (save, discard, upload).

Any hints/ideas?

Thanks.


Solution

  • The way .delegateEvents() binds events is by delegating to the view's el. In the source you'd see this:

    delegate: function(eventName, selector, listener) {
      this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
    },
    

    As you can see the jQuery .on() is invoked on the view's $el. If a selector argument is passed to .delegate() (like, say .js-save) that selector's events are delegated to its containing view's el.

    The way you override .render() you chose to substitute the "all-mighty" this.el for this.container. Invoking this.delegateElements() still calls .delegate() to do the binding on this.el, while your view actually lives in this.container. The view events are bubbling up to an unbound container.

    A quick fix may be to override render like this:

        render: function(){
            this.container.append(this.template({
                id: this.id,
                img: '...'
            }));
    
            log.debug('on render');
            this.bindUIElements();
    
            // .setElement is an internal method that will
            // make your container the view's el and will call
            // .delegateEvents on this new el
            this.setElement();
    
            return this;
        }
    });
    

    I probably can't call myself an upright Marionette community denizen unless I also provided you a more framework-oriented solution. It looks to me that you've overriden .render because you want to 1. render the ItemView into a specific containing element and 2. you want to load non-model data into your template.

    1. Setting the view el

    Backbone will take an existing DOM element for a view's el. Just pass in a selector or a jQuery object on the view's instantiation,

    var childView = new ChildView({ el: $container });
    

    The Marionette .render() method will attach the view to that $container.

    2. Passing in non-model data to your template

    Marionette has this handy view utility, templateHelpers, that lets you put data in the scope of the template renderer. The data can be any JS object, including functions. Just include the property in your view.

    templateHelpers: function () {
        return {
          img: function(){
            return 'http://some-domain.com/img/" + this.id;
          },
    
          id: this.id
        };
      }
    });
    

    where, in this version of the templateHelper this is the context of the view.