I have a list of checkboxes and I can only allow the user to pick two. I then need the value of those selected checkboxes added to an array. I also need the ability to remove those values if the user dis-selects its corresponding checkbox.
I've read this question How to push/pop arrays in Ember.js? and Its probably on the right track, but not quite what I need.
I have:
<label {{bindAttr class='checked:active'}}>
{{view Ember.Checkbox valueBinding='id' }}
Select
</label>
I also need the "active" class to be applied when its checked.
Ideas?
This might give you an idea on how to proceed.
Here is a working demo.
Here's the relevant code. You might want to separate this out as a component for reusability.
App.IndexController = Em.ArrayController.extend({
selectedOptions: [],
optionsSelected: function() {
console.log(this.get('selectedOptions').toArray());
}.observes('selectedOptions.[]')
});
App.DeveloperController = Ember.ObjectController.extend({
isChecked: false,
resetFlag: false,
_isCheckedChanged: function(){
if(!this.get('resetFlag')) {
var isChecked = this.get('isChecked');
if(isChecked) {
if(this.get('parentController.selectedOptions').length >= 2) {
this.set('resetFlag', true);
this.set('isChecked', false);
} else {
this.get('parentController.selectedOptions')
.addObject(this.get('content'));
}
} else {
this.get('parentController.selectedOptions')
.removeObject(this.get('content'));
}
} else {
this.set('resetFlag', false);
}
}.observes('isChecked')
});
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each item in model itemController="developer"}}
<label {{bindAttr class='checked:active'}}>
{{view Ember.Checkbox checkedBinding="isChecked"}}
{{item.content}}
</label>
{{/each}}
</ul>
</script>
Here is a working solution, based on Ember Components.
It doesn't handle all edge cases, e.g. bound values that change after the component has rendered, but it does the basics. Can easily be extended if needed.
The array can be constructed using Ember.A(), in order for it to have addObject etc.
Component:
App.ArrayCheckboxComponent = Em.Component.extend({
value: null # included in array when checked
array: null # array to bind to
_checked: null # boolean
init: ->
#_super.apply(this, arguments)
array = #get('array')
unless array.addObject && array.removeObject && array.contains
throw new Error('Array used for `ArrayCheckboxComponent` must implement addObject/removeObject')
if array.contains(#get('value'))
#set('_checked', true)
else
#set('_checked', false)
checkedDidChange: (->
value = #get('value')
array = #get('array')
if #get('_checked')
array.addObject(value)
else
array.removeObject(value)
).observes('_checked')
})
Component template:
{{view Em.Checkbox checkedBinding='_checked'}}
Usage:
{{array-checkbox value='item1' arrayBinding='path.to.array'}}
More on components:
http://emberjs.com/guides/components/
Related
Have an interesting problem in Ember js.
Here below is the component advanced-search/component.js with two computed properties roomType & room dependent on the service services/advanced-search queryObj.{roomId,roomTypeId} object properties.
The roomType computed property fires and updates the template correctly when an option is selected from the template. However, interestingly, the room computed property fails to fire when a room is selected from the template. This I verified by putting a console.log('checking room computed fired') inside the room computed property.
To explore this anomaly further, I did the following:
I uncommented the code you see in the init method that sets the rooms array which populates the room list dropdown and commented the code in the actions hash inside the setRoomType action method that was initially setting the rooms array. After these changes, the room computed property fires correctly and updates the template.
I noticed the array returned by the this.get('store).findAll('roomType') resulted in the roomType computed property to fire correctly and update the template, so I attempted to change the call for rooms inside setRoomType from roomType.get('rooms') to this.get('store') to see if it also resulted in the room computed property to fire correctly but it still did NOT fire the room computed property. So, I concluded that both the roomType and room computed properties could only fire and update the template correctly if their dropdown list arrays were set in the component's init method.
advanced-search/component.js
export default Ember.Component.extend({
advancedSearch: Ember.inject.service('advanced-search'),
queryObj: Ember.computed.alias('advancedSearch.queryObj'),
init() {
this._super(...arguments);
// I believe because the 'roomTypes` array is set in this init method, the 'roomType' computed property fires correctly and updates the template
this.get('store').findAll('roomtype').then((roomTypes) => {
this.set('roomTypes', roomTypes);
});
// When the 'rooms' array is also initialized here, the 'room' computed property fires successfully on 'room' selection from the dropdown and updates the template
// this.get('store').findAll('room').then((rooms) => {
// this.set('rooms', rooms);
// });
},
roomTypes: [],
roomType: Ember.computed('queryObj.{roomTypeId}', function() {
var that = this;
return this.get('roomTypes').find(function(roomType) {
return that.get('queryObj').roomTypeId === roomType.id;
});
}),
rooms: [],
room: Ember.computed('queryObj.{roomId}', function() {
console.log('checking room computed fired')
var that = this;
return this.get('rooms').find(function(room) {
return that.get('queryObj').roomId === room.id;
});
}),
actions: {
setRoomType(roomType) {
this.get('advancedSearch').setRoomType(roomType);
this.set('room', null);
if (roomType) {
// When rooms array initialized from here the room computed property fails to fire on room selection from drop down
roomType.get('rooms').then((rooms) => {
this.set('rooms', rooms);
});
} else {
this.set('rooms', null);
}
},
setRoom(room) {
this.get('advancedSearch').setRoom(room);
}
}});
Here below is the service code:
services/advanced-search
export default Ember.Service.extend({
queryObj: {},
setRoomType(roomType) {
this.set('queryObj.roomTypeId', roomType.id);
},
setRoom(room) {
this.set('queryObj.roomId', room.id);
}});
Here below is the component template:
advanced-search/template.hbs
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3>Advanced Search</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-md-2">
{{#power-select placeholder="Room type" allowClear=true selected=roomType options=roomTypes onchange=(action "setRoomType") as |roomType| }} {{roomType.name}} {{/power-select}}
</div>
<div class="col-md-2">
{{#power-select placeholder="Room No" allowClear=true selected=room options=rooms onchange=(action "setRoom") as |room| }} {{room.name}} {{/power-select}}
</div>
</div>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
NB: My desire is for the 'rooms' property to be set from the action method 'setRoomType' through the 'roomType' model's relationship property called 'rooms' so that the rooms are always filtered according to the 'roomType' selected.
Your help will be greatly appreciated.
Ember Version:
Ember Inspector 2.0.4
Ember 2.8.2
Ember Data 2.9.0
jQuery 3.1.1
Ember Simple Auth 1.1.0
You should not set the computed properties like this.set('room', null); This line of code is the culprit i guess.Once you set computed properties like this then this computed property will not fire again.
What you might need is room computed property with get and set. refer setting computed properties guide
test:Ember.computed('dependantKey',{
get(key){
return 'result';
},
set(key,value){
return value;
}
})
I have a list of checkboxes for people, and I need to trigger an event that will display information about each person selected in another area of the view. I am getting the event to run in my controller and updating the array of staff information. However, the view is not updated with this information. I think this is probably some kind of scope issue, but cannot find anything that works. I have tried adding a $watch, my code seems to think that is already running. I have also tried adding a directive, but nothing in there seems to make this work any better. I am very, very new to Angular and do not know where to look for help on this.
My view includes the following:
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<fieldset>
<p data-ng-repeat="person in staffCtrl.persons">
<input type="checkbox" name="selectedPersons" value="{{ physician.StaffNumber }}" data-ng-model="person.isSelected"
data-ng-checked="isSelected(person.StaffNumber)" data-ng-change="staffCtrl.toggleSelection(person.StaffNumber)" />
{{ person.LastName }}, {{ person.FirstName }}<br />
</p>
</fieldset>
</div>
<div data-ng-controller="staffController as staffCtrl">
# of items: <span data-ng-bind="staffCtrl.infoList.length"></span>
<ul>
<li data-ng-repeat="info in staffCtrl.infoList">
<span data-ng-bind="info.staffInfoItem1"></span>
</li>
</ul>
</div>
My controller includes the following:
function getStaffInfo(staffId, date) {
staffService.getStaffInfoById(staffId)
.then(success)
.catch(failed);
function success(data) {
if (!self.infoList.length > 0) {
self.infoList = [];
}
var staffItems = { staffId: staffNumber, info: data };
self.infoList.push(staffItems);
}
function failed(err) {
self.errorMessage = err;
}
}
self.toggleSelection = function toggleSelection(staffId) {
var idx = self.selectedStaff.indexOf(staffId);
// is currently selected
if (idx >= 0) {
self.selectedStaff.splice(idx, 1);
removeInfoForStaff(staffId);
} else {
self.selectedStaff.push(staffId);
getStaffInfo(staffId);
}
};
Thanks in advance!!
In the code you posted, there are two main problems. One in the template, and one in the controller logic.
Your template is the following :
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<!-- ngRepeat where you select the persons -->
</div>
<div data-ng-controller="staffController as staffCtrl">
<!-- ngRepeat where you show persons info -->
</div>
Here, you declared twice the controller, therefore, you have two instances of it. When you select the persons, you are storing the info in the data structures of the first instance. But the part of the view that displays the infos is working with other instances of the data structures, that are undefined or empty. The controller should be declared on a parent element of the two divs.
The second mistake is the following :
if (!self.infoList.length > 0) {
self.infoList = [];
}
You probably meant :
if (!self.infoList) {
self.infoList = [];
}
which could be rewrited as :
self.infoList = self.infoList || [];
I have a page with a form. In this form user can add multiple rows with key and values. There is a restriction that the customFields is created on the fly, not from any subscribed collection.
...html
<template name="main">
{{#each customFields}}
<div>
<input type="text" value="{{key}}"/>
<input type="text" style="width: 300px;" value="{{value}}"/>
</div>
{{/each}}
</template
.... router.js
Router.route 'products.add',
path: '/products/add/:_id'
data:
customFields:[]
....products.js
#using customFieldSet as Reactive Var from meteor package
Template.product.created = ->
#customFieldSet = new ReactiveVar([])
Template.product.rendered = ->
self = this
Tracker.autorun ->
arr = self.customFieldSet.get()
self.data.customFields = arr
Template.product.events(
'click .productForm__addField': (e)->
t = Template.instance()
m = t.customFieldSet.get()
console.log t
m.push(
key: ''
value: ''
)
t.customFieldSet.set m
....
The last event will be trigger when I click the button. And it add another row with key and value empty to the page.
Please advise me why I actually see the reactive variable customFieldSet updated, but there is nothing changed dynamically in html.
P/s: I guess customFields is not updated via Iron router.
Basically, you're doing the thing right. However, you shouldn't be assigning the new reactive data to your template's data context, but rather access it directly from your helpers:
Template.product.helpers({
customFileds: function () {
return Template.instance().customFiledsSet.get();
},
});
Now you can use {{customFields}} in your template code and it should work reactively. Just remember that {{this.customFileds}} or {{./customFileds}} will not work in this case.
I would like to pass a true/false statement to my handlebars
{{Gd-text-input label="Specify" name="Specify" key="entry.810220554" hideIf="entry.18110 === "Client""}}
I would like hideIf to be true if the variable entry.18110 is set to "Client
First, add this somewhere -
Handlebars.registerHelper('ifEqual', function (var1, var2, options) {
if (var1=== var2) {
return new Handlebars.SafeString(options.fn(this));
}
return new Handlebars.SafeString(options.inverse(this));
});
Then..
{{#ifEqual entry.18110 "Client"}}
{{Gd-text-input label="Specify" name="Specify" key="entry.810220554" hideIf="true"}}
{{else}}
{{Gd-text-input label="Specify" name="Specify" key="entry.810220554" hideIf="false"}}
{{/if}}
This is pretty much the only way to do it, as the handlebars team has specifically left most logic out of the templates since it generally doesn't belong there. Which is debatable, as sometimes it makes things more complicated not to allow simple logic. But, this is very workaroundable.
The other answer does not work for Ember Handlebars, for your case you could do something like this.
http://emberjs.jsbin.com/agewuxAT/3/edit
Component
App.HideableCompComponent = Em.Component.extend({
isHidden: function(){
var prop = this.get('hideIfProperty');
if(!prop) return false;
// allow lazy comparison? up to you
return this.get('content').get(prop) == this.get('hideIfValue');
}.property('hideIfProperty', 'hideIfValue')
});
Template
<script type="text/x-handlebars" data-template-name="components/hideable-comp">
{{#unless isHidden}}
I am a component I am not hidden!
{{else}}
I am hidden
{{/unless}}
</script>
Usage
{{hideable-comp content=model hideIfProperty='length' hideIfValue=3}}
I've got a setup like this in Ember:
App.ListObject = Ember.Object.create({
knownThings: function() {
var ot = this.openThings.get('content');
var ct = this.closedThings.get('content');
var kt = ot.concat(ct);
var known = Ember.ArrayController.create({content: kt});
return known;
}.property(),
openThings: Ember.ArrayController.create({
content: []
}),
closedThings: Ember.ArrayController.create({
content: []
}),
})
Basically, known things is the combined arrays of openThings and closedThings. I can't seem to figure out how to iterate over knownThings in the template. Just doing
{{#each App.ListObject.knownThings }}
Does not work as the property needs to be accessed like App.ListObject.get('knownThings') but that doesn't work in the template unless I'm doing something terribly wrong. Iterating over the other attributes in the template does work (open and closed things)
So, how would you iterate over knownThings in the template?
Slight Modifications needed...
Firstly,
knownThings: function() {
//use get to retrieve properties in ember, Always !
var ot = this.get('openThings').get('content');
//var ot = this.get('openThings.content') if you are using latest ember
var ct = this.get('closedThings').get('content');
//var ot = this.get('closedThings.content') if you are using latest ember
var kt = ot.concat(ct);
var known = Ember.ArrayController.create({content: kt});
return known;
//Add dependencies to keep your knownThings in sync with openThings & closedThings if at all they change in future
}.property('openThings', 'closedThings')
Coming to Handlebars iterate using
//you forgot content property, and in handlebars you don;t need to use get, dot operator is enough
{{#each App.List.knownThings}}
Let me know if this works...
Update
Working Fiddle...
Unless I didn't understand what you're saying, I think you should have ListObject extending Em.ArrayController instead of Em.Object. Also, if your property depends on content, it should be .property('content.#each'). If you're using the router, your template should look like {{#each thing in controller.knownThings}} and you use {{thin.something}}, if not using router, then {{#each item in App.listObject.knownThings}}. Also, openThings and closedThings don't seem to be correct and the way you're accessing them is wrong too.
I didn't write a fiddle for this specific case cause I don't really know what you're trying to do, but take a look at this fiddle, specifically at App.ResourcesController and the template 'resources-view':
Controller:
// ...
App.ResourcesController = Em.ArrayController.extend({
content: [],
categories: ['All', 'Handlebars', 'Ember', 'Ember Data', 'Bootstrap', 'Other'],
categorySelected: 'All',
filtered: function() {
if(this.get('categorySelected') == "All") {
return this.get('content');
} else {
return this.get("content")
.filterProperty(
"category",
this.get('categorySelected')
);
}
}.property('content.#each', 'categorySelected'),
filteredCount: function() {
return this.get('filtered').length;
}.property('content.#each', 'categorySelected'),
hasItems: function() {
return this.get('filtered').length > 0;
}.property('filteredCount')
);
// ...
Template:
<script type="text/x-handlebars" data-template-name="resources-view">
<h1>Ember Resources</h1>
{{#view Bootstrap.Well}}
The following is a list of links to Articles, Blogs, Examples and other types of resources about Ember.js and its eco-system.
{{/view }}
{{view Bootstrap.Pills contentBinding="controller.controllers.resourcesController.categories" selectionBinding="controller.controllers.resourcesController.categorySelected"}}
<i>{{controller.filteredCount}} Item(s) Found</i>
{{#if controller.hasItems}}
<ul>
{{#each resource in controller.filtered}}
<li>
<a {{bindAttr href="resource.url"
target="resource.target"
title="resource.description"}}>
{{resource.htmlText}}
</a>
</li>
{{/each}}
</ul>
{{else}}
{{#view Bootstrap.AlertMessage type="warning"}}
Couldn't find items for {{controller.categorySelected}}
{{/view}}
{{/if}}
</script>