Search code examples
ember.jsember-dataember-cli

How to highlight selected address?


If the user clicks the li entry, it triggers the useThisAddress action, which sets isSelected in the address object to true. From there bind-attr takes care of setting the appropriate class (ie `border-black').

Is this the right way to go about this? Or is there a better approach?

Below code works. However, I am a bit uncertain about setting isSelected to true on the address object, since it isn't a real property in the address object model.

// Controller
import Ember from 'ember';

export default Ember.Controller.extend({
  active: null,

  actions: {
    useThisAddress: function(address) {
      address.set('isSelected', true);
      var active = this.get('active');
      if (active) {
        active.set('isSelected', false);
      }
      this.set('active', address);
    }
  }
})

// Template
<ul>
  {{#each address in model}}
    <li {{bind-attr class='address.isSelected:border-black'}} {{action 'useThisAddress' address}}>
      Address: {{address.address1}} {{address.address2}}, {{address.city}}, {{address.postalCode}}
    </li>
  {{/each}}
</ul>

Solution

  • I think your gut reaction is correct - you shouldn't be setting properties on your models that are strictly related to your UI.

    Components are the building blocks in Ember for storing UI-layer state. It's a somewhat tricky time in Ember, because Controllers are also used for this purpose, but they're on the way out. Soon it will be all components.

    When I encounter a case like yours (list-with-selected-items), I usually make a list component and a list-item component. The list knows which item is selected, and each list-item knows whether its selected. For your simple case one of the other solutions may be more straight-forward, but I find typically with these lists, you'll eventually want more functionality (bind to selected item, more complex templates, etc.).

    So one approach may look like this:

    {{#address-list selected=selectedAddress as |list|}}
      {{#each address in model}}
        {{#address-list-item item=address list=list}}
          <p>{{address.address1}}</p>
        {{/address-list-item}}
      {{/each}}
    {{/address-list}}
    

    You use block params (note: Ember 1.10 or greater required) to pass the <address-list> into each <address-list-item>. Now, your address list item can tell the list a new address has been selected, by sending an action to it:

    App.AddressListItemComponent = Ember.Component.extend({
      classNameBindings: ['active'],
    
      active: function() {
        return this.get('list.selected') === this.get('item');
      }.property('list.selected', 'item'),
    
      click: function() {
        this.get('list').send('select', this.get('item'));
      }
    });
    
    App.AddressListComponent = Ember.Component.extend({
      actions: {
        select: function(item) {
          this.set('selected', item);
        }
      }
    });
    

    This sets up all the bindings for you, adds the .active class to each <address-list-item>, and lets you bind to the <address-list>'s selected item from outside the template.

    See a working JSBin here.

    Now, targeting the list like this to use .send is awkward, which is exactly why soon we'll be able to pass actions down into child components (check out the Road to Ember 2.0 guide and search for "Improving Actions"). Once that functionality lands, you'll be able to pass the select action handler from the <address-list> directly into each <address-list-item>. But this is one way to go, for now, and it'll make refactoring pretty straightforward when those changes do land.