PLEASE NOTE: This tutorial targets a deprecated version of Ember and WILL NOT WORK with current releases. I've updated this to work with v1.13+ & v2 - you can try the new tutorial here.

Modal dialogs can be tough to implement.

There, I said it. Modal windows, those ubiquitous friendly pop-up floaters that force you to rapidly tap the Esc key, are a dime a dozen. There are umpteen implementations available in every flavor you can think of (yes, all those links are for live modal-contianing libraries). Everyone has their own version, including various extensions and functionality. This leads to uncetainty: "which is the best? Which modal should I use? Exactly what opacity should I make my background cover?" Uncertainty leads to accumulation. Accumulation...leads to SUFFERING.

Step away from the library! Modals are just simple views, and there's a better way to implement them if we think of them that way. What better way to understand exactly how modals work than by writing one yourself?

Follow along with me to implement a "dead simple" modal dialog in Ember CLI. In this tutorial, we'll create a modal component we can drop in anywhere, and we'll discuss how to target it with actions elsewhere in your app.

Need a tl;dr? Gif here, code here, kthx.

App Scaffolding

We'll get started the Ember CLI way: with generators!
Run the following commands to lay out a rough scaffold:

$ ember new <your-app-name>
$ cd <your-app-name> 
$ ember generate controller application
$ ember generate component simple-modal

Now we've got a blank Ember application with a blank component called "simple-model" onboard. To make sure everything is hunky-dory, run ember server in your terminal, and then navigate your browser to http://localhost:4200. You should see a text response: "Welcome to Ember.js". One step down!

The Modal Component

Components created in Ember CLI are composed of two files:

  • the component itself, located in app/components/
  • the component view, located in app/templates/components

We'll work on the component itself first. Using you favorite text editor, open the component file located at app/components/simple-modal.js, and modify it as follows:

import Ember from 'ember';
import layout from '../templates/components/simple-modal';

export default Ember.Component.extend({
    actions: {
        toggleModal: function() {
            // OLD: this.set('enabled', !this.get('enabled'));
            this.toggleProperty('enabled'); 
            // Thanks for the tip, Joel!
        },
    },
    layout: layout
   });

That's it! Our component has two properties: layout and actions. Layout points at the template file we want to use for our component. In this case, we're including an ES6 import directive to bring the template created by ember generate into our component. Actions has a single action method, toggleModal, that simply switches the modal's 'enabled' property on and off. We'll see how this affects our component in the template.

The Modal Template

We're halfway there! Now, to create our modal's template, open the file located at app/templates/components/simple-modal.hbs and modify with something similar to this:

