Knockout - Checking checkboxes according to observableArray - javascript

So I have a list of schedules that I need to display for a user and show which schedules he or she is currently on and give them the possibility to jump on and off said schedules.
My viewmodel looks like this
self = this;
self.shifts = ko.observableArray();
self.selectedShifts = ko.observableArray();
//I populate self.shifts here with a WEB API call
//Run through each shift and check if current user is on it and set checked / not checked value for checkbox
ko.utils.arrayForEach(self.shifts(), function(shift) {
//Clear array
self.usersOnShift([]);
//Populate array with all users on the shift
self.usersOnShift = ko.observableArray(WEB API CALL HERE);
var userInShift = ko.utils.arrayFirst(self.usersOnShift(), function(user) {
if (selectedUserId == user.ID) {
return true;
}
});
if (userInShift) {
self.selectedShifts.push(shift.ID);
}
});
ko.applyBindings(self);
My HTML looks like this
<div class="simple_overlay" id="shiftOverlay">
<div class="details">
<div data-bind="foreach: shifts">
<div><span class="staff-initials" data-bind="text:wardName"> </span><input type="checkbox" data-bind="value: ID, checked: $root.selectedShifts"/> </div>
</div>
<div>
Connect
Close
</div>
</div>
</div>
I can see that the value of the checkboxes are set correctly to the ID of the corresponding shifts. However a shift that I know the user in question is on is not checked and I know that the selectedShifts observableArray contains the value.
Somehow the "checked: $root.selectedShifts" call / check is not working but I know that it contains the right value. What am I doing wrong?

The problem is that your value is an integer, but when bound to the checkbox element, it becomes a string. When the checked binding tries to find the value in the array, it doesn't find a match because it uses strict equality for comparison and (2 === "2") is false.
The simplest way to work around this problem is to convert your values to string when you add them to the array:
self.selectedShifts.push("" + shift.ID);
Of course this means that your model has to change, and that might not be a great solution. I came up with a custom binding, checkedInArray that replaces checked and supports any type of value. You can learn about it, see it in action, and use it like this:
<input type="checkbox" data-bind="checkedInArray: {value: ID, array: $root.selectedShifts }" />
In Knockout 2.3.0 (which is still in development) there will be a new binding, checkedValue, that will allow you use any type of value with the checked binding. Using that version, you could update your HTML to use checkedValue:
<input type="checkbox" data-bind="checkedValue: ID, checked: $root.selectedShifts"/>

Is shift.ID an observable property? If it is, then you need to add it to the array like this:
self.selectedShifts.push(shift.ID());
Otherwise you're just adding the whole observable to the array, and not the value.

Related

How to two-way bind a checkbox using Angular?

I currently have an accordion with a bunch of options in the form of checkboxes. The user can select the checkboxes as expected however, I want to already have some of those checkboxes checked depending on certain conditions. The issue is sometimes that is determined after the page has loaded. Below are simplified examples to demonstrate my code (my code is for work and would not be able to share due to confidentiality issues).
My HTML looks like this:
<div class="row">
<div "ngFor="let value of filteredPeople" >
<div>
<input type="checkbox" (click)="selectPeople(value)" [checked]="checkedPeople.get(value)">
{{ value }}
</div>
</div>
</div
My Javascript:
public checkPeople() {
this.selectedPeople.forEach(element => {
this.checkedPeople.set(element, true);
});
}
To explain some variables and methods:
Variables:
filterPeople - a string array of all possible people
checkedPeople - a map with a KVP of string (the people) and boolean (whether or not their checkbox is checked)
selectedPeople - a string array of people whose checkboxes I want already checked
Methods:
selectPeople - checks the corresponding checkbox when user clicks on it
checkPeople - a method called when I want the specific checkboxes checked (these specific checkboxes change based on other factors so I cannot hard code it)
For some reason my checkPeople method does not seem to select the expected checkboxes, I am not sure why but I have a feeling it is to do with the fact that I have used "[checked]" in the HTML and that it should be something else. Can someone clarify the issue for me and help me identify a fix? (Note: as the title suggests, I am using Angular)
Edit:
I have just debugged my code and added breakpoints, the checkedPeople map has the correct mapping of people to true for each of the elements in selectedPeople which shows that the checkPeople method is working as expected. Therefore the issue must be with the [checked] attribute if I'm not mistaken. I cannot see why it wouldn't work though.
You should use [ngModel]="checkedPeople.get(value)"
instead of [checked]="checkedPeople.get(value)"
to initialize the checkbox and
(change)="checkUncheckPeople(value, $event)"
to update the value when you check or uncheck it, where
checkUncheckPeople(value, e) {
this.checkedPeople.set(value, e.target.value);
}
So, in conclusion, your HTML input element will be:
<input
type="checkbox"
[ngModel]="checkedPeople.get(value)"
(change)="checkUncheckPeople(value, $event)"
/>
If you choose to use an object instead of a map then you can also directly use
[(ngModel)]="checkedPeople[value]"
to two-way bind the variable

Angular - trouble with properties updating onchange from checkbox

