23
votes

I have a view that displays items in a grid. If a button to create a new row is clicked, a popup window (use SimpleModal) is displayed to allow the user to save the row to the server. If all goes well, the window closes and the grid is refreshed. All works fine up to this point. If I now open the window a second time, the save record event will get called two times. If I close and open the window a third time, then the event will be called three times, etc. I have no idea why the event is getting rebound multiple times. Here are my two views:

FieldList View:

var fieldListView = Backbone.View.extend({
el: "#centerbodycontent",
initialize: function () {
    _.bindAll(this, 'render', 'addField');
},

render: function () {
    $(this.el).empty();
    $("#FieldGridTemplate").tmpl({ results: this.model }).appendTo(this.el)
    stripe($(this.el).find("tbody"));
    return this;
},
events: {
    "click a#addfield": "addField"
},
addField: function (e) {
    window.appController.popup = new fieldFormView({ model: new fieldModel({ contenttype_id: this.model.id }) });
    window.appController.popup.render();
}

});

Form View (this is what gets called for the popup)

var fieldFormView = Backbone.View.extend({
el: "#popupwindowcontent",

events: {
    "click #savefieldandnew": "savefield",
    "click #savefieldandclose": "savefield",
    "change input,textarea,select": "changeField"
},
render: function () {
    var formWrapper = $(this.el).find(".modal-form")[0];
    $(formWrapper).empty();
    $("#FieldFormTemplate").tmpl({ results: this.model }).appendTo(formWrapper)
    OpenForm(this.el, "Add Field", 450, 600);
    return this;
},
// automatically updates the model during field changes
changeField: function (e) {
    var changeobj = new Object;
    switch (e.target.type) {
        case "radio":
            changeobj[e.target.id] = parseInt($(e.target).val());
            break;
        case "checkbox":
            changeobj[e.target.id] = $(e.target).is(":checked");
            break;
        default:
            changeobj[e.target.id] = $(e.target).val();
            break;
    }

    if (e.target.id.toLowerCase() == "name") {
        var k = $(this.el).find("#key");
        if (jQuery.trim(k.val()).length == 0)
            k.val($(e.target).val().replace(/[^a-zA-Z0-9]+/g, '').toLowerCase());

        var l = $(this.el).find("#label");
        if (jQuery.trim(l.val()).length == 0)
            l.val($(e.target).val());
    }

    var options = { silent: true };
    this.model.set(changeobj, options);
},
savefield: function (e) {
    var kcode = (e.which);
    var thiz = this;
    var m = this.model;
    var nextaction = e.target.id;
    alert(nextaction);
    if (kcode == 0 || kcode == 1 || kcode == 32) {
        e.preventDefault();
        if ($("#contentfieldform").validate({
            rules: {
                name: { required: true },
                label: { required: true },
                key: { required: true }
            }
        }).form()) {
            m.save(null, {
                headers: { "If-Match": m.get("version") },
                error: function (model, response) {
                    var errResp = JSON.parse(response.responseText);
                    popupException("Content Type Modification Error", errResp.errors[0].message);
                },
                success: function (model, response) {
                    window.appController.popup.model = new fieldModel;
                    $.pnotify({
                        pnotify_title: 'Field Saved',
                        pnotify_text: 'The field was saved successfully.'
                    });

                    if (nextaction == "savefieldandclose") {
                        $.modal.close();
                    }
                }
            });
        }
    }
}
3
I wound up solving this by delegating the events myself in the Render method - I basically clear the view's el and then reattach it and bind the events there. The scary thing is I have no idea why what I was doing did not work in the first place so this is still open for anyone to provide an answer and tell me what I was doing wrong.AlexGad
OK, it finally hit me what was going on. For anyone who is running into a similar problem, be careful what the ID for the el in your view is. I was using the same ID for the el in multiple views and this was causing no end of problems especially since I was not removing the el for new popups but rather the children underneath the el in the DOM. The result was the views and events were still hanging off the el and were accumulating every time I opened the popup again with a new view since the view had its own events that were added to the previous ones.AlexGad
you can answer your own question.Ilia Choly

3 Answers

23
votes

Since nobody answered I thought I would add some more info in case anyone runs into this. A good resource to explain what is happening (and offer a generic solution) is http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/. Also, the latest version of backbone has some new methods to allow unregistering of events from views.

5
votes

I have the same headache this morning, but found small workaround which works like a charm for me.

The main idea is to avoid creating new View object every time we need to show something, instead I try to reuse old view object. In my case it looks like this :

   Overview: backBone.View.extend({
        el: $('#overviewTab'),

        dialog : null,

dialog is my field where im going to hold my view object

now on the callback that creates new view i do this:

showNewActivityDialog: function () {
            this.dialog = this.dialog || new window.RTB.Views.NewActivity();
            this.dialog.render();
            return false;
        },

in this case I don't create new views - i reuse a previously created, so I don't bind new events!
Hope it will works for you

0
votes

A quick fix (which i don't encourage) is to use _.throttle function. Use it to wrap your event handler so that it is only called once every, say 100 ms. It might work for UI, not for app logic.

addField: (function() {
    return _.throttle( function (e) {
        window.appController.popup = new fieldFormView({ model: new fieldModel({ contenttype_id: this.model.id }) });
        window.appController.popup.render();
    }, 100 );
})(),