Backbone.js `model.destroy()` custom transitions?
Asked Answered
S

4

6

When I use Backbone's model.destroy(), it seems to automatically remove that view from the DOM.

Is there a way for me to use destroy() to send the DELETE request, but remove the view from the DOM myself?

Something like:

this.model.destroy({
    wait: true,
    success: function(){
        $('#myElement').animate({
            "height" : "0",
            1000,
            function(){$('#myElement').remove()}
        });
    }
});
Spickandspan answered 24/4, 2015 at 23:59 Comment(4)
What kind of a view are you using? Backbone? Marionette? CollectionView? ItemView? LayoutView?Settee
And it's not working?Phila
@Morslamina - This is a Marionette Item ViewSpickandspan
@Simo Endre - Correct. The view gets removed from the DOM before the jQuery event is fired.Spickandspan
S
2

You need to override _onCollectionRemove() in whichever Collection view contains the item views (documentation). This is the function which is called when your model is removed from the collection, and it's also what's destroying your view. Specifically how you choose to override it is up to you, but it might be easiest to override it with your animation function, maybe along the following lines...

_onCollectionRemove: function(model) {
  var view = this.children.findByModel(model);
  var that = this;
  view.$('#myElement').animate({
        "height" : "0",
        1000,
        function(){
            that.removeChildView(view);
            that.checkEmpty();
        }
    });
}

If you prefer to handle the removal of the view manually in your destroy callback, just override _onCollectionRemove() to contain an empty function and do whatever you'd like in the callback of your delete request. I'd recommend the approach I describe above rather than doing it in your destroy callback, though. Completely eliminating the function and then handling it's responsibilities elsewhere in your code interferes with Marionette's intended event flow. Simply overriding the function with a different UI effect preserves the natural flow.

EDIT: Another user's previous answer (now deleted due to downvoting) suggested that it might be wise to call destroy after the UI effect was completed. This is not a good approach for the reason OP pointed out - if something goes wrong with the destroy method, (for example, if the remote server goes down) it appears to the user as if the model was deleted (the UI effect had already completed) even though the server was unreachable and the model remains.

Settee answered 26/4, 2015 at 23:26 Comment(2)
This is exactly what I needed. I should have looked into Marionette being the culprit. Thank you!Spickandspan
Backbone removes a model from the UI and triggers a 'destroy' event immediately unless wait === true is passed when calling model.destroy. (This trigger will, in turn, call remove on the collection for that model.) In other words, the default Backbone operation is not to wait for the server. In general, as long as the response from the server is monitored and handled, there's nothing intrinsically wrong with removing the view from the UI before the server returns.Crandell
Y
2

the mentioned onBeforeDestroy method does not work for me. It throws an error in backbone (remove method missing) My solution has the same aproach and is working very well in itemView

remove: function(){

    this.$el.animate({"height" : "0"},500, function(){
        $(this).remove();
    });

},
Yes answered 23/6, 2015 at 21:22 Comment(0)
D
1

You need to use one of:

  • collection.remove model
  • collection.reset collection.model

Every of this methods will re-render your collection or composite view.

It is not Good practice to remove element from collection/composite view directly by using js or jQuery;

Defeatism answered 6/5, 2015 at 11:15 Comment(1)
I agree, however, I don't want to re-render the entire collection view. I'm using what @Morslamina suggested; Marionette's view.removeChildView method for actually removing the element from the DOM.Spickandspan
C
1

Instead of focusing in the model event, we can focus on the view life cycle. For that purpose, Marionette makes the onBeforeDestroy callback available on Marionette.View (which is extended by all Marionette views). In your ItemView you'd define the callback like this

onBeforeDestroy: function () {
  $('#myElement').animate({ "height" : "0", 1000 });
}

They're is an important caveat here. Since $.animate is an asynchronous function, it is possible that the view may be removed before $.animate finishes the transition. So, we have to make a modification to our onBeforeDestroy.

onBeforeDestroy: function () {
  var animationDelay = 1000ms;
  this.remove = _.delay(_.bind(this.remove, this), animationDelay );
  this.$el.animate({ "height" : "0", animationDelay });
}

Essentially, what we did here is set the View.remove() method to fire after the animation has run through, ensuring that when this.remove is called, it's called asynchronously, after the animation has run through. You could also do this with Promises, I suppose, but that requires a bit more overhead.

Crandell answered 11/5, 2015 at 3:31 Comment(1)
This is also very valid. Thanks for the input!Spickandspan

© 2022 - 2024 — McMap. All rights reserved.