0
votes

I've been facing an issue for the entire day now, and I'm kind of desperate! I'm pretty sure this will be simple for some Backbone experts around here...I'm new with Backbone and I might have not taken the best approach. Any help or advice will be awesome!

The Issue

After adding a new item to a collection, the collection is re-rendered, and I can see the new item added to my DOM. If I click on the 'remove' button the event gets fired, and the new item get destroyed...BUT the element stays in the DOM. If I re render the page, (refresh or navigate somewhere else and come back) it's gone.

Whereas if I add a new item, navigate somewhere else right after my collection get rendered, then come back, and press 'remove', this time it gets destroyed AND removed from the DOM... Any idea?

Adding a new item

save: function(e){
    e.preventDefault();
    var data = form2js('deviceForm', '.', true);
    //Allow to create or edit a model;
    this.model.set(data);
    this.model = this.collection.create(this.model, {
        wait: true,
        success: function(){
            Utils.alert('success', 'Device has been added/edited');
            app.vent.trigger("devices:show");
        }
    });
}

this code is inside the view shown when I add a new item. When the new item gets added, it calls the function below. this.collection is my list of devices, reused inside the router.

Router

showDevices: function(){
    if (!this.devices){
        this.devices = new Devices();
        this.devices.fetch();
    }
    if (this.devicesList) this.devicesList.remove();
    this.devicesList = new DevicesView({collection: this.devices});
    $('.addPadding').hide().slideDown(1000, 'linear').html(this.devicesList.render().el);
},

I fetch the list just once and then reuse it every time I need it, avoiding unnecessary fetch. I clean the view if existing and instantiate a new one.

Collection (View)

initialize: function(){
    _.bindAll(this, "render", "addOne");

    this.collection.on('add', this.addOne);
    this.collection.on('reset', this.addAll);
},
render: function(){
    this.$el.html(this.template);
    this.addAll();
    return this;
},
addOne:function(device){
    var devi = new DeviceView({model: device});
    this.$el.find(".devices-list").append(devi.render().el);
},
addAll: function(){
    this.collection.each(this.addOne, this)
}

no el attribute defined in my collection view. binding to collection event so that it gets rendered for a new item or fetch done.

Item view

tagName: 'tr',
events: {
    'click .remDevice': 'removeDevice',
    'click .editDevice' : 'editDevice'
},
template: _.template( template ),
initialize: function(){
    this.listenTo(this.model, 'destroy', this.remove);
},
render: function(){
    this.$el.html(this.template(this.model.toJSON()));
    return this;
},
removeDevice:function(){
    this.model.destroy();
}

My items are appended into a table, to the tbody element. This table is inside the template of my collection view. When I click on the remove button, the model is destroyed and the event is fired and call this.remove. But doesn't works when the collection is re rendered just after the creation of the item. Works after though...

Hope I gave enough information! Thanks a lot in advance! PS: No error in the console and model + collections are populated in my debugger.

EDIT: this.remove is called at each click since my model gets destroyed every time. By overriding it and trying to remove manually the element with:

this.$el.remove();

makes no difference and doesn't show any error. this.$el contains my newly added element.

EDIT 2 A new clue: If I've a collection of 6 devices, add a 7th, then try to delete ANY of them, they got destroyed but not removed of the DOM unless I do the re render. So it's not linked to the NEW item only, but to the fact I added a new item to the collection, rendered it and try to delete one Item. Also, the EDIT function still works on each item and redirect me to the right page with the right model to edit.

LAST EDIT I tried to empty the collection before to add new Items to avoid zombies...but didn't change anything. I hoped it would have avoided random side effects in my Collection View

addAll: function(){
                this.$el.find(".devices-list").empty();
                this.collection.each(this.addOne, this)
            },
1
I'm not exactly sure why yet, but I don't like the line in your collection view _.bindAll(this, "render", "addOne"); what is that line doing? does it work if you remove that? - Mike Vormwald
From the code you supplied under Collection it seems that it's actually a view. - Cory Danielson
@MikeV, that line is equivalent to do _this = this, then use _this in my other functions. It just keeps my context to my view. thanks for the edit! sorry about that - Bruno
If you add a third this parameter to your collection.on() lines, you won't need the _.bindAll stuff. I try to stay away from using _.bindAll - Cory Danielson
A nice way to debug your ItemView/DeviceView would be to add this.model.on('all', function(eventName){console.log(eventName)}); then check the console to see what events are being fired. You really just need to figure out why that destroy event is not trigging the remove function. Once you solve that, I think you'll solve your problem. I've deleted my answer, because it wasn't a solution. - Cory Danielson

1 Answers

0
votes

I found a "dirty" way to fix my issue. I had a breakpoints inside the 'remove function'. Visually comparing the this.$el and the line added to the table was the EXACT same line.

so I tried to do this:

$('.devices-list').find(this.$el)

But nothing was returned...which is crazy because they contain the exact same data! I assumed this.$el is rendered in a zombie version or something. I'm pretty sure there is something wrong with the way the items are rendered to get to that point...but I definitely can't find out and I'm open to suggestions...

Solution

In my Device View/Item View, I edited these two functions, add to the tr the model id, then use jquery to find it in the DOM. like this I focus on existing elements and not $el which is cached jquery stuff...

render: function(){
        this.$el.html(this.template(this.model.toJSON()));
        this.$el.attr('id', this.model.id);
        return this;
    },
remove: function(){
            var elem =  $('.devices-list').find('#'+this.model.id);
            $(elem).find('#'+this.model.id).slideUp(1000);
            $(elem).find('#'+this.model.id).remove();
        }

Hopefully someone will get a better idea or someone will be helped by my solution!