HEY! I'm working on an awesome course on Golang and Ember.js skills that's live soon. Get early access to it here:

nerdyworm

Ember Modal Example

Modal views seem to be a source of pain in the ember world. Not all applications treat a modal state the same. Some applications consider a modal a new route, others treat it as a non routed state.

For most crud modals I prefer to have them not route based. This is due to the fact that modal edit/create dialogs tend to be a shorter version of the form than their routed counter parts.

Generally this is my short list of requirements for modals.

  1. Server must acknowledge success or failure before the modal can close
  2. Animate open and close

These are the genral requirements that I deal with all the time. Things like jquery ui modals and bootstrap modals are wonderful for most things, but as soon as your need to control the life cycle or change the close triggers they become overly complex.

After all a modal is just a div with a backdrop, it is not rocket surgery.

Let’s dive into how to make this happen with ember.

application_route.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
App.ApplicationRoute = Em.Route.extend({
  events: {
    openModal: function(modal) {
      this.render(modal, {
        into: 'application',
        outlet: 'modal'
      });
    },

    closeModal: function() {
      App.animateModalClose().then(function() {
        this.render('empty', {
          into: 'application',
          outlet: 'modal'
        });
      }.bind(this));
    }
  }
});

We can start by defining how we want to open and close our modal. The openModal function takes the name of a modal view i.e. widgets.modal and renders it into the application’s outlet named modal.

The closeModal function uses a application wide function App.animateModalClose() which returns a promise that is resolved when the modal is finished animating. Then we simply remove the modal from the outlet by rendering an empty template into the outlet.

How do we actually use this?

widgets_route.js
1
2
3
4
5
6
7
8
App.WidgetsRoute = Em.Route.extend({
  events: {
    edit: function(widget) {
      this.controllerFor('widgets.modal').edit(widget);
      this.send('openModal', 'widgets.modal');
    }
  }
});
widgets_modal_controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
App.WidgetsModalController = Em.ObjectController.extend({
  edit: function(record) {
    record.on('didUpdate', this, function() {
      this.send('closeModal');
    });

    this.set('model', record);
  },

  save: function() {
    this.get('model.transaction').commit();
  }
});

The edit event will be triggered from a handlebars template with a widget argument.

The WidgetsModalController#edit function takes a record and listens for the didUpdate model life cycle event. This event is triggered once the server acknowledges the model was updated. Then we send the closeModal event which will be handled by the ApplicationRoute.

When we send the openModal event the ApplictionRoute will handle the event. Ember will then render the widgets.modal template with the WigetsModelController.

Overall I do like the design of the modal controller. It only knows about the model’s life cycle and that there is something in the application that will handle the closeModal event. This seems like just enough responsibility for a controller to have. A parallel would be a rails controller that updates a modal and sets the flash message.

Animating Open and Close

modal_view.js
1
2
3
4
5
6
7
8
9
App.ModalView = Em.View.extend({
  // the yeild is missing a pair of { }.
  // octpress likes to interpret them as liquid tags :(
  layout: Em.Handlebars.compile("{yield}<div class=modal-backdrop></div>"),

  didInsertElement: function() {
    App.animateModalOpen();
  }
});

This the basic modal view. All it does is set up a layout with the modal backdrop and call our animation method once the element is inserted into the dom.

modal_animation.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
App.animateModalClose = function() {
  var promise = new Ember.RSVP.Promise();

  $('.modal.in').removeClass('in');
  $('.modal-backdrop.in').removeClass('in');

  setTimeout(function() {
    promise.resolve();
  }, App.DEFAULT_CSS_TRANSITION_DURATION_MS);

  return promise;
};

App.animateModalOpen = function() {
  var promise = new Ember.RSVP.Promise();

  $('.modal').addClass('in');
  $('.modal-backdrop').addClass('in');

  setTimeout(function() {
    promise.resolve();
  }, App.DEFAULT_CSS_TRANSITION_DURATION_MS);

  return promise;
};

The animation methods should be asynchronous. To make sure that we know when the modal is done animating we simply resolve a promise after some predefined duration. We could also do the animation via jQuery and not have to change the external interface.

fin

This is one way of handling modal views.

Ember gives you a lot of options. Your going to have to pick the ones that work best for your application.

How are you handling modals in your applications? Can you share a complete example? <3

Comments