How to pass an array of checkboxes to a knockout custom binding? - arrays

i'm using knockout 2.2.1.
I have a set of 3 check boxes to concatenate to get the corresponding values all together:
<fieldset data-role="controlgroup" id="top-colours" data-bind="topColoursLabel: { topColoursRed,topColoursBlue,topColoursGreen }">
<legend>Top Colours:</legend>
<input type="checkbox" name="top-colours-red" data-bind="checked: topColoursRed" id="tc-check-1" />
<label for="tc-check-1">Red stripes</label>
<input type="checkbox" name="top-colours-blue" data-bind="checked: topColoursBlue" id="tc-check-2" />
<label for="tc-check-2">Blue stripes</label>
<input type="checkbox" name="top-colours-green" data-bind="checked: topColoursGreen" id="tc-check-3" />
<label for="tc-check-3">Green stripes</label>
</fieldset>
The result shall be for example: "Red stripes, Blue stripes".
My viewmodel is as follows:
function ColoursViewModel() {
var self = this;
self.template = "coloursView";
self.topColoursRed = ko.observable(false);
self.topColoursBlue = ko.observable(false);
self.topColoursGreen = ko.observable(false);
self.topColoursDescription = ko.observable("");
}
How shall be the custom bindings to achieve this?
I try something like that:
ko.bindingHandlers.topColoursLabel = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.utils.unwrapObservable(valueAccessor());
// ...
var checkText = '...';
viewModel.topColoursDescription(checkText);
}
};
I'm not able to find out how to pass the array to my custom bindings to subscribe to the values of the 3 check boxes, because I'm noob to knockout.
It seems to me, that a declaration like:
data-bind="topColoursLabel: { topColoursRed,topColoursBlue,topColoursGreen }"
would be great to achieve this, but i'm searching the right way to do that.
Note: i cannot use a computed observable here, because i need to get some other properties from element - i mean the label-for text - so a custom binding is needed.
Can someone help?
UPDATED jsFiddle: http://jsfiddle.net/Sx87j/

Actually, custom binding handler is not what you really need. You should implement your self.coloursDescription as computed observable which will track checkbox changes and return currently selected stripes:
self.topColoursDescription = ko.computed(function(){
var colors = [];
if (self.topColoursRed()) colors.push('Red stripes');
if (self.topColoursBlue()) colors.push('Blue stripes');
if (self.topColoursGreen()) colors.push('Green stripes');
return colors.join(', ');
});
Also remove all tracks of your custom bindings from markup and you will get something like this: http://jsfiddle.net/h7Bmb/8/
Update
I can make your updated fiddle to work with top colours. Making it work with bottom colors too looks a bit complicated with your current approach.
Enumerate all linked color observables in your binding:
<fieldset data-role="controlgroup" id="top-colours" data-bind="topColoursLabel: [ topColoursRed, topColoursBlue, topColoursGreen ]">
Change your custom binding code (the line where ko.utils.unwrapObservable is called):
ko.utils.arrayForEach(valueAccessor(), ko.utils.unwrapObservable);
Example: http://jsfiddle.net/Sx87j/1/

Related

How to call a function on uncheck and on check with Aurelia

