Skip to content
Duncan Walker edited this page Jun 30, 2015 · 14 revisions

Previous: Installation | Next: Customization


Core functionality is added to your application using the modal controller. The modal controller is injected into all your application's controllers giving you easy access to new public methods throughout your application.

Rendering a Modal

First, make sure you've added the modal outlet to your template(s):

{{!--app/templates/application.hbs--}}

{{outlet}}
{{outlet 'modal' name='modal'}}

The simplest way to render a modal is by calling showModal() from any controller. showModal accepts a single argument, which may be a string or an object depending on your use case.

To render a modal in the current context (the route's controller) pass a template name as a string. By default, the template will be rendered inside the modal view. For example, if we were to call showModal from inside an action:

// app/controllers/some-controller.js

import Em from 'ember';

export default Em.ObjectController.extend({

  actions: {
    confirm: function() {
      this.showModal('confirm-choice');
    }
  }

});

In the above example, app/templates/confirm-choice will be rendered inside the modal view. This is essentially an extension of your current route's template - just seperated visually - and you can bind properties in the confirm-choice template as if it was part of the route's template.


Rendering an Advanced Modal

If you want control over the context in which a modal is rendered, you can pass any combination of template, controller, model, and view as an object to the showModal() method. For example:

// app/controllers/some-controller.js

import Em from 'ember';

export default Em.ObjectController.extend({
  currentChoice: Em.Object.create({
    name: 'Mike',
    id: 4
  }), // Some object

  actions: {
    confirm: function() {
      this.showModal({
        template: 'confirm-choice', // String
        controller: 'form', // String
        model: this.get('currentChoice') // Object
      });
    }
  }

});

Inside your chosen template, the model you pass is accessible as follows:

{{!--app/templates/confirm-choice.hbs--}}

{{#with modal.model}}
  {{name}} // Mike
  {{id}} // 4
{{/with}}

Please note, model is not set as the model on the specified controller - it's set as the model on the modal controller. This gives you more flexibility over how you handle data without overriding the current route's controller's existing content.

showModal accepts an options as a second argument called renderingOptions. renderingOptions can have any combination of the following properties:

// app/controllers/some-controller.js

import Em from 'ember';

export default Em.ObjectController.extend({

  actions: {
    confirm: function() {
      this.showModal({
        template: 'confirm-choice'
      }, {
        outlet: 'loading-screen', // Defaults to 'modal'
        parentView: 'index' // Defaults to 'application'
      });
    }
  }

});

Whether you specify outlet, parentView, or both, the outlet must be found in the specified or default view's template. For example, the above rendering options correspond to the following template:

{{!--app/templates/index.hbs--}}

{{outlet 'loading-screen'}}

Warning: If you use the same named outlet in different views you will see unexpected behaviour.

As you can imagine, this allows you to use ember-modals for much more than just dialog-like UI. This addon can manage outlets throughout your application.

Warning: If the templateName of the view specified using parentView has a different name from the view (e.g. your index view uses a template called home) you will not be able to use the built in rendering methods.


Rendering a Modal Programtically

Should you wish to have more control over the rendering of a modal, you can set the following properties from any controller.

Warning: Manipulating the modal using get and set method is not recommended and can lead to unexpected behaviour when using multiple outlets. Where possible use the showModal method.

modal.controller // String
modal.model // Object
modal.template // String
modal.view // String

modal.defaultOutlet // String
modal.defaultParentView // String
modal.defaultView // String

modal.transitionDuration // Number

A single property can be set as follows:

// app/controllers/some-controller.js

import Em from 'ember';

export default Em.ObjectController.extend({

  doSomeSetup: function() {
    this.set('modal.controllerName', 'form');
  }
});

Multiple properties can be set as follows:

// app/controllers/some-controller.js

import Em from 'ember';

export default Em.ObjectController.extend({

  doSomeSetup: function() {
    this.get('modal').setProperties({
      controller, 'form',
      defaultOutlet: 'backup-modal',
      model: this.get('content')
    });
  }
});

Once you have setup your modal as desired you must call this.get('modal').show() to render it with the correct properties. For example:

// app/controllers/some-controller.js

import Em from 'ember';

export default Em.ObjectController.extend({

  doSomeSetup: function() {
    var modal = this.get('modal');

    // Set the properties...
    modal.setProperties({
      template: 'confirm-choice'
      controller, 'form',
      defaultOutlet: 'backup-modal',
      model: this.get('content')
    });

    // ... Then render the modal
    modal.show();
  }
});

In the above example, app/templates/confirm-choice will be rendered inside the modal view (the default) with the form controller. The template will have access to model using {{modal.model.someProperty}} and the whole thing will be inserted into the backup-modal outlet in the application template.

show() accepts an optional argument - the renderingOptions object covered in rendering an advanced modal. For example:

// app/controllers/some-controller.js

import Em from 'ember';

export default Em.ObjectController.extend({

  doSomeSetup: function() {
    var modal = this.get('modal');

    // Set the properties...
    modal.setProperties({
      template: 'confirm-choice'
      controller, 'form'
    });

    // ... Then render the modal
    modal.show({
      outlet: 'form-modal',
      parentView: 'form'
    });
  }
});

See customization for a better look at customizing the modal controller's properties (and mistakes to avoid).


Using Multiple Outlets

You can render modals and other views into different outlets in different parent views, as previously discussed. You can even show two modals at the same time. An example might be rendering a modal over a loading screen, both of which are rendered using ember-modals.

In order to effectively manage multiple outlets you must do three things:

  1. Use unique outlet names across all views
  2. Pass the outlet name when you call hide() if it's different from the modal controller's defaultOutletName
  3. Add the name option to all outlets (see below)

To ensure everything works as intended with multiple outlets, add name to each outlet with the exact name of the outlet:

{{!--app/templates/application.hbs--}}

{{outlet 'modal' name='modal'}}
{{outlet 'form-modal' name='form-modal'}}

Warning: ember-modals currently only works with outlets where the view and template have the same name.

Removing a Modal

There are two ways to remove a modal, depending on your situation:

  1. From a controller
  2. From a view

From a Controller

To close the modal from any controller call this.get('modal').hide().

This is the most common way of closing a modal. For example, you will handle the user's response from some modal UI, save the response in the controller, and then close the modal only after a successful save. For example:

// app/controllers/some-form.js

import Em from 'ember';

export default Em.ObjectController.extend({

  save: function() {
    var _this = this;

    // Do required task before closing modal
    _this.get('content').save().then(function() {
      // Then close modal
      _this.get('modal').hide();
    }, function(xhr) {
      // Else, keep modal open and show user the error
    });
  }

});

Another use case might be using a modal to offer the user a choice between two different routes for navigation. In this situation, you might want to close the modal when the new route has begun loading and not before that point. So, in the route being transitioned to, you can do:

// app/route/some-new-route.js

import Em from 'ember';

export default Em.Route.extend({

  // Close the modal that was opened in a previous route.
  setupController: function(controller, model) {
    this._super(controller, model); // Default route functionality
    controller.get('modal').hide();
  }

});

hide() accepts an optional argument. This should be a string referencing the name of the outlet to remove the modal from. You will only need to use this when the modal is in an outlet that is different from the modal controller's defaultOutlet property.

// app/route/some-new-route.js

import Em from 'ember';

export default Em.Route.extend({

  // Close the modal that was opened in a previous route.
  setupController: function(controller, model) {
    this._super(controller, model); // Default route functionality
    controller.get('modal').hide('loading-screen'); // Name of outlet
  }

});

From a View

You may close a modal from inside any view or template by sending the closeModal action. The close modalAction just proxies the method of closing from the controller.

This is a very common way of closing a modal from the modal layout or template. If you have a close button in your template you can use this action. For example:

{{!--app/templates/some-form.hbs--}}

<button {{action 'closeModal'}}>Close</button>

As previously mentioned, you can pass the outlet name to the closeModal action. This is only necessary if the modal is in a different outlet than the one named by the modal controller's defaultOutlet property.

In any view extended from ember-modals/views/modal, you will have access to the outlet property for this very occasion:

{{!--app/views/some-modal-content.hbs--}}

<button {{action 'closeModal' view.outlet}}>Close</button>

Simple!

If you want to do some other handling as well as closing the modal then you should probably write a custom action that closes the modal from your controller. For example:

{{!--app/templates/some-form.hbs--}}

<button {{action 'continue'}}>Continue</button>
<button {{action 'closeModal'}}>Close</button>

And then in your controller:

// app/controllers/some-form.js

import Em from 'ember';

export default Em.ObjectController.extend({

  actions: {
    continue: {
      this.doTheThing(); // Do whatever it is you need to do
      this.get('modal').hide(); // Then close the mdoal
    }
  }

});

You may find it necessary to use promises and place the call to close the modal in the resolve callback.

Alternatively, in a view's method you can use Ember's send() method or just do this.get('controller.modal').hide().


Previous: Installation | Next: Customization