Renato Alves
Renato Alves

Reputation: 115

Backbone: Using views inside _.template

Im trying to create a dynamic Widget system to create panels inside my page. Lets took a single example:

                Widgets.create({
                    name : 'menu-site',
                    title: 'Menu', 
                    content: 'This is my menu!'
                });  

It will render this:

<section class="widget menu-site">
    <h1>Menu</h1>
    <article>This is my menu!</article>
</section>

Its ok right? I have done it thousand times. However, the problem starts when i try to use a view inside:

                var view = new MyCustomView;                  
                Widgets.create({
                    name : 'custom-panel',
                    title: 'Custom Panel', 
                    content: view.render().el
                });                    

I can render the content by calling the outerHTML inside the WidgetView, however, when i do it, the events declared inside MyCustomView does not work!

Someone ever faced this problem?

For those who need more details, this is my WidgetView code:

        var Widget = Backbone.Model.extend({
            defaults : function() {
                return {
                    title : '',
                    content : '',
                    closeable : true
                }
            }
        });
        var WidgetList = Backbone.Collection.extend({
            url : '/widgets',
            model : Widget
        });
        Widgets = new WidgetList;


        var WidgetView = Backbone.View.extend({
            tagName : 'section',
            className : function() {
                return ['widget',' ', this.model.get('name')].join('');
            },
    template: _.template($('#widget-template').html()),

            events : {
                "click .close" : "removeWidget"
            },

            initialize : function() {
                this.listenTo(this.model, 'destroy', this.remove);
            },


            removeWidget  : function() {
                this.model.destroy();
            },

            render: function() {
              this.$el.append(this.template(this.model.toJSON()));   
              return this;
            },
        });

        var AppViewport = Backbone.View.extend({
            el : $("#widgets"),

            events : {
            },

            initialize : function() {
                this.listenTo(Widgets, 'add', this.addNewWidgetView);
            },

            addNewWidgetView : function(widget) {
              var view = new WidgetView({model: widget});
              this.$el.append(view.render().el);
            }
        });

        new AppViewport;

Or maybe, im thinking about another workaround for this.. But i wasted so much time trying to understand this problem that i just cant see the light!

UPDATE --------------- 18/01 ---------

I made some tests, and changed my code: Instead of using an underscore template, i created a WidgetViewLayout, and did all the template work manually:

            var WidgetView = Backbone.View.extend({
                tagName : 'section',
                className : function() {
                    return ['widget',' ', this.model.get('name')].join('');
                },

                events : {
                    "click .close" : "removeWidget"
                },

                initialize : function() {
                    this.listenTo(this.model, 'destroy', this.remove);
                },


                removeWidget  : function() {
                    this.model.destroy();
                    MenuRouterInstance.navigate('');
                },

                render: function() {
                    var layout_view = new WidgetViewLayout({model: this.model.toJSON()});
                    this.$el.append(layout_view.render().el);   
                    return this;
                }
            });

            var WidgetViewLayout = Backbone.View.extend({
                tagName : 'div',

                render : function() {
                    var title = this.model.title,
                        content = this.model.content,
                        closeable = this.model.closeable || false;

                    var anchor = this.setupAnchor(closeable),
                        h1 = this.setupH1(title, anchor),
                        article = this.setupArticle(content);

                    this.$el.append(h1);
                    this.$el.append(article);

                    return this;
                },

                setupH1 : function(title, target) {
                    var element = $('<h1 />');
                    element.text(title);
                    element.append(target);   

                    return element;
                },

                setupAnchor : function(closeable) {
                    var element = '';
                    if(closeable == true) {
                        element = $('<a />'); 
                        element.addClass('close');
                        element.text('(fechar)');
                    }

                    return element;
                },

                setupArticle : function(content) {
                    var element = $('<article />');
                    element.append(content);

                    return element;
                }
            })

It seems to work now, but i still have some problems with the views created inside my Widget, like that:

                    var view = new MyView;                  
                    Widgets.create({
                        name : 'menu',
                        title: 'My menu', 
                        content: view.render().el
                    }); 

When MyView creates multiple SubViews inside itself, and i call model.destroy to remove one of the Subview's Model, the listened method "this.remove" ends up removing all SubViews, instead of removing only one. In the end, i was forced to do this:

                removeit : function(removed_item) {
                    if(removed_item.get('name') == this.model.get('name')) {
                        this.remove();                            
                    }
                },

I dont know if its a bad pattern, but it have worked! I appreciate if someone had a better idea!

][`,s

Upvotes: 1

Views: 1003

Answers (1)

mu is too short
mu is too short

Reputation: 434615

I think your problem is that you're trying to treat a DOM element object (el) as a string. When you do this:

content: view.render().el

you'll have a live DOM node in content and the view's events will be bound to that DOM node. You can use one of the HTML methods (apparently outerHTML in your case) to get the node's HTML through your template but delegate that handles the events is bound to the DOM node, not to the string of HTML that you end up working with.

You could modify your render a bit to work with the raw DOM node as-is when necessary, something like this.

render: function() {
    var json = this.model.toJSON();
    if(_(json.content).isString()) {
        this.$el.append(this.template(json));
    }
    else {
        var content = json.content;
        delete json.content;
        this.$el.append(this.template(json));
        this.$('article').append(content);
    }
}

Basically, if your content isn't a string, then put into your $el manually rather than going through the template.

Upvotes: 3

Related Questions