backbone.js events not firing after re-render

2019-01-31 20:56发布

[EDIT: I solved earlier problem by calling delegateEvents(), but shouldn't have to. Re-posting w/more info.]

I have a View that when rendered has a login button on it with a click event attached. First render all works: click on button and upon successful ajax call the login prompts disappear and welcome view (LoggedInView) is displayed. But, if I navigate back to this View later (#foo) the UI renders but the event association is gone without manually forcing the issue by calling delegateEents().

What happened that my events didn't re-associate themselves?

LoginView = Backbone.View.extend({
    template: _.template($("#loginTemplate").html()),
    initialize: function(stuff,router) {
        _.bindAll(this,'render','rc','gotProfile');
        this.model.bind("rc",this.rc)
        this.router = router;
    },
    events: {
        'click .loginButton': 'login'
    },  
    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        $(this.el).find(".actionButton").button();  // Button
//      this.delegateEvents(this.events);  // NEEDED!  Re-wire events on re-render
        return this;
    },
    rc: function(code) {
        switch(code) {
            case errCodes.USER_NOT_FOUND:   this.notFound(); break;
            case errCodes.OK:               this.loginOk(); break;
            default:                            this.otherErr(); break;
        }
    },
    login: function() {
        clearErrors( $('[rel="req"]') );
        var okReq = validate( $('#login [rel="req"]'), validateReq );
        var okEmail = validate( [$('#uid')], validateEmail );
        if( okReq && okEmail ) {
            this.model.set({'uid':$('#uid').val().trim(),     'pwd':$('#pwd').val().trim()});
            this.model.fetch();
        }
    },
    notFound: function() {
        validate( [$('#uid'),$('#pwd')], function(){return[false,"Invalid user / password"]} );
    },
    otherErr: function() {
        validate( [$('#uid'),$('#pwd')], function(){return[false,"Please contact support for help logging in."]} );
    },
    loginOk: function() {
        this.profile = new Profile({uid:this.model.get('uid'),token:this.model.get('key')});
        this.profile.bind("rc",this.gotProfile)
        this.profile.fetch();
    },
    gotProfile: function() {
        this.router.navigate('/',true);
    }
});

LoggedInView = Backbone.View.extend({
    template: _.template($("#loggedInTemplate").html()),
    uList: new ProfileList(),
    initialize: function() {
        _.bindAll(this,'render','renderUserList','renderUser');
        this.model.bind('show', this.render);
        this.uList.bind('rc', this.render);
    },
    events: {
        'click #ufa': 'getUsersForHouse'
    },
    render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        this.renderUserList();
//      this.delegateEvents(this.events); // NEEDED!  Re-wire events on re-render
        return this;
    },
    renderUserList: function() {
        $(this.el).find('ul').empty();
        this.uList.each(this.renderUser);
    },
    renderUser: function(aUser) {
        $(this.el).find('#userList').append("<li>"+aUser.get('person').firstName+"</li>");
    },
    getUsersForHouse: function() {
        this.uList.fetch(this.model.get('token'),"house");
    }
});

Main = Backbone.Router.extend({
    routes: {
        'foo': 'foo',
        '*all': 'home'
    },
    initialize: function() {
        this.token = new Token();
        this.loginView = new LoginView({model:this.token},this);
    },
    foo: function(){  // naving here looses click event on login button
        $('#login').empty();
        $("#login").append(this.loginView.render().el);
    },
    home: function() {
        $('#login').empty();
        if( this.loginView.profile == null ) 
            $("#login").append(this.loginView.render().el);
        else {
            this.loggedInView = new LoggedInView({model:this.loginView.profile});
            $("#login").append(this.loggedInView.render().el);
        }
    }
});

4条回答
叼着烟拽天下
2楼-- · 2019-01-31 21:04

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.

查看更多
We Are One
3楼-- · 2019-01-31 21:14

You empty the the #login div. As the jQuery doc says:

To avoid memory leaks, jQuery removes other constructs such as data and event handlers from the child elements before removing the elements themselves.

So you are effectively removing events from your views. I prefer to use detach because it keeps events. http://api.jquery.com/detach/

You can implement a show/hide in your views that will deal with this.

查看更多
迷人小祖宗
4楼-- · 2019-01-31 21:18

I found a solution to this, because I was having the same problem, actually detach() is not the solution because it will bring back the old DOM elements for you which is not useful.

this is what you need to do

render: function(){ this.$el.empty();  ......  this.$el.appendTo($DOM);   }

if you use the .html() method your events will not trigger. you need to attach the newly created HTML to the DOM, by using appendTo().

by the way this only happened to me if you are using tagName and className to build your view dynamically.

查看更多
The star\"
5楼-- · 2019-01-31 21:29

I'm facing the same problem. The way I solved it for now is completely re- instantiating the View object with new MyView() this ensures that the events are rebound.

Hope that helps?

查看更多
登录 后发表回答