Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backbone.Marionette: Defer view close until beforeClose animation is complete

I'm trying to set animations on rendering and closing an ItemView with Backbone.Marionette. For rendering a view, this is fairly simple:

MyItemView = Backbone.Marionette.View.extend({
   ...
   onRender: function() {
     this.$el.hide().fadeIn();
   }
   ...
});

This will have my view fade in when I render it. But let's say I want to fade out my view upon close.

beforeClose: function() {
   this.$el.fadeOut();       // doesn't do anything....
}

This won't work, because the item closes immediately after calling this.beforeClose(), so the animation doesn't have time to complete.

Is there any way, using Marionette as it stands, to accomplish a closing animation?


Alternatively, this is the workaround I've been using:

_.extend(Backbone.Marionette.ItemView.prototype, {
    close: function(callback) {

        if (this.beforeClose) {

            // if beforeClose returns false, wait for beforeClose to resolve before closing
            // Before close calls `run` parameter to continue with closing element
            var dfd = $.Deferred(), run = dfd.resolve, self = this;
            if(this.beforeClose(run) === false) {
                dfd.done(function() {
                    self._closeView();              // call _closeView, making sure our context is still `this`
                });
                return true;
            }
        }

        // Run close immediately if beforeClose does not return false
        this._closeView();
    },

// The standard ItemView.close method.
    _closeView: function() {
        this.remove();

        if (this.onClose) { this.onClose(); }
        this.trigger('close');
        this.unbindAll();
        this.unbind();      
    }
});

Now I can do this:

beforeClose: function(run) {
    this.$el.fadeOut(run);      // continue closing view after fadeOut is complete
    return false;
},

I'm new to using Marionette, so I'm not sure if this is the best solution. If this is the best way, I'll submit a pull request, though I'll want to put a bit more thought into how this could work with other types of views.

This could potentially be used for other purposes, such as asking for confirmation on close (see this issue), or running any kind of asynchronous request.

Thoughts?

like image 526
eschwartz Avatar asked Sep 16 '12 17:09

eschwartz


1 Answers

Overriding the close method is the one way to do this, but you can write it bit shorter, as you can call the Marionettes close method instead of duplicating it:

_.extend(Backbone.Marionette.ItemView.prototype, {
    close: function(callback) {
        var close = Backbone.Marionette.Region.prototype.close;
        if (this.beforeClose) {

            // if beforeClose returns false, wait for beforeClose to resolve before closing
            // Before close calls `run` parameter to continue with closing element
            var dfd = $.Deferred(), run = dfd.resolve, self = this;
            if(this.beforeClose(run) === false) {
                dfd.done(function() {
                    close.call(self);
                });
                return true;
            }
        }

        // Run close immediately if beforeClose does not return false
        close.call(this);
    },


});

Another idea is to overide the remove method of your view. So you fade out the element of the view and then remove it from the DOM

remove: function(){
  this.$el.fadeOut(function(){
    $(this).remove();
  }); 
}
like image 70
Andreas Köberle Avatar answered Nov 15 '22 15:11

Andreas Köberle