1

I'd like to create a drop-down menu from a hasMany relation which filters the values of an other drop-down menu. I have ships which belongsTo companies and which haveMany cruises. The user of the webpage should be able to select nothing (the table displays all ships) or a company (the table displays just the ships of that company) or a specific ship (the table just displays one ship). If the user selected a specific company only ships of that company should be displayed in the ship drop-down menu. The table of ships should be updated too.

Screenshot of the ships index table.

How can I create a set of two drop-down menus (above or below the table) which behave like this example and filter the content of the table?

Screenshot of an drop down example.

I know this question is a tall order. I tried to break it down as much as possible. Thank you for your time and effort.

The code of my example application

ember new travel_agency
cd travel_agency
ember install:addon ember-cli-scaffold
ember g scaffold ship name:string
ember g scaffold company name:string
ember g scaffold cruise starts_at:date
ember generate adapter application
ember g http-mock ships
ember g http-mock companies
ember g http-mock cruises
ember install:addon ember-moment

app/adapters/application.js

import DS from 'ember-data';

export default DS.RESTAdapter.extend({
  'namespace': 'api'
});

app/models/company.js

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  ships: DS.hasMany('ship', { async: true })  
});

app/models/ship.js

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  company: DS.belongsTo('company', { async: true }),
  cruises: DS.hasMany('cruise', { async: true })  
});

app/models/cruise.js

import DS from 'ember-data';

export default DS.Model.extend({
  startsAt: DS.attr('date'),
  ship: DS.belongsTo('ship', { async: true })  
});

server/mocks/ships.js

[...]
var shipList = [
  {"id":1,"name":"Carnival Fantasy","company":1,"cruises":[1,2]},
  {"id":2,"name":"Carnival Triumph","company":1,"cruises":[3,4]},
  {"id":3,"name":"Queen Mary 2","company":2,"cruises":[5]},
  {"id":4,"name":"Queen Elizabeth","company":2,"cruises":[6]},
  {"id":5,"name":"Norwegian Jewel","company":3,"cruises":[7,8]}
]
[...]

server/mocks/companies.js

[...]
var companyList = [
  {"id":1,"name":"Carnival"},
  {"id":2,"name":"Cunard"},
  {"id":3,"name":"Norwegian Cruise Line"}
]
[...]

server/mocks/cruises.js

[...]
var cruiseList = [
  {"id":1,"startsAt":"2014-10-01","ship":1},
  {"id":2,"startsAt":"2014-10-15","ship":1},
  {"id":3,"startsAt":"2014-10-30","ship":2},
  {"id":4,"startsAt":"2014-11-10","ship":2},
  {"id":5,"startsAt":"2014-11-20","ship":3},
  {"id":6,"startsAt":"2014-11-20","ship":4},
  {"id":7,"startsAt":"2014-10-20","ship":5},
  {"id":8,"startsAt":"2014-11-20","ship":5}
]
[...]

app/templates/ships/index.hbs

[...]
<tbody>
  {{#each ship in model}}
    <tr>
      <td>
        {{ship.name}}
      </td>
      <td>
        {{ship.company.name}}
      </td>
      <td>
        {{#each cruise in ship.cruises}}
          {{moment date "L" cruise.startsAt}}, 
        {{/each}}
      </td>
      <td>
        {{link-to "Edit" "ships.edit" ship}}
      </td>
      <td>
        {{link-to "Show" "ships.show" ship}}
      </td>
      <td>
        <a href="#" {{action "remove" ship}}>Remove</a>
      </td>
    </tr>
  {{/each}}
</tbody>
[...]
wintermeyer
  • 8,178
  • 8
  • 39
  • 85
  • This could be too large of an answer for a simple stackoverflow question. My assumption is that you need to observe the value of the first dropdown, and based on the value obtained, pass a different options list to the second dropdown. The second list of options can be a computed property depending on the selected value from the first list of options. – nem035 Mar 31 '15 at 18:47

1 Answers1

1

I'm not going to dive into your whole application. Because it doesn't have much to do with the functionality you want. If I understand correctly you want to filter the second option-list based on the value of the first.

You'd need a template containing two option-lists

{{view "select" content=model.colors value=selectedItem}}
color: {{selectedItem}}
{{view "select" content=filteredBoats optionLabelPath="content.boat"}}

You'd need a route providing a model:

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return { 
      colors: [],
      boats: []
    };
  }
});

And then you need a controller wiring things up:

App.IndexController = Ember.Controller.extend({
  filteredBoats: function(){
      var selI = this.get('selectedItem');

      return this.get('model.boats').filter(
        function(boat) { 
           return boat.color == selI; 
        });
  }.property('selectedItem')
});

Lets recap and see what happens.

  1. In the view we bind the result of the first option-list to selectedItem in the controller
  2. In the controller we create a computed property and filter model.boats based on it's value
  3. In the view we bind the second option-list to the computed property.

See this jsbin.

albertjan
  • 7,739
  • 6
  • 44
  • 74