How to do a select (dropdown) in Ember 2.0?

29 August 2015

When the select view was about to be removed from Ember, a lot of people -myself included- wondered how it was going to be replaced. My bet was on a 'select component', after all, views should be transformed into components, right?

Then I saw this gist from Edward Faulkner:

1<select onchange={{action (mut vehicle) value="target.value"}}>
2  {{#each vehicles as |vehicleChoice|}}
3    <option value={{vehicleChoice}} selected={{eq vehicle vehicleChoice}}>{{vehicleChoice}}</option>
4  {{/each}}

I did not understand half of it, so I dug down to see how the pieces come together. In this short post, I want to explain what I have found out.

Solve a simpler problem

A great mathematician, George Polya, wrote a book in 1945 called "How to Solve It", in which he puts down a framework for solving mathematical problems. One (probably more) of his recommendations can be applied to software development, too: Solve a simpler problem.

Heeding this advice, we'll first tackle a more mundane problem and in the second round, we'll solve the original riddle.

Let's assume Edward had written the following:

1<select onchange={{action "selectVehicle" value="target.value"}}>
2  {{#each vehicles as |vehicleChoice|}}
3    <option value={{vehicleChoice}} selected={{eq vehicle vehicleChoice}}>{{vehicleChoice}}</option>
4  {{/each}}
 1import Ember from 'ember';
 3export default Ember.Controller.extend({
 4  vehicle: null,
 5  vehicles: Ember.String.w('Tesla Chrysler Toyota'),
 6  actions: {
 7    selectVehicle(vehicle) {
 8      this.set('vehicle', vehicle);
 9    }
10  }

This is easier (as in: more familiar) but there are still a few things that might need explanation. First, before 1.13.3, event listeners on browser events could not trigger Ember actions like that:

1<select onchange={{action "selectVehicle" value="target.value"}}>

What this does, is that when the selected value of the dropdown changes, it fires the onchange listener we added on the <select> which results in calling our action handler, selectVehicle. The handler just updates the vehicle property of the controller. This will mark as selected the dropdown option the

(eq comes from a great little Ember addon called ember-truth-helpers. It returns true if the two parameters are equal).

Since there is no two-way binding set up between the selected option of the dropdown and the controller property (vehicle), this needs to be done using a DOM event listener (onchange) and updating in the action handler. That's exactly what happens in the selectVehicle action handler.

So far so good, let's move on.

Solve the original one

Wait a minute. How did selectVehicle receive the selected vehicle choice (e.g

When the browser calls an event listener, it passes it an event object which would become the first parameter of selectVehicle. However, selectVehicle does not receive the event but the actual value of the selected option, how does that come about? The missing link is a lesser-known option of the action helper, value. The property passed to it is read off of the first parameter of the handler and then replaces it. In our example, target.value is looked up on the event object, which is exactly the value of the select option that triggered the onchange event.

Ok, only one thing left.

The original example had this line:

1<select onchange={{action (mut vehicle) value="target.value"}}>

instead of the more familiar:

1<select onchange={{action "selectVehicle" value="target.value"}}>

What mut does here is that it allows updating the passed property, so when the action is called, vehicle is set to the passed value, the value of the selected option. That is it, we solved the riddle.

The same implementation pattern can be used to update the properties related to checkboxes, input fields and radio buttons. This new way of doing things takes a while to get used to, but we'll see more and more of it with one-way bindings becoming best practice.

(By the way, my PR to add a section about the actions helper's value option to the guides was merged yesterday, so hopefully more people will know about it.)

Wait, there is more!

This post received a lot of comments, some of which are inquiring about more complex use cases. I decided to do a screencast where I implement some of these. You can get it by signing up below:

Share on Twitter