I can't seem to understand how to do the following looking over the docs for "ember power select".
I have a model user:
export default Model.extend({
"state": attr('string')
});
Stored as a value in the DB is: NY for state
I also have the following data to load into the ember power select options:
stateList: [
{
label: 'New Jersey',
value: 'NJ'
},
{
label: 'New York',
value: 'NY'
},
]
The following handlebar code will load in the states and display them. You can search and select the state:
{{#power-select
options=stateList
searchField="label"
selected=state
onchange=(action (mut state))
as |state|
}}
{{state.label}}
{{/power-select}}
The issue... on select of 'New York', I would like the stored value of 'state' to be 'NY'
Right now it's simply storing the entire object. I understand through 'onchange' I can set the value, but I don't really understand how you set the value to 'NY' and have it selected?
I've tried doing
this.set('state',selection.value)
But I think it's looking for the index of the object, I however simply want to pass 'NY' and not a whole object... is this possible?
Answer to your question is NO(AFAIK). But you can achieve this using computed property.
Component js code
import Ember from 'ember';
export default Ember.Component.extend({
stateList: [{'label': 'New Jersey','value': 'NJ'},{ 'label': 'New York','value': 'NY'}],
state:'NY',
selectedState: Ember.computed('state', function(){
return this.get('stateList').findBy('value',this.get('state'));
}),
actions:{
setSelectedState(selectedVal){
this.set('state',selectedVal.value);
}
}
});
Component hbs code
{{#power-select
options=stateList
searchField="label"
selected=selectedState
onchange=(action 'setSelectedState')
as |state|
}}
{{state.label}}
{{/power-select}}
{{yield}}
Here is Ember-Twiddle
Related
I am having an issue with getting drag-and-drop sort functionality with EmberJS working.
The tutorials that cover this issue don't provide any help on the initial sort, which I am doing through a computed property.
What I'm encountering seems to be a race condition between ember's rerender when the sortedItems computed property changes and jQueryUI Sortable updating the DOM. List Items get duplicated, or disappear altogether upon sorting.
Route
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return Ember.Object.create({
id: 1,
title: "Title",
subtitle: "Subtitle",
items: [
Ember.Object.create({
name: "Item 2",
sortOrder: 2,
id: 1
}),
Ember.Object.create({
name: "Item 1",
sortOrder: 1,
id: 2
}),
Ember.Object.create({
name: "Item 3",
sortOrder: 3,
id: 3
})
]
});
}
});
Controller
import Ember from 'ember';
export default Ember.ObjectController.extend({
sortProperties: [ 'sortOrder' ],
sortedItems: Ember.computed.sort('model.items', 'sortProperties'),
actions: {
updateSortOrder: function(indexes) {
this.get('items').forEach(function(item) {
var index = indexes[item.get('id')];
item.set('sortOrder', index);
});
}
}
});
View
import Ember from 'ember';
export default Ember.View.extend({
didInsertElement: function() {
var controller = this.get('controller');
this.$(".item-list").sortable({
update: function(event, ui) {
var indexes = {};
$(this).find('li').each(function(index) {
indexes[$(this).data('id')] = index;
});
controller.send('updateSortOrder', indexes);
}
})
}
});
Template
<h1>{{ title }}</h1>
<h2>{{ subtitle }}</h2>
<ul class="item-list">
{{#each item in sortedItems }}
<li data-id="{{ unbound item.id }}">
name: {{ item.name }}<br />
sortOrder: {{ item.sortOrder }}<br />
id: {{ item.id }}
</li>
{{/each}}
</ul>
Here's a Barebones Ember app that reproduces the issue: https://github.com/silasjmatson/test-sortable
Question
Is there a way to avoid this race condition or sort only once when the controller is initialized?
Apologies if there is already an answer on the interwebs for this. I haven't been able to resolve this despite a week of searching/experimenting.
You can sort only once when the controller is initialised:
sortedItems: Ember.computed(function() {
return Ember.get(this, 'model.items').sortBy('sortOrder');
})
Because you're using the computed sort property it is attempting to sort the list for you whenever the sortOrder property changes on any item, which is rerendering the #each and doing something funky.
Just sort once initially using the method above and then let jQuery handle the order of items - rather than jQuery and Ember.
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 :)
If the select is multipe=false, it works. If we change it to multiple=true, the bindings stop working.
http://jsfiddle.net/6Evrq/163/
App = Ember.Application.create();
App.Router.map(function () {
// put your routes here
});
App.IndexController = Ember.ObjectController.extend({
selectedServiceFlavours: Ember.computed.defaultTo('serviceFlavours.firstObject.myvalue'),
serviceFlavours: function(){
return [
{name: "1 (1)", myvalue: "1"},
{name: "2 (2)", myvalue: "2"},
{name: "3 (3)", myvalue: "3"}
];
}.property(),
});
And the select:
{{view Ember.Select content=serviceFlavours optionLabelPath="content.name" optionValuePath="content.myvalue" value=selectedServiceFlavours multiple=true }} selected: {{selectedServiceFlavours}}
Since the value property is not supported for a multiple select, you must use the selection property as you did in your fiddle:
{{view Ember.Select content=serviceFlavours optionLabelPath="content.name"
optionValuePath="content.myvalue" selection=selectedServiceFlavours multiple=true}}
In order to get your selected option to return to default on content update you need to pass the correct parameter to your computed property, like this:
selectedServiceFlavours: function () {
return [this.get('serviceFlavours.firstObject')];
}.property('serviceFlavours')
This way Ember knows to update the selectedServiceFlavour when the serviceFlavours change.
Here is your updated fiddle: http://jsfiddle.net/taLgt1md/5/
I have an Ember JS 1.5.1 app with ember-data 1.0.8 beta. There are TWO simple compiled templates the relevant parts are:
index
<div class="container-fluid">
<div class="col-md-2 sidebar">
<ul class="nav nav-sidebar">
{{#each model}}
<li>
{{#link-to 'activities' this}}{{name}}{{/link-to}}
</li>
{{/each}}
</ul>
</div>
<div class="col-md-10 col-md-offset-2">
{{outlet}}
</div>
</div>
activities
<div>
<ul>
{{#each model.activities}}
<div class="row">
<p>activity {{id}} is {{name}}</p>
</div>
{{/each}}
</ul>
</div>
The application is also simple, reduced to a few bits of fixture data and some route functions:
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter;
App.Router.map( function(){
this.resource('index', {path: '/'}, function(){
this.resource('activities', { path:':name'}, function(){
this.resource('activity');
});
});
});
App.IndexRoute = Ember.Route.extend({
model: function(){
return this.store.find('role');
}
});
App.ActivitiesRoute = Ember.Route.extend({
model: function(params){
var roles = this.modelFor('index');
return roles.findBy('name', params.name).get('activites');
}
});
App.Role = DS.Model.extend({
name: DS.attr('string'),
activities: DS.hasMany('activity', {async:true} )
});
App.Activity = DS.Model.extend({
name: DS.attr('string')
});
App.Role.FIXTURES = [{
id: 1,
name: 'Management',
activities: [1]
},{
id: 2,
name: 'Analysis',
activities: [1,2]
},{
id: 3,
name: 'Development',
activities: [2]
}]
App.Activity.FIXTURES = [{
id: 1,
name: 'talking'
},{
id: 2,
name: 'doing'
}];
What I get when I navigate to localhost is a simple list of the three roles on the left hand side of the screen and nothing on the right hand side. (as expected)
When I then select a link (such as 'Analysis') the outlet on the right hand side fills with the expected list of two activity names "talking" and "doing".
LHS list RHS pane
========== ========
Management talking
Analysis doing
Development
So far so good.
I noticed that when I hovered over the 'Analysis' link the browser shows the url below as expected
localhost:/#/Analysis
However when I cut and paste this url into the browser address bar directly I only get the left hand side list of links and nothing in the main window. The list of "talking" and "doing" does no appear. There are no errors shown in the browser and ember does not raise and exceptions.
How do you get this simple nested route to refresh all the contents when you directly deep link rather than having to navigate from the root all the time?
When you use link-to and pass it the model, it will skip the model hook supplying the model from the link-to to the route. If you refresh the page, it will hit each route down the tree until it's fetched the models for each resource/route necessary to fulfill the request. So if we look at your routes one at a time it will do this:
Hit the application route, fetch its model if it exists (application route is the root of every Ember app).
Hit your index route, where it will return App.Role.find()
Hit your activites route, where it will return App.Activity.find()
Number 3 is where you real issue lies. Regardless of whether or not that part of the url says Analysis, Management, or Development you will already return App.Activity.find(). You've defined the dynamic slug :name, ember will parse the appropriate part of the url, and pass that part is as an object, in the case of Analysis Ember will pass in { name: 'Analysis' } to your model hook. You will want to take advantage of this, to return the correct model.
App.ActivitiesRoute = Ember.Route.extend({
model: function(params){
var roles = this.modelFor('index');
return roles.findBy('name', params.name);
}
});
Additionally you are using a fairly old version of Ember Data. Here's a small example of how Ember Data should be used with newer versions: http://emberjs.jsbin.com/OxIDiVU/617/edit
As you can see, you no longer declare the store. Additionally you may run into trouble with what would be considered async properties, and might want to read https://github.com/emberjs/data/blob/master/TRANSITION.md
Can I and should i pass arrays to components in ember?
For example if I wanted to template something that renders a couple of radiobuttons with labels:
<label for="media">Media</label><input type="radio" name="entry.1602323871" value="media" id="media" />
<label for="guest">Guest</label><input type="radio" name="entry.1602323871" value="guest" id="guest" />
Could I somehow pass an array with this content and loop through it.
Media, media
Guest, guest
Yeah, You can pass anything to components. Just a try to the radio-buttons
//Basic radio Component
App.RadioButton = Ember.Component.extend({
tagName : "input",
type : "radio",
attributeBindings : [ "name", "type", "value", "checked:checked" ],
click : function() {
this.set("selection", this.$().val());
},
checked : function() {
return this.get("value") === this.get("selection");
}.property('selection')
});
Em.Handlebars.helper('radio-button',App.RadioButton);
Updated (component name should be dasherized)
Working Radio Demo
It's now a tiny bit easier to get radio-buttons into your project (if you're using ember-cli) by using the ember-radio-buttons addon
Install:
npm install ember-radio-buttons --save-dev
Usage:
{{radio-button value='one' checked=selectedNumber}}
{{radio-button value='two' checked=selectedNumber}}
Upped #selva-G's answer.
I found that using the ButtonGroup Component from Bootstrap-for-Ember is actually cleaner.
Here's what I've done:
In my view's template:
<script type="text/x-handlebars" data-template-name="myview">
{{bs-btn-group contentBinding="things" selectedBinding="selectedThing"}}
</script>
In that view's controller (which doesn't necessarily need to be an ArrayController, rather can be a generic Ember Controller):
App.MyviewController = Ember.ArrayController.extend({
things: [
Ember.Object.create({value: 'first', title: 'First Thing'}),
Ember.Object.create({value: 'second', title: 'Second Thing'}),
Ember.Object.create({value: 'third', title: 'Third Thing'})
],
selectedThing: 'second'
selection: function() {
console.log(this.get('selectedThing');
}.observes('selectedThing')
});