Topic: Modal forms in Ember.js

Topic type:

Example code using Twitter Bootstrap with Ember.js 1.0.0-rc.1 and Ember Data (See Update for helpful links for later versions of Ember.js)

Update for later Ember.js:

There is now a recipe for how to do this (sans Bootstrap) in the official Ember.js Cookbook. You should be able to adjust that approach to work with Bootstrap by using Bootstrap CSS modal classes.

Update:

This was written to when 1.0.0-pre4 was current, but has working sample code has been updated to point at 1.0.0-rc.1 and confirmed to work. Thanks for debugging help from dogawaf in #emberjs IRC.

Full working code sample

See http://jsbin.com/ixigez/83/edit

Code Highlights

These are purposely fairly standard routes for a simple Ember.js application (if you are using 1.0.0-pre4 and later).

There isn't anything here that assumes that the posts.new or the post.edit routes will be presented as modals. That's as it should be, but they are declared as distinct states that our application is in. This buys a lot, as we will see.

As you might guess, in this Ember.js simple blog application, the new and edit form implementation are exactly the same. Ember.js is smart enough in its conventions that we can use the same form template to handle both.

Here's the posts/form template:

The template uses Twitter Bootstrap specific CSS classes that specify "modal" in them to be presented in modal windows, but otherewise there is nothing here that is out of the ordinary HTML augmented with Bootstrap and Ember.js handlebars code.

There are couple things to note though. The template uses the action helper in a few places to wire up some events that we will later support in our route handlers; cancel and submit.

The Ember.Textfield helpers also have their values bound to content.title and content.body which correspond to our Post model's properties. In other words, content is set to an instance of our Post model. This is pretty easy to do with the new router and route handlers.

Let's take a look:

Basically the route handlers take advantage of Ember.js's convention that the model property will be set to the generated controller's content by default (and therefore made available to our template as content).

In other words, all we have to do to set up the data for the form template is define our model properties in our route handlers.

The route handlers also have some shared code in the PostsFormable mixin. It starts with the renderTemplate declaration. I'll come back to that in a second.

Remember the action helper's calls to "cancel" and "submit"? The mixin's code contains the route handler's events declaration that has the code that handles them.

With cancel, the instance of the post that we created for the post.new is cleared out and then the application is changed to the posts route (i.e. the list of posts).

With submit, the instance of the post is commited, thus pushed to our data store, and permanently added to our application, and again we switch to the posts route.

Neither of them have anything specifically to do with the fact that we using modals for our presentation. It's handling the data and state details of our application without being concerned about the presentation of the form per se. Nicely separated from our templates and views.

Where the mixin does connect with presentation is simply to say that the route handlers' routes will both render using the posts/form template and that they should be connected to the outlet named 'modal'.

 This is because we are differing from the default template naming convention where routes are paired with a template with the same name. This allows us to re-use the posts/form template for both posts.new and post.edit routes and be a bit DRYer.

So what triggers our forms to look like modals?

So far all we have done to set up our posts.new and post.edit forms to be modal is use a different named outlet and add some CSS classes in our template's HTML, but there is one last essential piece to complete things; the view.

From the Ember.js guide for Views' introduction:

Views in Ember.js are typically only created for the following reasons:

  • When you need sophisticated handling of user events
  • ...

Events in this context are user interaction events, such as clicks, mouseovers, etc. So when a user clicks on a link or button to get the form, we want to use a modal transition and presentation.

Since our forms represent distinct routes in our applicaton, we use the linkTo helper to change the application to this route. A link to our form may be in multiple places in our application and so it would add a lot of complexity to handle it in the view where the user clicks from (alternatively you could define a helper to be DRY, but its not worth it in this case), so this is subprime.

Turns out Ember.js can handle this really elegantly with two view events on the view that represents our form; when the view's HTML element is added to the DOM and when the view's HTML element is pulled out of the DOM.

First we wire up a bit of the HTML that ties into Bootstrap's modal handling. Since our view corresponds directly to form semantically in HTML, we switch the view's tag to "form". This will wrap our template in a form tag.

As a part of the work to tie to Bootstrap, we declare the CSS classes that the form tag will have. This is of course critical for Bootstrap's CSS and JavaScript to work as we expect.

However what really triggers our modal and cleans them up when we dismiss or submit our forms is didInsertElement and willDestroyElement.

How this works is that when our application switches to a route that has the posts/form template (and thus the App.PostsFormView), this view's element is added to the DOM and the didInsertElement callback function is then run. There is a parallel process that happens when our applications switches out of route that uses the posts/form. The willDestroyElement is called just before our view's element is pulled from the DOM.

All these two callbacks have to do is run the code that shows or hides the modal like you would with standard Bootstrap.

One neat detail is that Ember.js provides with a shortcut to only this view's HTML via the @$() (or this.$()in JavaScript) jQuery tie-in.

And that is it. Enjoy!

Discuss This Topic

There are 2 comments in this discussion.

Read and join this discussion

join this discussion