Using the Ember addon ember-bootstrap I can make a set of radio buttons like this :
{{form.element controlType="radio" label="Fruit Type" property="radio" options=radioOptions optionLabelPath="label"}}
with a Controller that looks like this :
export default Controller.extend({
init() {
this._super(...arguments);
this.radioOptions = [
{
label: 'Citrus',
value: 'C',
inline: true
},
{
label: 'Non-Citrus',
value: 'N',
inline: true
}
];
}
});
The relevant doco is here https://www.ember-bootstrap.com/#/components/forms .
However what I can't do is provide a custom value to each radio button so that I end up with rendered HTML like this :
<label>Citrus</label>
<input type="radio" value="C">
<label>Non-Citrus</label>
<input type="radio" value="N">
I have looked at "Custom Controls" on https://www.ember-bootstrap.com/#/components/forms but I can't see how that applies to this case.
EDIT: Just to be clearer about why I want to do this I want to display the readable label (eg "Citrus") but have the non-readable value ("C") available to send back to the server (because the server thinks in terms of "C" or "N".
It's not essential I could send "Citrus" back and map it around on the server but I just thought this would be very straightforward.
Looking at the part of the doco starting with "You can also just customize the existing control component:" on https://www.ember-bootstrap.com/#/components/forms it does seem like you should be able to do the sort of thing I'm after but the example shown doesn't address the use of a value attribute and I can't figure out how to .
You don't need to have the HTML rendered like that. if you want to access the checked radio, simply it is the property name dot value like radio.value.
Here how to get it in the on submit action:
actions: {
onSubmit() {
alert(this.radio.value)
}
}
I had exactly the same issue but I finally solved it. You have to use form.element in block mode. Why is it also necessary to also write an action to update the value? I have no idea!
In my implementation, I'm using Ember changesets and my property is called usageType. I hope it's clear enough to adapt for your needs.
I'm also using Ember Truth Helpers, which is what the {{eq opt.value el.value}} part is. It sets checked to true if the input value is equal to the current-selected value.
# my-component.js
actions: {
selectUsageType(option) {
return this.set('changeset.usageType', option);
}
}
# usage-type-options.js (imported in component JS)
[{
label: 'First label',
value: 'A'
},
{
label: 'Second label',
value: 'B'
}]
# Etc.
# my-component.hbs
# I'm not using angle-bracket invovation here, oops
{{#form.element
property="usageType"
label="My label" as |el|
}}
{{#each usageTypeOptions as |opt|}}
<div class="form-check">
<input
type="radio"
class="form-check-input"
id="{{el.id}}-{{opt.value}}"
checked={{eq opt.value el.value}}
onchange={{action "selectUsageType" opt.value}}
>
<label for="{{el.id}}-{{opt.value}}" class="form-check-label">
{{opt.label}}
</label>
</div>
{{/each}}
{{/form.element}}
Related
I am learning ember js from last couple of weeks, and building an application to learn it. I am in a situation where I have to build a dynamic form which will be bind to ember model. (the simplest example for this problem could be nested form, where we can click on add more link/button to add form on the fly, and add values to them).
But for me, I am building survey like site, where we can have lots of option to select and user can select one of the option from available one:
what I have done so far?
readAnswer: Ember.computed(function() {
return this.get('store').query('answer', { filter:
{
question_id: this.get('question.id'),
submission_id: this.get('submission.id')
}
})
}),
buildAnswer: Ember.computed(function() {
this.get('store').createRecord('answer', {
question: this.get('question'),
submission: this.get('submission')
})
}),
answer: Ember.computed(function() {
const ans = this.get('readAnswer');
console.log(`question_id: ${this.get('question.id')}, submission_id: ${this.get('submission.id')}`);
if(Ember.isEmpty(ans)) {
return this.get('buildAnswer');
} else {
return ans;
}
})
answer.hbs
<div class="row col-sm-12">
<div class="form-group">
<label>{{model.title}}</label>
<p>
{{input type="text" value=answer.field01 class="form-control" placeholder="width"}}
</p>
<p>
{{input type="text" value=answer.field02 class="form-control" placeholder="depth"}}
</p>
</div>
</div>
NOTE here answer.hbs is a component, and these are call recursively (in loop) from parent route. So for 2 questions, we can have 4 textboxes, 2 text box for each question, first textbox for answer.field01 and second textbox for answer.field02
Let's say I have 2 questions, till now, I can see 2 answers build in the ember store if they don't already exists in database and then, I can see 4 textboxes generated in view. But they are not binding. Meaning, if I can value of the textbox, nothing happens in the ember store.
Expected Result
When I input answer in the textbox, it should bind with answer.fieldxx properly.
I extracted those codes from computed property to init() function and everything works now:
answer: null,
init() {
this._super(...arguments);
// build without computed property
this.get('store').query('answer', { filter:
{
question_id : this.get('question.id'),
submission_id : this.get('submission.id')
}
}).then((answers) => {
if(Ember.isEmpty(answers)) {
let a = this.get('store').createRecord('answer', {
question : this.get('question'),
submission : this.get('submission')
})
this.set('answer', a); // build new object and set answer
} else {
this.set('answer', answers.get('firstObject')); // get first answer and build it (because it is always 1 record)
}
}, (reason) => {
console.log(reason);
});
},
I want to have a search field inside a component that can be placed anywhere in the app. It can appear on any template, or nested in components. The search form would accept user input (search term) and submit would trigger a search action which transitions to a results template.
Seems simple enough, but I can't figure out how to make an action globally available. And if I could, how do you pass the inputted term to the action in the first place? There's surprisingly little info on how to handle form submits with Ember CLI.
Thus far I've just been submitting a regular form with action='/results'. But that's obviously reloading the app.
I've been messing with creating an action in the index controller like this:
export default Ember.Controller.extend(defaultParams, {
term: '',
actions: {
keywordSearch() {
this.transitionToRoute('results', { queryParams: { q: this.get('term') }});
}
}
});
Then passing a closure action down to my search component, which is nested 2 deep from the index template.
index.hbs:
{{index-search keywordSearch=(action "keywordSearch")}}
index-search.hbs (component):
{{search-field keywordSearch=keywordSearch }}
search-field.hbs (nested component):
<form {{ action (action keywordSearch) on='submit' }}>
{{ input value=term }}
<button type="submit">Search</button>
</form>
And that will run the action, but the term is not supplied. How do you supply term to the closure action?
And...do I really need to pass the action down to every single place the search field is going to appear in the app, or is there an easier way to do it?
Instead of writing actions in all components and routes, you can create a service for search. Inject the service into the component and handle the route transition from service method. Check the sample code below,
Search-component.hbs
<form {{ action (action search) on='submit' }}>
{{ input value=keyword }}
<button type="submit">Search</button>
</form>
Search-component.js
export default Ember.Component.extend({
globalSearch: Ember.inject.service('search'),
actions: {
search() {
const { keyword } = this.getProperties('keyword');
this.get('globalSearch').showResults(keyword).then(() => {
alert('Success');
}, (err) => {
alert('Error while searching: ' + err.responseText);
});
}
}
});
Service - app/services/search.js
import Ember from 'ember';
export default Ember.Service.extend({
init() {
this._super(...arguments);
},
showResults(keyword) {
// write code for transition to search results route here
}
});
So I thought I had this solved already as it worked with my prototype using just arrays, but now that I'm actually dealing with the model, I'm not sure if it will work.
If I create a property on my controller like so:
myAttributes: [{
name: 'attr1',
label: 'Attribute 1',
value: null
}, {
name: 'attr2',
label: 'Attribute 2',
value: 1
}]
then I can loop through myAttributes with {{#each}}, so while each object is essentially a field, that's different than the fields on a single model, which only have a value, so I can't do {{#each model as |rec|}} when there's only one record.
In a nutshell, I want to have a button group to set the value for each field in my model, like so:
I have around 60 of these fields so that's why I wanted to use {{#each}} and my component for each field, but of course each goes over records in the model, not fields in a record.
Is this impossible to do? Do I just have to bite the bullet and write out the markup for each field like I would do if I had only a few fields?
Update: Even if I could loop through the fields on a single record (maybe with {{#each model.#each as |field|}}?), for this case what I also need to do is break out the fields into sections in the UI, so for example loop through fields 1-10 in the first section, and 11-20 in the next section, and there doesn't seem to be a good way to do that.
In the end, I think I'm better off just using a component on each field, like so:
{{attribute-component value=model.attr1}}
{{attribute-component value=model.attr2}}
.
.
.
There's a neat helper called each-in that iterates over key/attributes of an object.
https://guides.emberjs.com/v2.6.0/templates/displaying-the-keys-in-an-object/
There is a way to do that in your case, but I think that actually a simpler way to get the same effect is this:
template.hbs
{{#each myArrayOfPeople as | person | }}
{{#each attribs as | anAttrib | }}
{{get person (mut anAttrib)}},
{{/each}}
<br>
{{/each}}
component.js
attribKeys:['name', 'phoneNumber', 'otherAttrib'];
This will output something like:
joe, 123, something,
sam, 456, anotherthing,
sarah, 944, foo
you can use that same (mut anAttrib) helper to bind an attribute to an input or whatever your need is.
{{attribute-component value=(mut anAttrib)}}
I have now forgone trying to iterate over each field because I actually need to break out the fields into accordion containers based on other criteria, although #averydev's answer about nested {{#each}} was a very cool tip and useful.
I also updated to Ember 1.13 in order to use the mut helper (although I had previously used an action on the component, that passes the new value to an action in the controller, that sets the value to the model property. Since that was convoluted, this new method using mut is much more understandable.
Just to help anyone else out in the future, here's my code (simplified for SO, the real code has nothing to do with rooms and furniture). I have a custom component that uses a radio button group component to actually set the value (this is from ember-radio-button):
template.hbs
{{room-style title='Living Room' field=(mut model.livingRoomStyle)}}
{{room-style title='Master Bedroom' field=(mut model.masterStyle)}}
templates/components/room-style.hbs
<div class="btn-group" data-toggle="buttons">
{{#radio-button value=1 groupValue=field classNames="btn btn-default" changed="changed"}}
Modern
{{/radio-button}}
{{#radio-button value=2 groupValue=field classNames="btn btn-default" changed="changed"}}
French
{{/radio-button}}
{{#radio-button value=3 groupValue=field classNames="btn btn-default" changed="changed"}}
Rustic
{{/radio-button}}
</div>
components/room-style.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
changed: function() {
//update() is a function you get with attrs.[prop] when using the mut helper
this.attrs.field.update(this.get('field'));
}
}
});
I am trying to construct a meteor template for simplifying creating radio buttons on a form. I would like to be able to pass an array or object as an argument through spacebars to the template. How can I pass an array/object as an argument or is this even possible?
Template:
<template name="radioButton">
<div class="mdl-textfield mdl-js-textfield">{{radioLabel}}</div>
{{#each getRadioOptions}}
<label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="{{radioOptionID}}">
<input type="radio" id="{{radioOptionID}}" class="mdl-radio__button" name="{{radioID}}" value="{{optionID}}">
<span class="mdl-radio__label">{{optionLabel}}</span>
</label>
{{/each}}
</template>
Template helper:
Template.radioButton.helpers({
getRadioOptions: function () {
console.log("getRadioOptions called");
console.log(this);
console.log(this.radioOptions);
return this.radioOptions;
},
radioOptionID: function() {
return this.radioID+"-"+this.optionID;
}
});
Attempted spacebar notation:
{{> radioButton radioID="sampleID" radioLabel="Sample Radio Buttons"
radioOptions=[{optionID:"option1",optionLabel:"Option One"},
{optionID:"option2",optionLabel:"Option Two"}] }}
After running this notation and looking at the browser console, I get back this: (which shows that only null was passed for radioOptions)
getRadioOptions called
Object {radioID: "sampleID", radioLabel: "Sample Radio Buttons", radioOptions: null}
null
You almost got it right, except that you can't give the data as a javascript array but need to use a JSON string, i.e., use:
{{> radioButton radioID="sampleID" radioLabel="Sample Radio Buttons"
radioOptions='[{"optionID":"option1", "optionLabel":"Option One"}, {"optionID":"option2","optionLabel":"Option Two"}]' }}
Note that you need to use quotation marks around the field names, too, because it's JSON and not javascript!
Then, in the helper, parse the string:
getRadioOptions: function () {
console.log("getRadioOptions called");
console.log(this.radioOptions); // string
return JSON.parse(this.radioOptions); // array
},
You cannot pass an object in an #each in spacebars. It has to be an Array. This should appear in your console.
Because Meteor include underscore, what you often pass is _.toArray( myObject ).
I'd like to create a rating system for a group of images. I used the css-only method described in "Designing next generation web projects with CSS3" which uses to check a radio input with MyId id. So the inputs can be hidden while the labels are showed as stars (full or empty depending from the "checked" attribute of the input).
The problem is that everything works fine with this input:
<input type="radio" {{bind-attr name="view.customName" value="view.value" id="view.customUuid"}}>
<label class="stars" {{bind-attr for="view.customUuid"}}>{{view.labelText}}</label>
but if I add an action to the input, the action is correctly called, but the input isn't checked anymore:
<input type="radio" {{action 'updateSelectedRating' view view.value}} {{bind-attr name="view.customName" value="view.value" id="view.customUuid"}}>
I also tried to add a checked attr which correctly returns true if the input should be checked, but nothing happens:
<input type="radio" {{action 'updateSelectedRating' view view.value}} {{bind-attr name="view.customName" value="view.value" id="view.customUuid" checked="view.isSelected"}}>
I can't understand if I wrote something really weird (I'm a beginner with Ember) or there is some king of problem with Ember (I bet for the first...).
The reason I used a view is that this is a part of a multifile upload page, so the id of the inputs must be different for every upload and the only way I found was with a view.
The code of the view is:
`import Ember from 'ember'`
RadioButtonView = Ember.View.extend
tagName : ''
attributeBindings : [ "name", "value"]
templateName: 'views/rating-radio-button'
customUuid : (->
return "#{#get('uuid')}-#{#get('value')}"
).property('uuid', 'value')
customName : (->
return "#{#get('uuid')}-#{#get('name')}"
).property('uuid', 'name')
`export default RadioButtonView`
The template of the view:
<input type="radio" {{action 'updateSelectedRating' view view.value}} {{bind-attr name="view.customName" value="view.value" id="view.customUuid" checked="view.isSelected"}}>
<label class="stars" {{bind-attr for="view.customUuid"}}>{{view.labelText}}</label>
And this is the action in the controller:
actions:
updateSelectedRating: (value) ->
#get('model').rating = value
Note that the value of the input is correctly set in the model, so it's only a problem of setting the input as checked.
I also tried using jquery inside the controller or the view, but it doesn't work:
Ember.$(inputId).prop("checked", true)
The same command, from the console of the browser, works great.
Solution by OP.
Thanks to #user1203738 who pointed me to the right direction.
Due to the nature of the view (which contained the radio button and the label) I had to nest two views, where the nested one is a view for the input:
{{view custom-radio-button name=view.customName selectionBinding="view.isSelected" value=view.value id=view.customUuid}}
<label class="stars" {{bind-attr for="view.customUuid"}}>{{view.labelText}}</label>
This is the custom-radio-button:
`import Ember from 'ember'`
CustomRadioButtonView = Ember.View.extend
tagName : 'input'
type: "radio"
attributeBindings : [ "name", "type", "value", "checked:checked:"]
click : ->
#set("selection", this.$().val())
checked : (->
#get("value") == this.get("selection")
).property()
`export default CustomRadioButtonView`
and this is the first view, edited to set the isSelected flag:
`import Ember from 'ember'`
RadioButtonView = Ember.View.extend
tagName : ''
attributeBindings : [ "name", "value"]
templateName: 'views/rating-radio-button'
isSelected: 0
updateIsSelected: (->
#get('controller').set('selectedRating', #isSelected)
).observes('isSelected')
customUuid : (->
return "#{#get('uuid')}-#{#get('value')}"
).property('uuid', 'value')
customName : (->
return "#{#get('uuid')}-#{#get('name')}"
).property('uuid', 'name')
`export default RadioButtonView`