How to populate a select box from a datastore


#1

I would like to populate a select box dynamically from a datastore. My template form contains the following:

{{view "select" class="form-control" content=statusList value=status}}

where statusList is defined as a hard-coded array of strings:

App.ChannelsCreateController = Ember.ObjectController.extend({

    // TODO: get from store
    statusList : ['online','available','away',...,'unknown'],
    ....
});

I have a store called statusname and here is the fixture:

App.Statusname.reopenClass({
    FIXTURES: [
        { id: 301, name: 'online'    },
        { id: 302, name: 'available' },
        { id: 303, name: 'away'      },
        ...
        { id: 311, name: 'unknown'   }
    ]
});

Rather than using a static array of strings, how can I dynamically load the select box with the status names as extracted from my datastore?


#2

Hi kgish,

Have you seen the documentation for Ember.Select?

If you are providing an array of objects/datastore reference to the selectbox you will need to define a valuePath and labelPath like the example on the page provides.

App.ApplicationController = Ember.ObjectController.extend({
  programmers: [
    {firstName: "Yehuda", id: 1},
    {firstName: "Tom",    id: 2}
  ]
});

and

{{view "select"
       content=programmers
       optionValuePath="content.id"
       optionLabelPath="content.firstName"}}

#3

Here you are using a static array, but I do not want to do this. I want to have it filled on demand from a data store, so for example I can request via the adapter, e.g. GET => /statusnames.


#4

It seems from your datastore you are using fixtures which I have not personally used in quite some time. If you can provide a jsbin with your example I would be happy to look. As an additional resource it seems you need to provide the fixtures to your controller via the route. A tutorial has been posted regarding this workflow but you may need to modify it to use setupController unless statusList is the model for your controller.


#5

If I understand you well, I think you have to overwrite the standard ember select in order to work with your data layer (EmberData?)

You can overwrite the didSelect hook in order to change the selected variable from a primitive type variable (standard behaviour of selectect only what displayed) and cahnge it with the object you are retriveing from the datastore.


#6

Can you please provide some example code?


#7

You shouldn’t have to override ember select as far as I know, but it depends on what you are trying to do. If all you want to do is display your statusnames with fixtures:

Make sure you are letting ember know you are using fixtures:

// you could set the fixture adapter for statusnames only as well
App.ApplicationAdapter = DS.FixtureAdapter;

Set a controller variable for statusnames:

App.ChannelsCreateController = Ember.ObjectController.extend({
    statusnamesLookup: Ember.A()
});

Then you need to request the status names and assign the results to your controller variable. I’m still feeling my way through this ember thing, but I use the afterModel and setupController methods in the route:

App.ChannelsCreateRoute = Ember.Route.extend({
  //not necessarily necessary, but I like placeholders  
  statusnames: Ember.A(),
  
  // i assume you're creating a channel record
  model: function() {
    return return this.store.createRecord('channel);
  }

  // will provide a promise-based result like model()
  afterModel: function(record, transition){
    var self = this;
    return this.store.find('statusname').then(function(result){
      self.set('statusnames', result);
    });
  },

  setupController: function(controller, model){
    this._super(controller, model);
    // set the controller variable to the route's statusnames
    controller.set('statusnamesLookup', this.get('statusnames'));
  },
});

And then use ember select just as the docs suggest:

{{view "select"
    content=statusnamesLookup
    optionValuePath="content.id"
    optionLabelPath="content.name"}}

Working fiddle here: http://jsfiddle.net/v35ggxz5/3/

I’m guessing you’re going to want to do more with this, but this should get you up and running.


#8

Thanks very much, it works!

The values to populate the select box should not depend on whether I am using the Fixture or REST adapter.

This seems like an overly complicated solution for something so seemingly simple, is there no other more elegant way to do this.

Every time I want to fill such a status select box, I have to pollute my other routes and controllers with this extra code.


#9

Another solution that should work that requires less jumping through hoops: https://github.com/emberjs/ember.js/issues/9369#issuecomment-60014956


#10

You can put the raw fixture data in your controller if you want.

App.IndexController = Ember.ObjectController.extend({
    statusnamesLookup: [
      { id: 301, name: 'this'    },
      { id: 302, name: 'available' },
      { id: 303, name: 'away'      },
      { id: 311, name: 'unknown'   }
  ]
});

If you want to use a data-backed model, you are right, it still doesn’t matter if you use REST or fixtures. That’s handled by your model configuration.

I don’t know if this example is at all the standard practice. But from what I’ve read, the route is responsible for populating your model(s) and your controller then handles the state and display of the model.

The reason for the amount of code is to allow your route to request your data, wait for it with a promise, then have your controller display your model. This is the same process routes use for their target model, but some of that is auto-generated for you:

  1. route.model() requests your data (and requests is important here, it has to wait for a response.)
  2. route.setupController() assigns the model property on your controller
  3. controller provides the view or template with the model.

#11

That solution sets the controller variable directly in the setupController method. (And doesn’t use explicit variables in the route or controller.)

setupController: function(controller, model){
    this._super(controller, model);
    controller.set('statusList', this.store.find('statusname'));
}

I think the only difference in calling find() in afterModel() is that it will block the display/render of the template, while putting it in setupController() won’t.

In confession, my most painful moments with ember have been with select boxes. The issue jasonmit linked to forced me to put my first ember project on hold (and redo with angular) as I didn’t find the .content workaround in time. And I’m currently on my second project and having fun with changing relationships (with selects) and not seeing the model’s isDirty property change.