Ember JS: How to bind a model to a select option - ember.js

I'm new to ember and trying to figure out:
How to bind a select option to a specific model.
How to update the template with the selected model for a given option.
This is my current code (truncated for brevity). Any help to point me in the right direction appreciated.
Template (EditRoom.hbs):
{{view Ember.Select
content=options
optionValuePath="content.id"
optionLabelPath="content.name"
prompt="Choose an option..."
}}
<div class="filter">
//This is where the models will be outputed
{{#each filter1}}<a></a>{{/each}}
{{#each filter2}}<a></a>{{/each}}
</div>
Controller:
App.EditRoomController = Ember.ObjectController.extend({
// Select dropdown
options: [
{text: "filter1", id: 1},
{text:"filter2", id: 2}
]});
Route:
App.EditRoomRoute = Ember.Route.extend({
model: function(params) {
return Ember.RSVP.hash({
filter1: this.store.find('filter1'),
filter2: this.store.find('filter2')
});
}
});

Related

Embjers store.createRecord or store.push not updating view

I'm trying to create a new record and push it into the store. I can persist it just fine (and i'll push to the store after successfully persisting, once i figure this out), but the view/template isn't being refreshed. I know persistence works because, if I refresh the entire page, the new record appears.
I also tried this.store.push(), and nothing happens. I've looked around and everyone seems to be using filter() but only on a child set of records in a parent controller...
Thanks in advance, i have feeling its something really basic.
route/projects
export default Ember.Route.extend({
model: function(){
return this.store.find('projects');
}
});
controllers/projects
export default Ember.ArrayController.extend({
actions: {
createProject: function() {
var title = this.get('newProjectTitle');
var description = this.get('newProjectDescription');
if (!title.trim()) { return; }
// Create the new Project model
var project = this.store.createRecord('project', {
title: title,
description: description
});
project.save();
this.set('newProjectTitle', '');
this.set('newProjectDescription', '');
}
}
});
projects.hbs
<div class="form-group">
{{input type="text" class="form-control" placeholder="title" value=newProjectTitle}}
{{input type="text" class="form-control" placeholder="description" value=newProjectDescription action="createProject"}}
</div>
<button class="btn btn-xs btn-success" {{action 'createProject'}}>Create this Project</button>
<ul class="list-group">
{{#each project in model}}
{{#link-to 'project' project}}
<li class="list-group-item">
{{project.id}} - {{project.title}}
</li>
{{/link-to}}
{{/each}}
</ul>
You are missing the Setup Controller method -
setupController: function(controller, model) {
controller.set('model', model);
}
Use above method in route/projects.js file.
your code will be like -
export default Ember.Route.extend({
model: function(){
return this.store.find('projects');
}
setupController: function(controller, model) {
controller.set('model', model);
}
});
you can also refer this link for more information -
http://guides.emberjs.com/v1.10.0/routing/setting-up-a-controller/
ended up going with.. which works.. but is this the best way to do this?
var model = this.get('model');
model.pushObject(project);
I also found that...
var posts = this.store.find('post'); //network request.
var posts = this.store.all('post'); //no network request, but does live reload
Ember.js Models:Finding Reords

Weird binding with Ember.Select value

I'm having a weird issue with the Ember.Select view when I try to bind its value to a model.
Here is an abstract of what I'm doing, the complete jsbin can be found here:
Using JavaScript: http://jsbin.com/jayutuzazi/1/edit?html,js,output
Using CoffeeScript: http://jsbin.com/nutoxiravi/2/edit?html,js,output
Basically what I'm trying to do is use an attribute of a model to set an attribute of another model.
I have the Age model like this
App.Age = DS.Model.extend({
label: DS.attr('string'),
base: DS.attr('number')
});
And an other model named Person like this
App.Person = DS.Model.extend({
name: DS.attr('string'),
ageBase: DS.attr('number')
});
The template looks like this:
<!-- person/edit.hbs -->
<form>
<p>Name {{input value=model.name}}</p>
<p>
Age
{{view "select" value=model.ageBase
content=ages
optionValuePath="content.base"
optionLabelPath="content.label"}}
</p>
</form>
What I am trying to do is have a select in the Person edit form that lists the ages using base as value and label as label.
I expect the correct value to be selected when loading and to change when the selected option changes.
Has can be seen in the jsbin output, the selected is correctly populated but it sets the ageBase value of the edited person to undefined and does not select any option. The model value is correctly set when an option is selected though.
Am I doing something wrong ? Is it a bug ? What am I supposed to do to make this work ?
Thank you :)
You can conditionally render based on fulfilment of the ages as follows, since select doesn't handle promises (more on that below):
{{#if ages.isFulfilled}}
{{view "select" value=ageBase
content=ages
optionValuePath="content.base"
optionLabelPath="content.label"}}
{{/if}}
I updated your JsBin demonstrating it working.
I also illustrate in the JsBin how you don't have to qualify with model. in your templates since object controllers are proxies to the models they decorate. This means your view doesn't have to be concerned with if a property comes from the model or some computed property on the controller.
There is currently a PR #9468 for select views which I made a case for getting merged into Ember which addresses some issues with selection and option paths. There is also meta issue #5259 to deal with a number of select view issues including working with promises.
From issue #5259 you will find that Ember core developer, Robert Jackson, has some candidate select replacements. I cloned one into this JsBin running against latest production release version of Ember.
There is nothing at all preventing you using Roberts code as a select view replacement in your app. Asynchronous collections/promises will just work (and it is MUCH faster rendering from the benchmarks I have seen).
The template for that component is just:
{{#if prompt}}
<option disabled>{{prompt}}</option>
{{/if}}
{{#each option in resolvedOptions}}
<option {{bind-attr value=option.id}}>{{option.name}}</option>
{{/each}}
The js of the component is:
App.AsyncSelectComponent = Ember.Component.extend({
tagName: 'select',
prompt: null,
options: null,
initialValue: null,
resolvedOptions: null,
resolvedInitialValue: null,
init: function() {
var component = this;
this._super.apply(this, arguments);
Ember.RSVP.hash({
resolvedOptions: this.options,
resolvedInitialValue: this.initialValue
})
.then(function(resolvedHash){
Ember.setProperties(component, resolvedHash);
//Run after render to ensure the <option>s have rendered
Ember.run.schedule('afterRender', function() {
component.updateSelection();
});
});
},
updateSelection: function() {
var initialValue = Ember.get(this, 'resolvedInitialValue');
var options = Ember.get(this, 'resolvedOptions') || [];
var initialValueIndex = options.indexOf(initialValue);
var prompt = Ember.get(this, 'prompt');
this.$('option').prop('selected', false);
if (initialValueIndex > -1) {
this.$('option:eq(' + initialValueIndex + ')').prop('selected', true);
} else if (prompt) {
this.$('option:eq(0)').prop('selected', true);
}
},
change: function() {
this._changeSelection();
},
_changeSelection: function() {
var value = this._selectedValue();
this.sendAction('on-change', value);
},
_selectedValue: function() {
var offset = 0;
var selectedIndex = this.$()[0].selectedIndex;
if (this.prompt) { offset = 1; }
return Ember.get(this, 'resolvedOptions')[selectedIndex - offset];
}
});
The problem is that in:
{{view "select" value=model.ageBase
When the app starts, value is undefined and model.ageBase gets synchronized to that before value is synchronized to model.ageBase. So, the workaround is to skip that initial undefined value.
See: http://jsbin.com/rimuku/1/edit?html,js,console,output
The relevant parts are:
template
{{view "select" value=selectValue }}
controller
App.IndexController = Ember.Controller.extend({
updateModel: function() {
var value = this.get('selectValue');
var person = this.get('model');
if ( value ) { // skip initial undefined value
person.set('ageBase', value);
}
}.observes('selectValue'),
selectValue: function() {
// randomly used this one
return this.store.find('age', 3);
}.property()
});
givanse's answer should work.
I don't think it's because value is undefined, but because value is just an integer (42) and not equal to any of the selects content, which are Person objects ({ id: 2, label: 'middle', base: 42 }).
You could do something similar to what givens suggests or use relationships.
Models
//
// Models
//
App.Person = DS.Model.extend({
name: DS.attr('string'),
ageBase: DS.belongsTo('age', { async: true })
});
App.Age = DS.Model.extend({
label: DS.attr('string'),
base: DS.attr('number')
});
//
// Fixtures
//
App.Person.reopenClass({
FIXTURES: [
{ id: 1, name: 'John Doe', ageBase: 2 }
]
});
App.Age.reopenClass({
FIXTURES: [
{ id: 1, label: 'young', base: 2 },
{ id: 2, label: 'middle', base: 42 },
{ id: 3, label: 'old', base: 82 }
]
});
Template:
<h1>Edit</h1>
<pre>
name: {{model.name}}
age: {{model.ageBase.base}}
</pre>
<form>
<p>Name {{input value=model.name}}</p>
<p>
Age
{{view "select" value=model.ageBase
content=ages
optionValuePath="content"
optionLabelPath="content.label"}}
</p>
</form>
Ok, I found a solution that I think is more satisfying. As I thought the issue was coming from ages being a promise. The solution was to ensure that the ages list was loaded before the page was rendered.
Here is how I did it:
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
person: this.store.find('person', 1),
ages: this.store.findAll('age')
});
}
});
That's it! All I need from there is to change the view according the new model:
{{#with model}}
<form>
<p>Name {{input value=person.name}}</p>
<p>
Age
{{view "select" value=person.ageBase
content=ages
optionValuePath="content.base"
optionLabelPath="content.label"}}
</p>
</form>
{{/with}}
The complete working solution can be found here: http://jsbin.com/qeriyohacu/1/edit?html,js,output
Thanks again to #givanse and #Larsaronen for your answers :)

Update value when value is selected in Ember.Select

I have a Ember.Select in my template and an image:
Difficulty: {{view Ember.Select id="id_diff" contentBinding="difficulties" optionValuePath="content.id" optionLabelPath="content.title"}}
<img src="(path)" />
The Select is filled with values coming from server; in the controller:
difficulties: function() {
return this.get('store').find('difficulty');
}.property()
and the model:
Gmcontrolpanel.Difficulty = DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
path: DS.attr('string')
});
And that's ok; but i would like that when a difficulty is selected from the Ember.Select, the corrispondent path property would be inserted in the img tag
Anyone knows how to get this result?
To accomplish this, I would set up a couple of things.
First, update your Ember.Select to include a valueBinding against the model with a new property:
{{view Ember.Select id="id_diff" contentBinding="difficulties" optionValuePath="content.id" optionLabelPath="content.title" valueBinding="selectedDificulty"}}
This will bind your select view to a model object. Which means, on the controller, we can now include a new function with a .observes on that field:
updateImage : function(){
this.set('fullPath', this.get('path') + this.get('selectedDificulty'));
}.observes('selectedDificulty');
And finally, change your image path to the newly created one:
<img src="(fullPath)"/>

List of checkboxes values as array like Ember.Select (multiple)

Is there a way to get the data of a list of checkboxes in Ember as an array of id's?
In my app I want to make a new Site. I made a route to /sites/new to show a form with fields to add a Site.
This is my Model:
App.Site = DS.Model.extend({
name : DS.attr('string'),
languages: DS.hasMany('App.Language')
});
App.Language = DS.Model.extend({
name : DS.attr('string')
});
This is my Controller:
app.SitesNewController = Em.ObjectController.extend({
needs : [ 'languages' ],
name: null,
languages: null,
createSite : function() {
// Get the site name
var name = this.get('name');
var languages = this.get('languages');
console.log(name,description,languages);
// Create the new Site model
app.Site.createRecord({
name : name,
languages : languages
});
// Save the new model
this.get('store').commit();
}
});
This is (part of) my SitesNewView:
{{#each controllers.languages}}
<label class="checkbox">
{{view Ember.Checkbox checkedBinding="languages"}}
{{ name }}
</label>
{{/each}}
<button {{ action "createSite" }}>Save</button>
In my console languages is null. How do I get an array of language-id's out of this.get('languages')?
UPDATE
I mean something like an Ember.Select with attribute multiple=true, like this: {{view Ember.Select selectionBinding="languages" contentBinding="controllers.languages" optionValuePath="content.id" optionLabelPath="content.name" multiple="true"}}
Take a look to the jsfiddle that I quickly created.
This may not be the best solution but at least it should help you.

select dropdown with ember

I'm trying to produce a select input and pass the selected object to the change event on the view. The ember contact example uses a <ul> but with a select the view needs to be outside the each otherwise the change even isn't fired.
Here is the view js:
App.SelectView = Ember.View.extend({
change: function(e) {
//event for select
var content = this.get('content');
console.log(content);
App.selectedWidgetController.set('content', [content]);
},
click: function(e) {
//event for ul
var content = this.get('content');
console.log(content);
App.selectedWidgetController.set('content', [content]);
}
});
The ul from the example works:
<ul>
{{#each App.widgetController.content}}
{{#view App.SelectView contentBinding="this"}}
<li>{{content.name}}</li>
{{/view}}
{{/each}}
</ul>
But if I replace html directly, the change event isn't fired (which makes sense)
<select>
{{#each App.widgetController.content}}
{{#view App.SelectView contentBinding="this"}}
<option>{{content.name}}</option>
{{/view}}
{{/each}}
</select>
So I guess the select has to be wrapped in the view.. in which case how do I pass the relevant object?... This code results in the entire array being passed:
{{#view App.select_view contentBinding="App.widgetController.content"}}
<select>
{{#each App.widgetController.content}}
<option>{{name}}</option>
{{/each}}
</select>
{{/view}}
Ember now has a built-in Select view.
Here's a usage example:
var App = Ember.Application.create();
App.Person = Ember.Object.extend({
id: null,
firstName: null,
lastName: null,
fullName: function() {
return this.get('firstName') + " " + this.get('lastName');
}.property('firstName', 'lastName').cacheable()
});
App.selectedPersonController = Ember.Object.create({
person: null
});
App.peopleController = Ember.ArrayController.create({
content: [
App.Person.create({id: 1, firstName: 'Yehuda', lastName: 'Katz'}),
App.Person.create({id: 2, firstName: 'Tom', lastName: 'Dale'}),
App.Person.create({id: 3, firstName: 'Peter', lastName: 'Wagenet'}),
App.Person.create({id: 4, firstName: 'Erik', lastName: 'Bryn'})
]
});
Your template would look like:
{{view Ember.Select
contentBinding="App.peopleController"
selectionBinding="App.selectedPersonController.person"
optionLabelPath="content.fullName"
optionValuePath="content.id"}}
Again, here's a jsFiddle example: http://jsfiddle.net/ebryn/zgLCr/
check out the answers to a similar question: How to bind value form input select to attribute in controller
In the examples a CollectionView is used with an tagName=select. You may find this helpful in getting it work.
EDIT: Since I was looking to implement a select myself, here is the solution I came up with:
views/form.js.hjs:
{{#view contentBinding="App.typeController" valueBinding="type" tagName="select"}}
{{#each content}}
<option {{bindAttr value="title"}}>{{title}}</option>
{{/each}}
{{/view}}
{{#view Ember.Button target="parentView" action="submitEntry"}}Save{{/view}}
The select is part of a form. I do check for the submit event and in there read the value:
app.js.coffee
# provides the select, add value: 'my_id' if you need differentiation
# between display name (title) and value
app.typeController = Ember.ArrayProxy.create
content: [{title:'Energy'}, {title:'Gas'}, {title:'Water'}]
# simplified version, but should prove the point
app.form_view = Ember.View.create
templateName: 'views_form'
type: null
submitEntry: () ->
console.log this.$().find(":selected").val()
Hope this helps.
This isn't an Answer, just a fix on the broken jsfiddle link.. Apparently jsfiddle has no love for ember :/ But JsBin does! http://jsbin.com/kuguf/1/edit