7

I'm pulling my hair out, I cannot seem to get mouse events to work on my backbone view after the view is re-rendered unless i do the most ridiculous thing:

$("a").die().unbind().live("mousedown",this.switchtabs);

I actually had this in there but decided to update to the latest backbone and try to use the new delegateEvents()function.

Here is the way my project id structured:

Appview / AppRouter
  |
  ----->PageCollection
             |
             ------->PageView/PageModel
             ------->PageView/PageModel      these page view/models are not rendered
             ------->PageView/PageModel
             |
             ------->PageView/PageModel
                        |
                        ----->render()      *when a pageview is rendered*
                                |
                                -----> Creates new 
                                     Tabcollection
                                       |
                                       --->TabModel/TabView   <-- this is where the issue is 

What happens is that the tabcollection has a main tabview to manage all of the tabs, then creates a new model/view for each tab and puts a listener to re-render the tabview whenever a tab is loaded. If the tabview is re-rendered, no mouse events work anymore unless I put that contrived jQuery statement in there.

Heres the tabview and render (ive stripped it down quite a bit)

var TabPanelView = Backbone.View.extend({
    className:      "tabpanel",
    html:           'no content',
    model:          null,
    rendered:       false,
    events:{
        'click a.tab-nav': 'switchtabs'
    },
    initialize: function(args)
    {
        this.nav            = $("<ol/>");
        this.views          = args.items;
        this.className      = args.classname?args.classname:"tabpanel";
        this.id             = args.id;
        this.container      = $("<section>").attr("class",this.className).attr("id",this.id);
        _.bindAll(this);
        return this.el
    },
    /*
    This render happens multiple times, the first time it just puts an empty html structure in place
    waiting for each of the sub models/views to load in (one per tab)
    */
    render: function(args){
        if(!args)
        {
            //first render
            var nav             = $("<aside/>").addClass("tab-navigation").append("<ol/>").attr("role","navigation");
            var tabcontent      = $("<section/>").addClass("tab-panels");
            for(i = 0;i<this.views.length;i++)
            {
                $("ol",nav).append("<li><a rel='"+this.views[i].id+"' href='javascript:;' class='tab-nav'></a></li>");
                tabcontent.append(this.views[i].el);
            }
            this.$el.empty().append(nav).append(tabcontent);
        }
        else if(args && args.update == true){
            // partial render -- i.e. update happened inside of child objects
            var targetid = args.what.cid;
            for(i = 0;i<this.views.length;i++)
            {
                var curcontent  = this.$el.find("div#"+this.views[i].id);
                var curlink     = this.$el.find("a[rel='"+this.views[i].id+"']")
                if(this.views[i].cid == targetid)
                {
                    curcontent.html($(this.views[i].el).html());
                    curlink.text(this.views[i].model.rawdata.header);
                }
                if(i>0)
                {
                    // set the first panel 
                    curcontent.addClass("tab-content-hide");
                }
                if(i==0)
                {
                    curcontent.addClass("tab-content-show");
                    curlink.addClass("tab-nav-selected");
                }
                // this ridiculous piece of jQuery is the *ONLY* this i've found that works
                //$("a[rel='"+this.views[i].id+"']").die().unbind().live("mousedown",this.switchtabs);
            }
        }
        this.delegateEvents();
        return this;
    },
    switchtabs: function(args){

        var tabTarget = args.target?args.target:false
        if(tabTarget)
        {
            this.$el.find("aside.tab-navigation a").each(function(a,b)
            {
                $(this).removeClass("tab-nav-selected")
            })
            $(tabTarget).addClass("tab-nav-selected");
            this.$el.find("div.tab-content-show").removeClass("tab-content-show").addClass("tab-content-hide");
            this.$el.find("div#"+tabTarget.rel).removeClass("tab-content-hide").addClass("tab-content-show");
        }
    }
});

Can anyone think of why backbone mouse events simply don't fire at all, is it because they are not on the DOM? I thought that this was where backbone was particularly useful?...

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Alex
  • 3,732
  • 5
  • 37
  • 59
  • What steps have you taken to troubleshoot this so far? A couple of things I would try: See if you can set a breakpoint in the Backbone source where the `events` element is processed. Might give you some insight. Also, try using Chrome's DOM and Event Listener breakpoints. This might help you check if any code is being executed at all, or if the events are just failing to wire up. – Josh Earl Mar 14 '12 at 11:23
  • Ive logged against jq $(document).find("tab-nav-selected") to see if the rendered items are actually on the dom.. fine get the right results. have gone through breakpoints, in my own code. I'm not confident enough with it to go digging in backbone itself with the debugger. the scope all seems fine... I'm not certain by any means but it seems as though the events are either being overwritten due to re-rendering or... ummm – Alex Mar 14 '12 at 13:59

3 Answers3

6

This line of code is likely your problem:

this.delegateEvents();

Remove that and it should work.

The only time you need to call delegateEvents yourself, is when you have events that are declared separately from your view's events hash. Backbone's view will call this method for you when you create an instance of the view.

Derick Bailey
  • 72,004
  • 22
  • 206
  • 219
  • sadly no dice. honored to have you answer though, i've read a bunch of useful backbone stuff on your blog. I've given in and gone with jQuery.live... yes, it's an abomination.. but it's the only thing working and I've got 2 days til deadline.. humm – Alex Mar 14 '12 at 15:29
2

When the view is being re-rendered, are you reusing the same view and just calling render() on it again, or are you deleting the view and creating a whole new view?

Either way, it looks like the cause is that the view events are not being unbound before the view is re-rendered. Derick Bailey has a great post about this.

When you re-render, 1) make sure you unbind all the events in the old view and 2) create a new view and render it

Paul
  • 18,349
  • 7
  • 49
  • 56
  • nice article however I can't go making new parent views when every child view updates (the parent view must respond to the child view). Strangely enough I tried the method in the article however strangely it work about 1 in every 3 times i reloaded the page the other times ... nothing – Alex Mar 14 '12 at 13:55
1

When using $(el).empty() it removes all the child elements in the selected element AND removes ALL the events (and data) that are bound to any (child) elements inside of the selected element (el).

To keep the events bound to the child elements, but still remove the child elements, use:

$(el).children().detach(); instead of $(.el).empty();

This will allow your view to rerender successfully with the events still bound and working.

AmpT
  • 2,146
  • 1
  • 24
  • 25
  • 1
    @Alex In your code try: ``this.$el.children().detach().append(nav).append(tabcontent);`` and it should **work** perfectly. No need to recreate an instance of the view. – AmpT Mar 12 '14 at 12:42
  • Please see these answers as further reference: http://stackoverflow.com/a/12029250/2728686 and http://stackoverflow.com/a/7417078/2728686 – AmpT Mar 12 '14 at 12:44