Creating a Drop-down list

Hi I am new to ember and trying to create a dropdown list using .

Is there a way where I can access the selected item in drop down list in ember via component? Give me a short example.

Thanks in advance.

There are a number of ways to write a select component but the absolute simplest way would be like this:

// app/components/my-select.hbs
<select  {{on "change" (pick "target.value" @onChange)}}>
  {{#each @options as |theOption|}}
    <option value={{theOption}} selected={{eq theOption @value}}>{{theOption}}</option>
  {{/each}}
</select>

So what’s going on here?

  • We’re rendering a <select > tag, that’s just standard HTML.
  • We’re adding an event handler to the select tag with the {{on ...}} modifier. This sets up an onchange handler and delegates the actual handling to the onChange argument of the component (so whatever is rendering the component can set the value, this component shouldn’t do any mutations itself)
  • We “pick” the target.value attribute from the change event (so the onChange function receives just the selected value, not the whole change event)
  • Next we render each option that was passed in using the option tag, and only mark the option tag as selected if @value is equal to the current option.

and then you could render it like this:

<MySelect
  @value={{this.selectedOption}}
  @options={{this.options}}
  @onChange={{set this "selectedOption"}}
/>

Here we’re binding the local “selectedOption” property to the @value argument of the select. Next we’re passing in options (I assumed they’re at this.options but you could pass them in with any alias of course). Lastly we’re setting up a change handler. The set helper (see note below) is just an easy way of saying "set the value we receive to the new value of this.selectedOption.

Note that I’m using helpers from three different helper libraries here:

  • {{pick ...}} from ember-composable-helpers
  • {{eq ...}} from ember-truth-helpers
  • {{set ...}} from ember-set-helper

None of those are necessary but they make the example above the most compact and javascript-free. If you added a javascript class or two you could get the same effect without addons or writing extra helpers.

For the sake of completeness I’ll add snippets for a no-addons version below:

// app/components/my-select.hbs
<select  {{on "change" this.onChange}}>
  {{#each @options as |option|}}
    <MyOption @value={{option}} @selectValue={{@value}} />
  {{/each}}
</select>

// app/components/my-select.js
import Component from '@glimmer/component';
import { action } from '@ember/object';

export default class MySelectComponent extends Component {
  @action onChange(event) {
    this.args.onChange(event.target.value);
  }
}

// app/components/my-option.hbs
<option value={{@value}} selected={{this.selected}}>{{@value}}</option>

// app/components/my-option.js
import Component from '@glimmer/component';

export default class MyOptionComponent extends Component {
  get isSelected() {
    this.args.value === this.args.selectValue;
  }
}
2 Likes

Hi thanks for your help it is very helpful in understanding. I am very new to ember/ javascript. I also need help on where to define the drop down list options like ‘Yes’ and ‘No’. Thanks in advance.

Sure thing. As with anything there a lot of ways you could do it, but I’ll break it down and give you a couple examples.

First, we’re making the assumption that we’re using the component we created above, to simplify things. Second, there are at least 3 ways you could create/bind options and give them to the component:

  1. using a helper - this doesn’t require any javascript and can be done in the template
  2. create them on a controller (or get them from a route model) and pass them in via the route template - this would apply if you are rendering this select component directly in a route template, and you could use either static options created on the controller, or dynamic options passed in from a route model (which is bound to the controller as “model”)
  3. create them on a component backing class and pass them in when rendering the select in the component template - this would apply if you have a different component (e.g. a form component) rendering your select component

Scenario 1: helper

The simplest option is to create/bind the options via the {{array }} helper. This remove the need to use javascript entirely. To do that you could render your component like this:

<MySelect
  @value={{this.selectedOption}}
  @options={{array "Yes" "No"}}
  @onChange={{set this "selectedOption"}}
/>

The “array” helper takes a list of arguments and returns an array, so the above invocation is simply passing ["Yes", "No"] to the select as the options arg. This allows unlimited options to be passed and is also highly declarative.

Scenario 2: controller

Controllers will eventually go away so you may as well start thinking in component terms and skip to #3 but controllers still have their place so it’s still worth showing. Your controller should have the same name as your route and look something like this:

// app/controllers/my-route-name.js
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';

export default class MyRouteNameController extends Controller {
  @tracked selectedOption = null;
  options = ["Yes", "No"];

  @action optionChanged(newOption) {
    this.selectedOption = newOption;
  }
}
// app/templates/my-route-name.hbs
<MySelect
  @value={{this.selectedOption}}
  @options={{this.options}}
  @onChange={{this.optionChanged}}
/>
<div>The currently selected option is: {{this.selectedOption}}</div>

What’s going on here? We’ve added two properties to the controller, and one action (you could omit the action if you use the set helper like I demonstrated earlier).

The first property, selectedOption, is tracked meaning it will auto-update any consumers (including bound template values) if it changes. This is important because the select will be changing this value. Next we have the options property, which of course holds your options. In this case it doesn’t need to be tracked because it’s static and won’t change. The options are always the same. Last, we have the action optionChanged. This handles updating selectedOption every time the option changes (and again you could omit this action if you use the set helper that I mentioned previously).

Finally we’re rendering the selected option at the bottom so you can see it live update as you change the select value. Neato!

Scenario 3: component

If you’re rendering the select inside another component it’s going to look almost exactly the same as the controller version, just in a different file:

// app/components/my-form.js
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';

export default class MyFormComponent extends Component {
  @tracked selectedOption = null;
  options = ["Yes", "No"];

  @action optionChanged(newOption) {
    this.selectedOption = newOption;
  }
}
// app/components/my-form.hbs
<MySelect
  @value={{this.selectedOption}}
  @options={{this.options}}
  @onChange={{this.optionChanged}}
/>
<div>The currently selected option is: {{this.selectedOption}}</div>

Literally the same thing, just note that it’s a component backing class holding the properties and action instead of a controller and the select is rendered in a component template and not a route template

3 Likes

Being a tailwindCss fun!! I go with some like GitHub - tailwindlabs/headlessui: Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS. Go! tailwindCss with JIT