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!
There are 2 comments in this discussion.
Read and join this discussion
join this discussion
david
said Question
Hello Walter,
First, thank you for this great tutorial about Ember and modal forms!
I moved the posts/index template into the posts template so the modal window opens above the post list. The problem is that since the fields have their values bounded to the properties of the model, the posts are live-updated behind the modal window. But I'd like them to be updated only when the user submits the form. I found this article about lazy-updating text fields but I can't adapt it to your code…
Thank you in advance for your help! Best regards,
David
Tags: Ember.js, Bootstrap, modals, live-update