How do I create a component that generates Radio-buttons in Ember.js? - ember.js

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')
});

Related

Ember Power Select - Label / Value

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

EmberJS computed sort property conflicting with jQuery UI Sortable

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.

How to combine Layout for Ember Components with input Elements

I ran into this issue by updating from EmberJS 1.7 to 1.8:
https://github.com/emberjs/ember.js/issues/9461#issuecomment-61369680
[Update: the example jsbins can be found in the link above. I've to earn enough reputation to be allowed to post more than two links. I am sorry for the inconvenience!]
In EmberJS it seems not to be possible to combine a component with tagName 'input' and a layout.
Now I have an input element and a graphical representation sitting next to each other like:
<svg ...>
<input type="radio"...>
The image content is driven by a CSS rule which depends on the radio button state (yes, I want to style my own radio button).
My (propably very naive) components-template to achieve the rendered output:
<script type="text/x-handlebars" id="components/radio-button">
<svg><circle cx="50%" cy="50%" r="8" /></svg>
{{yield}}
</script>
[Update: Added component code]
And the component code:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return {'radio': 0};
},
});
App.RadioButtonComponent = Ember.Component.extend({
tagName: 'input',
attributeBindings: [
'type',
'checked',
'disabled',
'tabindex',
'name',
'autofocus',
'required',
'form',
'value'
],
type: 'radio',
checked: false,
disabled: false,
indeterminate: false,
init: function() {
this._super();
this.on('change', this, this._updateElementValue);
var name = this.get('name'),
controller = this.get('radioController'),
checked = controller.get('model.%#'.fmt(name)) === this.get('value');
this.set('checked', checked);
},
group: "radio_button",
classNames: ['radio-button'],
_updateElementValue: function() {
var name = this.get('name'),
controller = this.get('radioController');
controller.set('model.%#'.fmt(name), this.get('value'));
}
});
But with EmberJS 1.8 my component [Update: added code]
<script type="text/x-handlebars">
{{radio-button name="radio" radioController=this value=0}}
</script>
now gets rendered like:
<input type="radio"...><svg ...></input>
I'am now puzzled by how to keep attribute bindings for input elements and use layouts with components in EmberJS.
Here is an solution for adding a layout to an input element:
http://emberjs.jsbin.com/nihacacebe/1/edit?html,js,output
The trick simply is to use a 'proxy' component:
<script type="text/x-handlebars" id="components/radio-button">
{{radio-button-input name=name radioController=radioController value=value}}
<i>{{name}}</i>
{{yield}}
</script>
This component allows to add some layout to the input element in the hbs and forwards everything to the 'real' component:
App.RadioButtonInputComponent = Ember.Component.extend({
tagName: 'input',
type: 'radio',
/* Add your code here... */
});
The proxy component is then used like a real component:
<script type="text/x-handlebars">
{{radio-button name="radio" radioController=this value=0}}
</script>
This solution was inspired by the work of Yapp Labs Ember-Cli Add On:
https://github.com/yapplabs/ember-radio-button
I still have a hard time to understand why using layout is not permitted for input elements in components.

Trigger an action on the change event with Ember.js checkbox input helper?

