I have a list of checkboxes that execute functions on check and uncheck. I also have an observable array that holds the values of the checkboxes currently active (put into local storage). The relevant code is here:
this.layerToggleChecked = knockout.observableArray();
// ...
this.layerToggle = function (source, name, type, url, description) {
return knockout.computed({
read: function () {
return this.layerToggleChecked();
},
write: function (checked) {
if (checked) {
alert("loading");
this.layerToggleChecked.push(source());
} else {
alert("removing");
this.layerToggleChecked.remove(source());
}
}
}, this);
}
The checkboxes work as planned triggering the functions until I added return this.layerToggleChecked(); which returns
knockout-3.2.0.js:13 Uncaught TypeError: b.push is not a function
Knockout's checked binding handles arrays differently from other values. You're expecting a true or false to be written to the computed observable, but you're returning an array, which obviously aren't the same.
From http://knockoutjs.com/documentation/checked-binding.html:
Special consideration is given if your parameter resolves to an array. In this case, KO will set the element to be checked if the value matches an item in the array, and unchecked if it is not contained in the array.
When the user checks or unchecks the checkbox, KO will add or remove the value from the array accordingly.
Since you're using an array to hold the checked values, you're better off binding directly to it:
<input type="checkbox" data-bind="checkedValue: source, checked: layerToggleChecked" />
Related
I have a series of checkboxes that need to push their name into a state array when checked, and remove their name when they are unchecked. No matter what I do, checking any sets the array to contain only that checkbox's name.
const [selectedItems, setSelectedItems] = useState([]);
useEffect(()=> {
// Log selectedItems out every time it changes.
console.log(selectedItems) // Always only the last selected checkbox name, no others.
}, [selectedItems])
const onCheckChange = e=> {
console.log(e.target.name, e.target.checked) // Are as expected, the name and value.
if (e.target.checked) {
setSelectedItems(prev=> [...prev, e.target.name])
// setSelectedItems([...selectedItems, e.target.name]) // Same result.
} else {
setSelectedItems( prev=> prev.filter(s=> s!==e.target.name) )
// setSelectedItems(selectedItems.filter(s=> s!==e.target.name)) // Same result.
}
}
Every time I check a checkbox, selectedItems is set to contain only the selected item's name. Every time I uncheck a checkbox, setSelectedItems is set to [].
This seems like the most normal pattern ever, but there must be some weirdness with arrays and useState? Thanks to anyone who can help!
The problem is in e.target.name. Most likely all your checkboxes are using the same name.
To fix, try to give different names, or use something else to find which checkbox was interacted (for example use index, with adding it as an argument of your onChange handler)
I have a series of text fields with a button next to each field. When the user taps the button next to one of the fields I want to apply a particular style to that button (to change its colour) - essentially allowing the user to "tick" that they have checked that field (similar to a checklist).
There are nine text fields/buttons on the page, and I have the status of all the buttons stored in an array called items_checked which is initialized in data() as the following:
items_checked: [false, false, false, false, false, false, false, false, false]
Each button has the following code:
<button class="btn btn-danger" v-on:click="toggleChecked(0)" v-bind:class="{'itemChecked' : items_checked[0]}">
where the number indicates the index of the button (i.e. 0 is the first button, 1 is the second button, etc.) to correspond to the equivalent boolean in items_checked.
The v-on:click event just toggles the checked status in the items_checked for the tapped button:
toggleChecked (itemIndex) {
if (this.items_checked[itemIndex] === false) {
this.items_checked[itemIndex] = true
} else {
this.items_checked[itemIndex] = false
}
console.log(this.items_checked)
}
This works as the console.log shows the boolean values toggling.
However, the v-bind does not work as the itemChecked class does not get applied to the button. It seems to be an issue binding to a boolean value within an array, as when I bind just to a standard boolean declared within data() it works fine.
I do eventually need all the checked statuses stored in an array, so that I can evaluate that all have been checked before allowing the user to submit the page.
Any help would be appreciated.
This is common reactivity problem.
In document:
Due to limitations in JavaScript, Vue cannot detect the following
changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue
When you modify the length of the
array, e.g. vm.items.length = newLength
You can use deep copy
toggleChecked (itemIndex) {
this.items_checked[itemIndex] = !this.items_checked[itemIndex]
this.items_checked = JSON.parse(JSON.stringify(this.items_checked))
console.log(this.items_checked)
}
Another solution is using Vue.set
toggleChecked (itemIndex) {
var oldValue = this.items_checked[itemIndex]
this.$set(this.items_checked, itemIndex, !oldValue)
console.log(this.items_checked)
}
I have a bunch of check boxes and the value of each one is a different price. I created an object that holds the function to push the values into an array if the box is checked. When I console log the empty array it displays as empty so i know that works. I just cant get it to console log with the pushed value in it.
<input type="checkbox" id="bac" value ="1">bacon - $1
var allIngredients = {
ingredientArray: [],
baconBox: function() {
var bacon = document.getElementById('bac');
if (bacon.checked === true) {
this.ingredientArray.push(bacon.value);
}
},
edit:
console.log(allIngredients.ingredientArray);
that returns an empty array like this "[]". I cannot get it to return an array with the value of bacon in it like this "[1]" when i check the box. The value of bacon is 1.
As others have pointed out in the comments, you should call the function allIngredients.baconBox().
One option is to add an event listener, like so:
document.getElementById('bac').addEventListener('change', function() {
allIngredients.baconBox();
});
Although a downside of this approach is that unchecking the box wouldn't remove the bacon.value from your allIngredients array.
Let's say I have an array declared in the data section
data() {
return {
myData : [{foo:2, bar:3},{foo:4,bar:5}]
}
}
When I change the bar property of the second element, I want to know that the second element was changed.
What would my watch function look like?
watch : {
myData : {
deep : true,
handler(oldVal, newVal) {
console.log("New val is the entire myData[] array which is not what I want", newVal);
// I also want to know what index of the array the changed value belonged to.
}
}
}
First, your arguments are backwards on the watcher, as it should be newVal, oldVal
Also, I think this warning may apply here:
https://v2.vuejs.org/v2/api/#vm-watch
Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
I have a json object thats being loaded through a checkbox in an ng-repeat. It lists possible services and I'm trying to get access to the ones that have been checked when I submit the form.
I'm loading the checkbox through:
<label style="margin-right:10px" ng-repeat="item in dsServces">
<input
type="checkbox"
name="selectedFruits"
value="{{item.DefaultServiceID}}"
ng-model="expense.dsService[item.DefaultServiceName]"
> {{item.DefaultServiceName}}
</label>
I can see the expense is being built by testing with:
{{ expense.dsService }}
returns (when a couple are selected)
{"Email":true,"Backups":true}
But I can't work out how to loop through so it says just the service name so I can add them against the customer.
In the back end I have:
$scope.expense = {
tags: []
};
And on the submit:
angular.forEach($scope.expense, function(item) {
console.log(item);
});
Which spits out Object {Email: true, Backups: true} but I can't work out to have a loop where it just returns which ever objects are true.
My main aim is to have something like
angular.forEach(forloop) {
//service.item should be like 'Email' whatever the item.DefaultServiceName is
// the loop should be all items that are checked
addService(service.item);
});
If I am understanding properly, you want to push the key values of the object into an array. If that is the case, just make a little change to your forEach()
Like so:
// The first parameter is what you want to loop through
// Second is the iterator, you can separate out the key and values here
// The third is the context, since I put the array we want to push to in here
// we just need to call `this.push()` within the iterator
angular.forEach($scope.expense, function(value, key) {
this.push(key);
}, $scope.expense.tags);
Let me know if this helps.