0

I've looked around for a while now and have not been able to find anything that suggests what the cause of this is.

My code:

var faqView = Backbone.View.extend({
    tagName: 'div',
    id: 'faq-list',
    initialize: function() {
        var view = this;
        this.collection = new faqCollection();
        this.collection.fetch({
            success: function(collection, response) {
                collection.each(function(faq){
                    view.$el.append(_.template($('script#faq_item').html(),{faq: faq.attributes}));
                });
            },
            error: function(collection, response) {
                view.$el.html("<p>Unable to get the FAQ items.<br>Please try again later.</p>");
            }
        });
    },
    render: function() {
        this.$el.appendTo('div#container');

        return this;
    },
    events: {
        'click h3': 'toggleAnswer'
    },
    toggleAnswer: function(event) {
        console.log(this);
        console.log(event);
    }
});

var router = Backbone.Router.extend({
    routes: {
        "faq": "faq",
        "*other": "defaultRoute"
    },
    faqView: {},
    initialize: function() {
        this.faqView = new faqView();
    },
    defaultRoute: function() {
        this.resetPage();
    },
    faq: function() {
        this.resetPage();
        $('body').addClass('page-faq');
        this.faqView.render();
    },
    resetPage: function() {
        $('body').removeClass('page-faq');
        this.faqView.remove();
    }
});

The above code is included as the last items in the <body>. The HTML is as follows.

<body>
    <div id="container">
    </div>
    <script type="text/template" id="faq_item">
        <h3 class="contracted"><span>{{faq.question}}</span></h3>
        <p style="display: none;">{{faq.answer}}</p>
    </script>
    <script type="text/javascript" src="./js/models.js"></script>
    <script type="text/javascript" src="./js/views.js"></script>
    <script type="text/javascript" src="./js/collection.js"></script>
    <script type="text/javascript" src="./js/router.js"></script>
    <script type="text/javascript">
        //<![CDATA[
            $(function() {
                var app = new router;
                Backbone.history.start();
            });
        //]]>
    </script>
</body>

All the required elements exist (as far as I can tell) and I'm not manually setting the el attribute of the View. I'm lost as to why the events are not binding/firing when the <h3> is clicked.

Edit No errors thrown and the functionality works if I don't use the router and create the view by it self. e.g.

var app = new faqView();
app.render();
mu is too short
  • 426,620
  • 70
  • 833
  • 800
Nalum
  • 4,143
  • 5
  • 38
  • 55
  • 1
    Does the list look correct? Do you get any errors? – Niclas Sahlin May 09 '12 at 12:58
  • There is too much code here, I think you should make the effort to find the smallest code example that reproduces the issue. For example I don't think all this Router code is needed or all this initialize complexity. – fguillen May 09 '12 at 13:09
  • @Niclas Sahlin: The list generated is correct. – Nalum May 09 '12 at 13:14
  • @fguillen: I left in what I thought would be helpful to see and removed what I thought had nothing to do with the code that isn't working. – Nalum May 09 '12 at 13:15
  • @Nalum maybe, for example, you can remove all the Router related code and directly call `var myFaqView = new faqView(); myFaqView.render();`. Step by step you can find out which portion of the code is the trouble spot. – fguillen May 09 '12 at 13:28
  • @fguillen: I've just tried that and the event triggered correctly, which would suggest that the router is perhaps the problem. – Nalum May 09 '12 at 13:35
  • I don't think is the problem but you have a naming collision with `router.faqView`. – fguillen May 09 '12 at 15:49
  • Nope, that doesn't have any effect on it. Thanks for the help. – Nalum May 09 '12 at 16:23
  • @fguillen: There is no naming collision. But there is a `remove` call that is messing up the non-standard caching... – mu is too short May 09 '12 at 21:03

1 Answers1

3

Your problem is in your router. Right here in fact:

resetPage: function() {
    $('body').removeClass('page-faq');
    this.faqView.remove();
}

View#remove is just jQuery's remove on the view's el by default and that:

[...] method takes elements out of the DOM. [...] all bound events and jQuery data associated with the elements are removed

So once you this.faqView.remove(), the delegate handler that drives the view's events is gone.

The usual approach is to create and destroy views as needed instead of creating a view and caching it for later. Your router should look more like this:

var router = Backbone.Router.extend({
    routes: {
        "faq": "faq",
        "*other": "defaultRoute"
    },
    defaultRoute: function() {
        this.resetPage();
    },
    faq: function() {
        this.resetPage();
        $('body').addClass('page-faq');
        this.view = new faqView();
        this.view.render();
    },
    resetPage: function() {
        $('body').removeClass('page-faq');
        if(this.view)
            this.view.remove();
    }
});

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

You could try messing around with detach inside an overridden remove method in faqView as well but there's really no need to have an instance of faqView around all the time: create it when you need it and remove it when you don't.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • So the `events` attribute is only applied on initialize? Good to know. Thanks for the help this has sorted out the issue. Just started looking at backbone.js and am liking it. – Nalum May 10 '12 at 08:30
  • @Nalum: [`delegateEvents`](http://documentcloud.github.com/backbone/#View-delegateEvents) does the binding, it is called automatically but you can call it yourself. The Backbone source is pretty easy to understand so don't be afraid to look at it to clarify things. – mu is too short May 10 '12 at 09:05