Trying to build an off-canvas panel using Ember.JS components was proving difficult. It did not help that most the results still referenced the now deprecated Ember.View.

Background

I had built the off-canvas panel to animate using CSS3 transitions and animations instead of a more traditional JavaScript approach. The implementation required jQuery to add the .is-visible class to a <div class="cd-panel">...</div>.

Final Answer

The answer I found was to use RSVP.js implementation of Promises that comes as part of Ember.JS.

// app/components/story-panel/component.js
import Ember from 'ember';

export default Ember.Component.extend({
  actions: {
    close: function() {
      var promise = new Promise(function(resolve, reject) {

        this.$('.cd-panel').removeClass('is-visible');

        setTimeout(function() {
          resolve(true);
        }, 600);
      }.bind(this));
      promise.then(function() {
        return this.sendAction('close');
      }.bind(this));
    }
  },
  becomeVisible: function() {
    setTimeout(function() {
      this.$('.cd-panel').addClass('is-visible');
    }.bind(this), 100);
  }.on('didInsertElement')
});

Other considered options

In my hours long search for an answer to this problem, I discovered another approach that seemed to work (though for unknown reasons). The solution that relied upon the usage of Ember.run.next.

This did seem to work for the becomeVisible method, but I could not translate that into a working close action.

As far as to why Ember.run.next worked, I am still unsure. Looking at the Ember documentation for this method it calls out that there are normally better ways to resolve issues requiring the #next call.

Code sample

<div class="cd-panel from-right">
  <header class="cd-panel-header">
    <h4>View Story Details</h4>
    <a {{action 'close'}} class="cd-panel-close">Close</a>
  </header>

  <div class="cd-panel-container">
    <div class="cd-panel-content">
      {{model.title}}
      {{model.description}}
    </div>
  </div>
</div>
// app/iterations/route.js
import Ember from 'ember';

export default Ember.Route.extend({
  displayStory: false,
  model: function(params) {
    return this.store.query('iteration', params);
  },
  actions: {
    openStoryPanel: function(story) {
      return this.render('story-panel', {
        outlet: 'story-panel',
        into: 'application',
        model: story,
        controller: 'application'
      });
    },
    closeStoryPanel: function() {
      return this.disconnectOutlet({
        outlet: 'story-panel',
        parentView: 'application'
      });
    }
  }
});
<!-- /app/templates/application.hbs -->
{{outlet "story-panel"}}

<!-- /app/templates/story-panel.hbs -->
{{story-panel model=model close='closeStoryPanel'}}