I have a list of items coming in from an API and they won't always be the same, so the number of items in the array is always changing. I'm creating a checkbox for each item.
The user has the ability to check/uncheck each item. Here's what I want to do:
When an item is checked, it will push the ID of that item into an array
When an item is unchecked, it will remove the ID of that item from the array
I just need to know how I call something based on whether it was checked or unchecked. I've tried a "checked.delegate" and a "checked.trigger" and I can't seem to get that to work.
Just a regular click.delegate won't work because I can't keep state on whether it's true or false and I can't set variables for all of them because I don't always know which items are going to be coming in from the API. Any suggestions?
Try change.delegate or change.trigger like this:
VM method:
logchange(value) {
console.log(value);
}
View:
<input type="checkbox" change.delegate="logchange($event.target.checked)" />
There is (since when?) official documentation of how to solve exactly this specific problem cleanly: https://aurelia.io/docs/binding/checkboxes#array-of-numbers
No need to handle events!
Aurelia can bind directly to your array and handle everything for you - all you need to do is tell Aurelia what property of the elements you are repeating over to store in the array (the id).
The gist of it:
app.js
export class App {
products = [
{ id: 0, name: 'Motherboard' },
{ id: 1, name: 'CPU' },
{ id: 2, name: 'Memory' },
];
selectedProductIds = [];
}
app.html
<template>
<form>
<h4>Products</h4>
<label repeat.for="product of products">
<input type="checkbox" model.bind="product.id" checked.bind="selectedProductIds">
${product.id} - ${product.name}
</label>
<br>
Selected product IDs: ${selectedProductIds}
</form>
</template>
No other code is needed.
One way you can do this is with the help of a setter. Let's say you have a checkbox like this:
<input type="checkbox">
Create a private field in your View Model and then wrap it with a getter and a setter:
get isChecked(){
return this._isChecked;
}
set isChecked(value){
this._isChecked = value;
//enter your extra logic here, no need for an event handler
}
private _isChecked: boolean;
Then bind isChecked to the view:
<input type="checkbox" checked.bind="isChecked">
Every time the checkbox is checked or unchecked the setter will be called and you can call any method you want from within the setter.
Another more unconventional way to achieve this is by using the #bindable decorator like this:
#bindable isChecked: boolean;
It's unconventional because you probably don't want isChecked to be bindable but the decorator gives you access to the isCheckedChanged method:
isCheckedChanged(newValue, oldValue){
//Logic here
}
And of course there is the change event which you can catch with change.trigger and change.delegate but that has already been mentioned in another answer

AngularJS model not updating inside javascript array

I have a simple issue with AngularJS: the properties outside of the array update normally, but the array elements (that are bound to those properties) don't, here is a plunker to show that: http://plnkr.co/edit/gNtGhiTf228G8rP0O2iB?p=preview
var vm = this;
vm.topLine = {
netWrittenPremiumLastFye: 1000000,
netWrittenPremiumYtd: 123000
}
vm.grossColumnChartData = [
['Previous YE', vm.topLine.netWrittenPremiumLastFye],
['YTD', vm.topLine.netWrittenPremiumYtd]
];
We can use $scope.$watch('something', function(oldValue, newValue) {...}) but that's not a good choice in my situation since i'll have to do that many times, repeatedly.
I want to take advantage of angular 2-way binding as explained above.
Any helps or suggestions are appreciated, thanks!
I think the only way you can do this without using $watch is by updating the array directly in the html binding.
{{ grossColumnChartData = [
['Previous YE', vm.topLine.netWrittenPremiumLastFye],
['YTD', vm.topLine.netWrittenPremiumYtd]
] }}
<br/>
grossColumnChartData = {{grossColumnChartData | json}}
See this:
http://plnkr.co/edit/uthXcvoWSV2XNRFJzkck?p=preview
I have updated my plnkr and i have my answer now: http://plnkr.co/edit/gNtGhiTf228G8rP0O2iB?p=preview
<div ng-repeat="item in vm.grossColumnChartData">
<label>Input {{$index+1}}:</label>
<input ng-model="item.data" type="text"/>
</div>
I was confused between data binding and reference.
I used ng-repeat to update the array elements.

Check all boxes toggle method not working like expected

I'm wiriting a method to toggle a checklist as checked / unchecked, as follow:
$scope.checkAllToggle = function(dtProvider, destiny, ev) {
$(ev.currentTarget).parent().find('span').html());
data[destiny] = [];
if ($(ev.currentTarget).parent().find('span').html()=='Check all') {
for (var val in data[dtProvider]) data[destiny].push(data[dtProvider][val].id);
$(ev.currentTarget).parent().find('span').html('Uncheck all');
}
else $(ev.currentTarget).parent().find('span').html('Check all');
}
The label of the toggle button changes every time, but the state of the checkboxs only becomes checked after 2 click, from this point change in each 3 clicks.
What's wrong?
Append
if(!$scope.$$phase) $scope.$apply();
to the method make no difference, as prepending $scope to data
Code in plunker: http://plnkr.co/edit/Zf6UzLbC4osRov7IR5Na?p=preview
Rather than do it that way, this would be the easier way to do it.
Make the master input and assign it to a model.
<input type="checkbox" ng-model="master">
Then have your other inputs be tied to the master like this.
<input type="checkbox" ng-model="box1" ng-checked="master">
<input type="checkbox" ng-model="box2" ng-checked="master">
<input type="checkbox" ng-model="box3" ng-checked="master">
Here is your plunker with these changes.
Master Checkbox Plunker
I got your plunker to work by doing this.
$scope.checkToggle = function(dtProvider, destiny, ev) {
$scope.data[destiny] = [];
if ($(ev.currentTarget).parent().find('span').html() == 'Check all') {
for (var val in $scope.data[dtProvider]){
$scope.data[destiny].push($scope.data[dtProvider][val].id);
$(ev.currentTarget).parent().find('span').html('Uncheck all');
}
} else {
$(ev.currentTarget).parent().find('span').html('Check all');
}
$scope.$apply();
};
I could not get it to work if calling safely like if(!$scope.$$phase){$scope.$apply();} but it is working with straight $scope.$apply(). This might have something to do with rootScope and need to be safetly called in a different manner.
See this link: https://coderwall.com/p/ngisma
You also had some missing {} in your code that could have been causing some issues.

