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`
Related
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}}
I'm using the ng-bootstrap typeahead component to search a customer database. When the user selects a customer from the typeahead results list, I navigate to a customer details page. I've got this working, but I want to clear the input field after navigation has taken place. I've tried setting the model to null or an empty string in the selectItem event logic, but this isn't working:
customer-search-typeahead.component.html
<template #resultTemplate let-r="result" let-t="term">
<div>
<div>
{{r.resource.name[0].given}} {{r.resource.name[0].family}}
</div>
<div>
{{r.resource.birthDate | date: 'dd/MM/yyyy'}}
</div>
</div>
</template>
<input type="text" class="form-control" [resultTemplate]="resultTemplate" (selectItem)="onSelect($event)"
[(ngModel)]="model" placeholder="Start typing a customer name..." [ngbTypeahead]="search"/>
customer-search-typeahead.component.ts
#Component({
selector: 'customer-search-typeahead',
template: require('./customer-search-typeahead.component.html'),
styles: [`.form-control { width: 300px; }`]
})
export class CustomerSearchTypeaheadComponent {
model: any;
searching: boolean;
constructor(private customerService: CustomerService, private router: Router) {}
onSelect($event) {
this.router.navigate(['/customers', $event.item.resource.id]);
this.model = null;
};
search = (text$: Observable<string>) =>
//omitted for brevity
}
The typeahead input looks like this after a selection has been made:
Solution
customer-search-typeahead.component.html
<input type="text" class="form-control" #input [ngbTypeahead]="search" (selectItem)="onSelect($event); input.value='' ">
customer-search-typeahead.component.ts
onSelect($event, input) {
$event.preventDefault();
this.router.navigate(['/customers', $event.item.resource.id]);
};
The issue you witnessing arises from the fact that the NgModel directive is updating model binding asynchronously and the actual model is updated after the onSelect method gets executed. So your model update gets overridden by the NgModel functionality.
Fortunately we (ng-bootstrap authors) got all the flex points in place to cover your use-case :-) There are a couple of things that you could do.
Firstly the $event object passed to the onSelect method has the preventDefault() method and you can call it to veto item selection (and as a result writing back to the model and input field update).
$event.preventDefault() will make sure that the model is not updated and the input field is not updated with the selected item. But text entered by a user will still be part of the input so if you want to clear up this as well you can directly update the input's value property.
Here is code demonstrating all those techniques together:
onSelect($event, input) {
$event.preventDefault();
this.selected.push($event.item);
input.value = '';
}
where input argument is a reference to the input DOM element:
<input type="text" class="form-control" #input
[ngbTypeahead]="search" (selectItem)="onSelect($event, input)">
Finally here is a plunker showing all this in practice: http://plnkr.co/edit/kD5AmZyYEhJO0QQISgbM?p=preview
The above one is template ref value solution.
This is for ngModel solution.
Html code:
<input type="text" class="form-control" [resultTemplate]="resultTemplate" (selectItem)="onSelect($event)"
[(ngModel)]="model" placeholder="Start typing a customer name..." [ngbTypeahead]="search"/>
Component code:
onSelect($event) {
$event.preventDefault();
this.model = null;
this.router.navigate(['/customers', $event.item.resource.id]);
};
$event.preventDefault();
for ngModel value change empty
In an indexview I have links that set the sorting:
# Template
{{#link-to 'products' (query-params sortBy="title")}}Title{{/link-to}}
{{#link-to 'products' (query-params sortBy="price")}}Price{{/link-to}}
# Controller
queryParams: ['sortBy']
sortBy: 'id'
sortProperties: ( ->
[#get("sortBy")]
).property("sortBy")
That generates links that always have the class of 'active', but I want to highlight the currently active sort filter. What is the best way to do it?
I tried binding to a computed property like this:
{{#link-to 'products' (query-params sortBy="price") classNameBindings='sortByPrice'}}Price{{/link-to}}
sortByPrice: -> (
#get('sortBy') == 'price'
).property('sortBy')
That didn't quite work, but even if it did, that's not DRY at all – and eventually I would like to add a lot of different attributes on which to sort.
As I understand, the problem is that ember adds the 'active' class when it's in the context of that controller, which it always is with different query-params.
(Running the latest canary build of Ember as of 14th June)
This has been fixed in Ember Canary, as of https://github.com/emberjs/ember.js/pull/5109
QueryParams should add the "active" class based on whether the declared parameter in the {{#link-to}} helper has the same value as the attribute at that moment, as I can demonstrate in this jsbin.
That said, I'm having the same problem, so I believe there's some fringe case where this doesn't work right, and I'd be happy if you could modify this example to reflect that.
I'm facing the same problem now and I have temporary solution.
<!-- Posts Template -->
<!-- Categories -->
<div class="block step visible-desktop visible-tablet">
<div class="header">
<h3>Category</h3>
</div>
<div class="area categories">
<ul>
{{#each staticCategory in controller.staticCategories}}
{{post-category currentCategory=currentCategory staticCategory=staticCategory}}
{{/each}}
</ul>
</div>
</div>
<!-- Categories end -->
//Posts Controller
staticCategories: ['Front-End', 'JavaScript', 'jQuery', 'null'],
currentCategory: function () {
return this.get('category');
}.property('category'),
queryParams: ['category'],
category: null,
filteredContent: function () {
var category = this.get('category');
var posts = this.get('model');
return category ? posts.filterBy('category', category) : posts;
}.property('category', 'model')
//Post-Category Component template
{{#link-to 'posts' (query-params category=staticCategory)}}
{{staticCategory}}
{{/link-to}}
//Post-Category Component js
Blog.PostCategoryComponent = Ember.Component.extend({
tagName: 'li',
isActive: function () {
return this.get('staticCategory') === this.get('currentCategory')
}.property('currentCategory', 'staticCategory'),
classNameBindings: ['isActive:active']
});
I've found a solution for this. Ember (currently) seems to make a distinction between linking to a resource and linking to a sub-route, e.g doing {{link-to "resource"}} will always set the active class, but doing {{link-to "resource.index"}} will toggle the active state according to their query params.
Here's a jsbin showcasing the difference: http://emberjs.jsbin.com/zawukucisoni/3/edit
I've opened an issue that can be found here: https://github.com/emberjs/ember.js/issues/5359
Based on dropdown selected i.e if B is selected the template has to be modified for B.
Template -
<script type="text/x-handlebars" id="search">
...
<div class="controls">
<select class="input-medium focused">
<option>Any</option>
<option>A</option>
<option {{action 'chosenB'}} >B</option>
<option>C</option>
<option>D</option>
</select>
</div>
{{#if searchB}}
//Show view specific to B-only
{{/if}}
</script>
Router -
App.Router = Ember.Router.extend({
this.resource('search');
});
SearchController = Ember.ObjectController.extend({
searchB: false,
chosenB: function() {
this.set('searchB', true);
},
});
So, when I goto URL /search, intially B's only view is not shown as IF condition is not met. But, when a user selects B from dropdown, I have called action chosenB of SearchController from template which should have set the 'searchB" to true and B-only view should have appeared which is not happening..
Can you please help finding mistake here ..
Ember - 1.0.0-RC.2
Ember.js has a built-in Select view, please check out this doc http://emberjs.com/api/classes/Ember.Select.html, I think your problem is binding related, using the right component for the job would have the desired behaviour.
hope it helps
I'm using the latest pre 1.0 of ember.js and wanted to get away from using the deprecated button for simple forms.
I have something that works but I don't feel like this is the correct way to wire up a view that has both a text input and a button that needs access to that text.
Here is the basic view
{{#view PersonApp.AddPersonView}}
{{view Ember.TextField valueBinding="username"}}
{{#with this as username}}
<input type="submit" value="add" {{action addPerson username}}/>
{{/with}}
{{/view}}
Here is the view
PersonApp.AddPersonView = Ember.View.extend({
username: null,
addPerson: function(event) {
var username = event.context.username;
if (username) {
PersonApp.personController.addPerson(username);
this.set('username', ''); //this does not currently work
}
}
});
The only other issue I'm having is that I don't have access to username the usual way. ie - this.get('username') but in addition I can't clear the textbox value (even though it's shown above).
I'm looking to build a modern version of this gist (previous version of ember) https://gist.github.com/1477225
I see three issues here (perhaps there are more). First, username will not be a field in the event.context, but will actually be the event context. Secondly, I believe you need to specify view.username in the valueBinding, otherwise the controller is the default home of the property (I believe). Then, to set it to initial state you need to set it to null. Third, the target of your action will be the router, so you need to specify the view as the target.
This should work:
{{#view PersonApp.AddPersonView}}
{{view Ember.TextField valueBinding="view.username"}}
{{#with this as username}}
<input type="submit" value="add" {{action addPerson username target="this"}}/>
{{/with}}
{{/view}}
PersonApp.AddPersonView = Ember.View.extend({
username: null
addPerson: function(event) {
var username = event.context;
if (username) {
this.get('controller').addPerson(username);
this.set('username', null);
}
}
});
Also, a better way of creating a new person would be to create a blank person model, bind the controller and view to that, and then save the record, afterwards setting the binding back to null.
You can do the validation and then pass data right now, even with Gidrius' code. The only thing you need to do is write the validation code in the submit handling method. Or, 'cause we`re talking client-side validation anyway, you can do it on field value change or blur, which will give the user almost instant feedback on what he is doing.
I still couldn't get something like this.get('username') to work but I ended up with the following
{{#view PersonApp.AddPersonForm}}
{{view Ember.TextField valueBinding="username"}}
<input type="submit" value="add" {{action addPerson this}}/>
{{/view}}
PersonApp.AddPersonForm = Ember.View.extend({
addPerson: function(event) {
var username = event.context.username;
if (username) {
PersonApp.personController.addPerson(username);
event.context.set('username', '');
}
}
});
probably a bit too late, but might be helpful to someone else.
Usually form field value will be bind to controller or model, so all you need is to have is a submit function in the controller so whenever function will be called you will have access to the fields via bindings.
Here is how it all could look like, assuming you are using latest pre.4 ember
Updated
// DOM part
<form {{action submitForm on="submit"}}>
{{view Ember.TextField valueBinding="username"}}
<button type="submit">add</button>
</form>
And here is a controller
PersonApp.PersonController = Ember.ArrayController({
username: '',
submitForm: function() {
var u = this.get('username'); // saving value to variable
this.set('username',''); // sets username to ''
console.log(u); // will output saved username
}
});