3

I have a tree of categories and I would like to do rending using by Backbone.js instead of using jQuery or do rendering on the server-side. I have the following breakout that I described as template:

<li>
    <select class="categories">
        <option value="">Select</option>
    </select>
    <input class="edit" type="button" value="Edit">
    <input class="add" type="button" value="Add">
</li>

The tag select I render separately as inner view. When I change select I need get nested categories from the server and append them to the tag li using the template above wrapped in the tag ul. In outer view I create inner view and listen events on click at editing and adding. I have a trouble with the last events when the level of nesting is more than one because they are fires equal times to the level of nesting. What am I doing wrong?

The below code snippet shows how I work with outer and inner views:

    var CategoriesInnerView = Backbone.View.extend({
        tagName:       'select',
        initialize:    function(){
            _.bindAll(this,'addOne','addAll');
            this.collection.bind('reset',this.addAll);
        },
        addOne:        function(category){
            this.$el.append(new CategoryView({model:category}).render().el);
        },
        addAll:        function(){
            this.collection.each(this.addOne);
        },
        events:        {
            'change':'changeSelected'
        },
        changeSelected:function(){
            var children = new Categories();
            children.url = 'categories/' + this.$el.val();

            var childrenView = new CategoriesOuterView({collection:children});
            this.$el.parent().find('ul').remove();
            this.$el.parent().append(childrenView.render().el);

            children.fetch();
        }
    });

    var CategoriesOuterView = Backbone.View.extend({
        tagName:   'ul',
        template:  _.template($('#categories-template').html()),
        initialize:function(){
            this.inner = new CategoriesInnerView({collection:this.collection});
        },
        render:    function(){
            this.$el.html(this.template);

            this.inner.setElement(this.$('select')).render();

            return this;
        },
        events:    {
            'click .edit':'edit',
            'click .add': 'add'
        },
        edit:    function(){
            this.renderForm(this.collection.get(this.inner.$el.val()));
        },
        add:    function(){
            this.renderForm(new Category());
        },
        renderForm:function(category){
            // some code to render the form
        }
    });
2
  • "by Backbone.js instead of using jQuery", Backbone doesn't have any built in rendering functionality. Commented Dec 28, 2012 at 21:10
  • Yes, I know. I was wrong. Commented Dec 28, 2012 at 21:11

2 Answers 2

3

When you set up nested views, you have to take into account the fact that events will bubble up the DOM tree and that Backbone handles DOM events at the view.el level. This means that in your scenario, nested nodes will also trigger events in the parent nodes if you let the event go up the hierarchy.

See http://jsfiddle.net/PX2PL/ for a demo

A simple solution would be to stop the event propagation in your callbacks

var CategoriesOuterView = Backbone.View.extend({
    events:    {
        'click .edit':'edit',
        'click .add': 'add'
    },
    edit: function(e) {
        e.stopPropagation();
        this.renderForm(this.collection.get(this.inner.$el.val()));
    },
    add: function(e) {
        e.stopPropagation();
        this.renderForm(new Category());
    }
}

And an updated demo http://jsfiddle.net/PX2PL/1/

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the answer, I have already resolved it in the same way.
2

We really need to seem some of your code to answer this properly, but it sounds like one of two things is happening:

1) you are instantiating your view on the same element multiple times

2) your event selectors are too broad

But without actually seeing (the relevant parts of) your view, it's hard to say more than that.


Tangentially-Related Side Note

BTW, when you have something like this, there are two basic approaches you can take:

1) you can have your parent view create sub views, and put the event handling on the sub-views

2) you can have your parent view create sub views, or not (it could create all the HTML itself), and put the event handling on it.

The advantage of #1 is simplicity: your event handlers can just refer to this to refer to the relevant view. However, there's a problem with #1 if you have to scale it up too much: millions of views, each with their own event handlers, will hurt performance.

So, if performance is/will be important, #2 is better because you're only doing a single set of event hookups. However, you're event handlers will have to be smarter, because they'll have to figure out what element their working on based on the provided event (ie. e.target, vs. this in the #1 approach).

2 Comments

For what it's worth, I have views with 4-5 levels of nesting, and route 1 is always very simple and easy to implement.
In theory it shouldn't matter how many levels of nesting you have, it should still be very simple and easy to implement if you take approach #1. Approach #2 isn't hard either (just harder than #1). But of course (as you're discovering) the devil is most certainly in the details.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.