I am attempting to use a checkbox in Angular in order to toggle the contents of an array. I am using property booleans to determine if 3 different boxes are checked and adjust the contents of the array accordingly. The array defaults to containing all 3 subarrays via declaring this.allNouns in ngOnInit(). When I click the first checkbox, I can see in the console that it is running the associated onchange function and setting the associated boolean to its opposite, but the interpolated string in the view does not change. When I click it again, it will change the view but now everything is one step behind. Also when it hits the console logs, it appears to load the prior length and not the length of the newly established this.allNouns. Below are the html and ts snippets associated with it. This is the first of the three checkboxes, but I plan to apply it to all when smoothed out.
TS
preloadedNouns: Boolean = true;
toggleData() {
if(this.preloadedNouns==false){
this.allNouns=this.nouns.concat(this.onlyUserNouns);
console.log('hitting first condition in toggledata', this.allNouns.length);
}
else {
this.allNouns=this.nouns.concat(this.stockNouns).concat(this.onlyUserNouns);
console.log('hitting second condition in toggledata', this.allNouns.length);
}
}
HTML
<div name="allNouns" [(ngModel)]="allNouns" [ngModelOptions]="{standalone: true}" ngDefaultControl>{{allNouns.length}}</div>
<div class="toggleDiv">
<label>Preloaded Nouns</label>
<input id="preloadedNounsCheck"
type="checkbox"
[checked]="preloadedNouns"
(change)="toggleData(); preloadedNouns = !preloadedNouns">
</div>
i don't clear about your nouns logic.
<div class="toggleDiv">
<label>Preloaded Nouns</label>
<input id="preloadedNounsCheck"
type="checkbox"
[checked]="preloadedNouns"
(change)="preloadedNouns = !preloadedNouns;toggleData()">
</div>
but i hope based on my understanding above code will help to you.
just reorder the function call in (change).

Ember action preventing checkbox value from setting

In my ember application I would like to render a set of checkboxes dynamically via a loop and disable any unselected checkboxes if 2 of the checkboxes have been checked.
I am having problems with setting a value to 'true' when a checkbox is selected that has a change action. For the following example checkbox.value does not get set when the checkbox is selected.
{{ input type="checkbox" disabled=checkbox.disabled checked=checkbox.value change=(action "disableCheckboxes" val)}} {{checkbox.label}}
However when I remove the action it does get set:
{{ input type="checkbox" disabled=checkbox.disabled checked=checkbox.value}} {{checkbox.label}}
Full example on JSFiddle
How can I set the value while still running the action? Or is the a better way to disable the unchecked checkbox when 2 have already been selected?
Well, first you can use an action. Checkout this ember-twiddle.
But I would recommend to directly bind your value to your target object. See here.
The idea is basically to wrap your array and calculate the isDisabled in the wrapping:
boxes:[{id:1,value:true},{id:2},{id:3}],
boxArr: Ember.computed('boxes.#each.value', {
get() {
let disableOthers = Ember.get(this, 'boxes').filterBy('value', true).get('length') >= 2;
return Ember.get(this, 'boxes').map(origin => {
return {
origin,
disabled: disableOthers && !origin.value,
};
});
}
}),
Then you can just use it in your template:
{{#each boxArr as |box|}}
{{input type="checkbox" disabled=box.disabled checked=box.origin.value}} {{box.origin.id}}
{{/each}}
A third way to archive is with a helper. You can just have a computed property saying if all checkboxes with value=false should be disabled, and then use an and and not helper to set disabled=(and disableFalseCheckboxes (not box.value). Checkout ember-truth-helpers for that.

Why isn't this knockout code picking up changes bound to an ObservableArray?

I've got some strange behavior in (what should be) some relatively straight forward code. I'm trying to prepend an <input> tag onto a list of elements.
HTML
<button data-bind="click: addItem">Add</button>
<div data-bind="foreach: items">
<input data-bind="value: $parent.items()[$index()]" />
<button data-bind="click: $parent.removeItem">Remove</button>
<br>
</div>
Javascript
function ItemViewModel() {
var self = this;
self.items = ko.observableArray();
self.addItem = function() {
self.items.unshift('');
}
self.removeItem = function(name) {
self.items.remove(name);
}
}
ko.applyBindings(new ItemViewModel());
What's going wrong:
RemoveItem is sending an empty string to the function rather than the contents of the input element. However, what's weird is that this only applies to the most recently appended item AND only if no other item has been clicked. For instance, if I have on input type some text, and click remove, an empty string is sent to my function. However, if I add another input element via the add button, Knockout seems to 'reflow' and the original field picks up the changes in the text field and can be removed without a problem.
I'm pretty baffled here. Why isn't Knockout picking up the changes on recently prepended items?
So binding directly to a string doesnt provide the two way binding you would expect, regardless of if you had the value in an observableArray. observableArrays just track changes to a collection (items being added/removed) but not changes to individual items in the array.
To fix this problem, you will need to update your data model a bit:
js
self.addItem = function() {
self.items.unshift({name: ko.observable('')});
}
html
<input data-bind="value: name" />
Fiddle:
http://jsfiddle.net/e2b0089j/

Setting html checkbox checked value based on mvc model

I have an Edit view where I have some checkboxes that I need populated based on values from the model. I would like to avoid using different CheckBoxListFor extensions, as I believe there is a simpler way to do it.
I tried to set the value with javascript, but I end up with all checkboxes checked, which is not ok.
This is what I tried:
function SetAdminCheckBox() {
var contains = #Model.RoleNames.Contains("Admin");
var True = true;
var result;
result = contains ? 'checked' : '';
return result;
}
<input type="checkbox" name="roleNames" value="Admin" checked="SetAdminCheckBox()"/>
I found a way to do it, using some razor syntax:
<input type="checkbox" name="roleNames" value="Admin" #if (Model.RoleNames.Contains("Admin")) {<text>checked="checked"</text>} />
you can't use "checked" attribute like that, checked has no value it is either present or not if it is present than checkbox is checked otherwise not
use an if to write two version one which will output checked attribute and other which will not.

Categories

Resources