How can I fire a named action upon changing a checkbox in Ember.js? Any help will be greatly appreciated.
Here is what I have. Checking or unchecking the checkbox has no effect.
Template:
{{input type="checkbox" on="change" action="applyFilter"}}
Controller:
actions: {
applyFilter: function() {
console.log("applyFilter");
}
}
I'd like to post an update to this. In Ember 1.13.3+, you can use the following:
<input type="checkbox"
checked={{isChecked}}
onclick={{action "foo" value="target.checked"}} />
link to source
using an observer seems like the easiest way to watch a checkbox changing
Template
{{input type='checkbox' checked=foo}}
Code
foo:undefined,
watchFoo: function(){
console.log('foo changed');
}.observes('foo')
Example
http://emberjs.jsbin.com/kiyevomo/1/edit
Or you could create your own implementation of the checkbox that sends an action
Custom Checkbox
App.CoolCheck = Ember.Checkbox.extend({
hookup: function(){
var action = this.get('action');
if(action){
this.on('change', this, this.sendHookup);
}
}.on('init'),
sendHookup: function(ev){
var action = this.get('action'),
controller = this.get('controller');
controller.send(action, this.$().prop('checked'));
},
cleanup: function(){
this.off('change', this, this.sendHookup);
}.on('willDestroyElement')
});
Custom View
{{view App.CoolCheck action='cow' checked=foo}}
Example
http://emberjs.jsbin.com/kiyevomo/6/edit
Post Ember version >= 1.13 see Kori John Roys' answer.
This is for Ember versions before 1.13
This is a bug in ember's {{input type=checkbox}} helper.
see https://github.com/emberjs/ember.js/issues/5433
I like the idea of having a stand-in. #Kingpin2k's solution works, but accessing views globally is deprecated and using observers isn't great.
In the linked github ember issue, rwjblue suggests a component version:
App.BetterCheckboxComponent = Ember.Component.extend({
attributeBindings: ['type', 'value', 'checked', 'disabled'],
tagName: 'input',
type: 'checkbox',
checked: false,
disabled: false,
_updateElementValue: function() {
this.set('checked', this.$().prop('checked'));
}.on('didInsertElement'),
change: function(event){
this._updateElementValue();
this.sendAction('action', this.get('value'), this.get('checked'));
},
});
Example usage in a template ('checked' and 'disabled' are optional):
{{better-checkbox name=model.name checked=model.checked value=model.value disabled=model.disabled}}
For those using Ember > 2.x:
{{input
change=(action 'doSomething')
type='checkbox'}}
and the action:
actions: {
doSomething() {
console.warn('Done it!');
}
}
In Ember v1.13 it can be done simply by creating a component named j-check in my occasion(no layout content required):
import Ember from 'ember';
export default Ember.Checkbox.extend({
change(){
this._super(...arguments);
this.get('controller').send(this.get('action'));
}
});
And then you just call it from your template like this:
{{j-check checked=isOnline action="refreshModel" }}

Ember.js - Code is working with 0.9.5 but not 1.0.pre

I try to figure out why the following fiddle doesn't work with ember.js 1.0.pre while 0.9.5 works.
Version 0.9.5 (working)
http://jsfiddle.net/tPfNQ/1/
Version 1.0.pre (not working)
http://jsfiddle.net/hSzrZ/1/
I know that handlebars.js is not included in the latest build of ember.js and i have to include it by my own.
Here is the code i'm using:
<script type="text/x-handlebars">
{{#view Ember.Button target="Welcome.booksController" action="loadBooks"}}
Load Books
{{/view}}
{{#collection contentBinding="Welcome.booksController"}}
<i>Genre: {{content.genre}}</i>
{{/collection}}
</script>
Welcome = Ember.Application.create();
Welcome.Book = Ember.Object.extend({
title: '',
author: '',
genre: ''
});
var data = [ { "title": "Ready Player One", "author": "Ernest Cline", "genre": "Science Fiction" }, { "title": "Starship Troopers", "author": "Robert Heinlein", "genre": "Science Fiction" }, { "title": "Delivering Happiness", "author": "Tony Hsieh", "genre": "Business" } ];
Welcome.booksController = Ember.ArrayController.create({
content: [],
loadBooks: function(){
var self = this;
data.forEach(function(item){
self.pushObject(Welcome.Book.create(item));
});
}
});​
Source is: http://www.andymatthews.net/read/2012/03/07/Getting-Started-With-EmberJS
This is the most common issue people have with upgrading to 1.0.
The default view context has now changed to be the context of the view rather than the view itself.
So to access a value from the view's content you need to specify it via view.content.xxx.
{{#collection contentBinding="Welcome.booksController"}}
<i>Genre: {{view.content.genre}}</i>
{{/collection}}
In this particular case you could also use the #each helper if you wanted.
{{#each Welcome.booksController}}
<i>Genre: {{genre}}</i>
{{/each}}
I think there is talk of changing the #collection helper to work similarly.