{{#if enabled}}
    <div {{action 'toggleModal'}} class="modal-fog">
        <div class="modal-frame">
            <div class="modal-title">
                {{title}}
            </div>
            <div class="modal-body">
                {{yield}}
            </div>
        </div>
    </div>
{{/if}}    

There are some new words in here, but we'll move slowly.

  • {{#if enabled}}..{{/if}}: This block means that the modal will only be rendered if the component's 'enabled' value is true. This ties directly into our toggleModal() action on the component.
  • <div {{action 'toggleModal'}} ...>: This will close our modal when the background fog is clicked, a common convention with most modals. This is just for testing - we'll talk later about moving this action.
  • {{title}}: Ember component templates can access methods and properties from their component files in the same way standard templates access properties on controllers. We'll set this title in our handlebars helper shortly.
  • {{yield}}: When a Handlebars helper is passed as a block, the content inside the block is "yielded" out to the template. In this case, we'll use our helper's block to store the modal's body content.

Now we have a component and component template, all in ~20 lines of code. We're almost there! As a sanity test, run ember server and ensure no errors pop up in your console. Next, let's make the modal visible.

Form Over Function

Before we wire the modal up, we want to make sure we have something to look at! Open your app/styles/app.css file and let's add some very simple modal styling:

.modal-fog {
      background: rgba(30, 30, 30, 0.5);
      position: fixed;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
}
.modal-frame {
    position: absolute;
    top: 10%;
    left: 20%;
    border: 1px solid #333;
    background: #ddd;
    height: 200px;
    width: 200px;
  }
  .modal-title {
    position: absolute;
    top: 0;
    width: 100%;
    height:30px;
    text-align: center;
    border-bottom: 2px solid black;
  }
  .modal-body {
    margin-top: 40px;
    padding: 5px;
    overflow: scroll;
  }

This is not going to be pretty, but it will demonstrate the modal effect. Follow your heart & feel free to spruce this CSS up as you see fit.

Ta-Dah!

Time to get our modal on our page. For this demo, we'll just plop the modal out onto our application route, though you can place it on whatever templates or routes you'd like. Open your application template at app/templates/application.hbs and add our new component helper to it:

{{#simple-modal enabled=true title="Simple Modal"}}
    This is our modal! Click anywhere to dismiss this notification.
 {{/simple-modal}}
 <h2 id="title">Welcome to Ember.js</h2>

 {{outlet}}

The attribute enabled=true will ensure we see the modal on page load.

Save everything, run ember server, and take your browser to localhost:4200. You should see something similar to this:

Our First Modal

BOOM! You just got modal'ed.

Why So Static?

So you've got a modal component, it's rendering appropriately, and clicking anywhere on the screen will dismiss it. That's great for notifications but not so grand for basically anything else. We went to be able to render our modal on command, whether that commands comes from a controller or another template's action. Let's get that functionality wired up now.

We'll do three things on our application template:

  1. Set the modal's default view state to hidden (via enabled)
  2. Add an id value to our modal.
  3. Add a button that will target a specific modal.

Again, open app/templates/application.hbs, and adjust your code to match this:

{{#simple-modal enabled=false title="Simple Modal" id="modal-main"}}
     Modal text. Oh boy!
{{/simple-modal}}
<h2 id="title">Welcome to Ember.js</h2>
<button {{action 'openModal' 'modal-main'}}>Open Modal Window</button>

{{outlet}}

Our application template is ready. Try using that button in your app now and you'll get an error informing you that 'openModal' went unhandled. Let's go ahead and add a new action to the controller we created for application earlier. Open app/controllers/application.js and add the following:

import Ember from 'ember';

export default Ember.Controller.extend({
    actions: {
        openModal: function(targetId) {
            var modal = Ember.Views.views[targetId];
            modal.send('toggleModal');
        }
    }
});

Woah. Only 2 lines in that action, but a lot of work happening. Let's look at each:

  • Ember.View.view[targetID]: Ember.View is the Ember class responsible for handling DOM manipulation and rendering. It keeps a handy hash of all the currently available views in its views property, and we can look up a view in that hash just like we would in any other object. In this case, we're using targetId, an anonymous function variable that we're passing in back in our application template's button. This targetId should match the id attribute of the {{simple-modal}} you want to target.
  • modal.send('toggleModal'): The send method triggers an action on the referenced object. In this case, we're telling the modal we looked up in Ember.View.views to fire its toggleModal event.

Setting up our openModal action in this way lets us target any of multiple modals on a page. It's an extra step to remember when declaring the action in your main template, but it's incredibly powerful for moving through multiple states.

Save everything, take a deep breath, and ember server...

Expecto Modal!

Your page should now look something like this:

SHA-ZAM

Now you're thinking with modals - without all the hassle of bringing in someone else's libraries & layouts! We've done a lot of jumping around, but this is incredibly lightweight - the total necessary codebase clocks in at around 30 lines (sans styling). Hooray, effeciency!

Now What?

So you've got a modal set up. What should you do next to further integrate it in your app? Here are some ideas:

  • Style it up: this tutorial is all about function, not art. Now's your chance to add color, borders, box-shadow's and fancy CSS animations. Make your modal a 3D cube that rotates and presents a different option on each side. It's YOUR modal - go crazy!
  • Add a 'Close' button: A modal that disappears when clicked could cause serious problems if you want to do some pretty mundane things like accept user input on a login form. We can refactor our modal template by removing the toggleModal action from our modal-fog div and adding it to a button on the modal's component template (might I suggest top: 0, right: 0?).
  • Extend your layouts: We have one modal with one simple template. Wouldn't it be nice if we could have a couple different prefab modal templates, maybe one for logins, one for notifications, and one for annoying market research surveys? WE CAN! Take a look back at your component's layout property, and consider how you might replace our current single layout with a couple different options. Here's a hint:

    import layout-notice from '../templates/components/modal-notice.hbs';
    import layout-logins from '../templates/components/modal-login.hbs';
     <...>
    layout: this.get('layout');
    

Conclusion and Disclaimer

So, that's your modal. It's super simple, fast to wire up and very extensible thanks to Ember's easy-going ways and ES6 imports.

Please do note that this tutorial doesn't cover even the simplest of error handling. Your new modal is not ruggedized, and is only meant for fun and experimentation. Please don't try to charge your modal in the microwave or rinse it under the faucet - it WILL break.

Thanks for sticking around, and good luck with those market research surveys! :)

Questions? Comments? Suggestions? @ADotMartin