ng repeat not updating

Hi I am a newbie to angular js and I am hoping someone can help me out with the following problem.
I have a numeric field called numAdults and I need to show a set of field (such as name, address, telephone etc ) numAdult times to get those information for each of those person.
Here is the jsfiddle for the problem jsfiddle link
Here is also an overview of code of the controller
function bookingController($scope){
$scope.numAdults = 1;
$scope.personLoop = function(){
console.log('personLoop called')
return new Array($scope.numAdults);
//return new Array(2);
}
the html
<label for="book_num_adults">Number of adults:</label>
<input id="book_num_adults" type="text" ng-model="numAdults">
<div class="row" ng-repeat="t in personLoop()" style="border:2px solid red;margin-top:10px">
<h4>Person {{$index+1}}</h4>
<input placeholder="name"><br>
<input placeholder="address"><br>
<input placeholder="telephone"><br>
</div>
Can you also help me with how to transform this as an module ( not just a controller based )
Thank you in advance!
Your Fiddle was riddled with errors...
http://jsfiddle.net/bRgTR/5/
Under Frameworks & Extensions, you need to change the 2nd dropdown from "onLoad" to one of the "No wrap" options
Your controller definition is mangled. It's supposed to be: .controller('name', ['depname', function (depname) { })]); -- you had your closing array misplaced.
You should really use semi-colons.
You don't create an array of 5 items in JavaScript like this: var a = new Array(5), that creates an array that contains a 5. Instead, you should do var a = []; a.length = 5;

Use checkboxes in angularjs to manage array of objects

I had asked a question about
How to associate objects as models using ng-options in angularjs.
And I got an awesome answer very fast. My followup questions is that the response uses <select mutiple> to handle the child object array.
You can see a working example of what I want, working with <select> at http://plnkr.co/edit/FQQxrSE89iY1BnfumK0A?p=preview
How can I use <input type='checkbox'> (instead of <select>) to handle that object array i.e. ng:model="shirt.colors" while repeating the items from colors object array.
The reason, this appears so complicated to me is that I have to manage an array of objects instead of array of values... for example, if you look in the fiddle, there are color objects and shirt object that has multiple colors.
If the color object changes, it should change the corresponding color objects in shirt objects.
Thank you in advance.
You just need some intermediate value in your scope, and bind checkboxes to it. In your controller - watch for it changes, and manually reconstruct shirt.colors, according to it value.
<div ng-repeat='shirt in shirts'>
<h3>Shirt.</h3>
<label>Size: <input ng-model='shirt.size'></label><br/>
<label>Colors:</label>
<label ng-repeat="color in colors">
{{color.label}} <input ng-model="selection[$parent.$index][$index]" type="checkbox"/>
</label>
</label>
</div>
And in your controller:
$scope.selection = [[],[]];
$scope.$watch('selection', function () {
console.log('change', $scope.selection);
angular.forEach($scope.selection, function (shirtSelection, index) {
$scope.shirts[index].colors = [];
angular.forEach(shirtSelection, function (value, index2) {
if (value) $scope.shirts[index].colors.push($scope.colors[index2]);
});
});
}, true);
You can test it here: http://plnkr.co/edit/lh9hTa9wM5fkh3nT09RJ?p=preview

Resources