#click on checkbox add/remove data - javascript

I currently have the following scenario:
I have multiple checkboxes, once any is clicked, the value of it will get added to an array. If the checkbox is unchecked then the item needs to be removed out of the array again.
selectAddOn(addOnId) {
if (! this.selectedAddOns.includes(addOnId)) {
this.selectedAddOns.push(addOnId);
}
}
The following works and it adds them to my selectedAddOns[]. But when the checkbox is checked again, it is not removed. Sure, I could just use else, but...
Unfortunately, the browser behavior is when you click on a <label>, a click event will automatically be triggered on the <input>, so the outer div receives 2 events, one from label, one from input. I am aware that I can work around this by adding #click.prevent on the <label>, but this then will not add my custom checkbox styles.
<div #click="selectAddOn(index)" class="col-6" v-for="(addOn, index) in categories[categoryId].addOns">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input">
<span class="custom-control-indicator"></span>
<span class="custom-control-description">{{ addOn.name }} (+ ${{ addOn.price }})</span>
</label>
</div>
Any idea on how I can work around this scenario?

This is a built-in behavior of v-model when used with an array on multiple checkboxes. You don't need a click handler. (Code shamelessly lifted from Bert's answer.)
console.clear()
new Vue({
el: "#app",
data:{
selectedAddOns:[],
categories:[
{
addOns:[
{name: "AddOn One", price: 10},
{name: "AddOn two", price: 20},
{name: "AddOn Three", price: 30},
]
},
],
categoryId: 0
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
Selected Addons: {{selectedAddOns}}
<div class="col-6" v-for="addOn, index in categories[categoryId].addOns">
<label class="custom-control custom-checkbox" >
<input type="checkbox" class="custom-control-input" :value="index" v-model="selectedAddOns" >
<span class="custom-control-indicator"></span>
<span class="custom-control-description">{{ addOn.name }} (+ ${{ addOn.price }})</span>
</label>
</div>
</div>

Put the click event handler on the input.
console.clear()
new Vue({
el: "#app",
data:{
selectedAddOns:[],
categories:[
{
addOns:[
{name: "AddOn One", price: 10},
{name: "AddOn two", price: 20},
{name: "AddOn Three", price: 30},
]
},
],
categoryId: 0
},
methods:{
selectAddOn(addOnId) {
let index = this.selectedAddOns.findIndex(a => a === addOnId)
if (index >= 0)
this.selectedAddOns.splice(index, 1)
else
this.selectedAddOns.push(addOnId);
}
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
Selected Addons: {{selectedAddOns}}
<div class="col-6" v-for="(addOn, index) in categories[categoryId].addOns">
<label class="custom-control custom-checkbox" >
<input type="checkbox" class="custom-control-input" #click="selectAddOn(index)" >
<span class="custom-control-indicator"></span>
<span class="custom-control-description">{{ addOn.name }} (+ ${{ addOn.price }})</span>
</label>
</div>
</div>

Related

VueJS Input Binding for dynamic radio buttons

I am trying to render several radio buttons with dynamic data.
Users can create markets and then products and unlock these products for each market and give more properties. Among other things, radio buttons should be available for this purpose.
I've tried:
<div v-for="market in markets" :key="market.id">
<div>
<span>{{ market.name }}</span>
</div>
<div>
<div v-for="(field, index) in market.market_fields" :key="index">
<label :for="field.name">{{field.label}}</label>
<div v-if="field.type != 'radio'"><input :type="field.type" v-model="field.value"></div>
<div>
<input type="radio" :name="field.name" :value="true" v-model="field.value">
<label :for="field.name">ja</label><br>
<input type="radio" :name="field.name" :value="false" v-model="field.value">
<label :for="field.name">nein</label><br>
</div>
</div>
</div>
</div>
The problem seems to be with the v-model because the selection of a radio button is only ever for one market.
for example:
I click a radio button for market1, then the radio button is checked, but if I select the same radio button for market2, it is no longer checked for market1.
EDIT:
For every market the radio buttons had the same name attribute (field.name). So i changed it to market.name_field_name
Try to add index to input id :id="field.name + index":
const { ref } = Vue
const app = Vue.createApp({
data() {
const markets = ref(
[{market_fields: [{id:1, name:'market1', label: 'aaa', value: '', type: 'radio'}, {id:2, name:'market1', label: 'bbb', value: '', type: 'radio'}, {id:3, name:'market2', label: 'ccc', value: '', type: 'radio'}]}]
)
return {
markets
}
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="market in markets" :key="market.id">
<div>
<span>{{ market.name }}</span>
</div>
<div>
<div v-for="(field, index) in market.market_fields" :key="index">
<label :for="field.name">{{field.label}}</label>
<div v-if="field.type != 'radio'">
<input :type="field.type" v-model="field.value">
</div>
<div>
<input type="radio" :id="field.name + index + 'ja'" :value="true" v-model="field.value">
<label :for="field.name + index + 'ja'">ja</label><br>
<input type="radio" :id="field.name + index + 'nein'" :value="false" v-model="field.value">
<label :for="field.name + index + 'nein'">nein</label><br>
</div>
</div>
</div>
</div>
{{ markets }}
</div>

How to check all checkboxes on selecting one in Vuejs?

isCheckAll: false,
methods: {
checkAll: function(){
this.isCheckAll = !this.isCheckAll;
},
updateCheckall: function(){
this.isCheckAll = true;
},
<div class="checkbox-alignment-form-filter">
<input type="checkbox" id="three" class="vh-product" #click='checkAll()' v-model='isCheckAll' />
<label class="productlist-specific" for="three"
> : 2011</label
>
</div>
<div class="checkbox-alignment-form-filter2">
<input type="checkbox" id="four" class="vh-product" #change='updateCheckall()'/>
<label class="productlist-specific" for="four">E0A</label>
</div>
<div class="checkbox-alignment-form-filter3">
<input type="checkbox" id="five" class="vh-product" #change='updateCheckall()'/>
<label class="productlist-specific" for="five">BR</label>
</div>
<div class="checkbox-alignment-form-filter4">
<input type="checkbox" id="six" class="vh-product" #change='updateCheckall()'/>
<label class="productlist-specific" for="six">E11350A</label>
</div>
<div class="checkbox-alignment-form-filter5">
<input type="checkbox" id="seven" class="vh-product" #change='updateCheckall()'>
<label class="productlist-specific" for="seven">E0BR</label>
</div>
How to select check and uncheck all checkboxes, on selecting one checkbox.
I don't know what stopping me to proceed, For every checkbox taken one #click and sub checkboxes also taken one #click. and then in js i am toggling that condition to check and uncheck.
I think you had some problems with your logic, so let me explain this for you:
You need to have a v-model on all of them, to be able to control state updates (whenever a user clicks something it needs to be updated).
You don't need to have two methods for all of this, you can have just one to enable all of the checkboxes. The rest can be handled by v-model.
The following code will enable/disable all of the checkboxes by clicking the first one, and if you click the other ones, it'll just enable/disable that particular checkbox.
<div id="app">
<input type="checkbox" v-model="checkboxes[0]" v-on:click="checkAll()">
<input type="checkbox" v-model="checkboxes[1]">
<input type="checkbox" v-model="checkboxes[2]">
<input type="checkbox" v-model="checkboxes[3]">
</div>
const app = new Vue({
el: "#app",
data: {
// control the state of checkboxes
checkboxes: [false, false, false, false]
},
methods: {
checkAll: function(){
for(let i = 1; i < this.checkboxes.length; i++){
// update all of the checkboxes to the value of the first one.
this.checkboxes[i] = !this.checkboxes[0];
}
}
}
})
your problem was your data handling.
i made you a playable example in CodeSandbox.
with this you are able to control the main checkbox bidirectional.
if all sub checkboxes are true the main checkbox will be set to true as well.
<template>
<div id="app">
<div class="checkbox-group">
<div class="main-checkbox">
<input
type="checkbox"
:name="checkboxes.main.label"
v-model="checkboxes.main.value"
#input="toggleAllCheckboxes()"
/>
<label :for="checkboxes.main.label">{{ checkboxes.main.label }}</label>
</div>
<div
class="sub-checkbox"
v-for="(checkbox, index) in checkboxes.sub"
:key="index"
>
<input
type="checkbox"
:name="checkbox.label"
v-model="checkbox.value"
/>
<label :for="checkbox.label">{{ checkbox.label }}</label>
</div>
</div>
</div>
</template>
<script>
export default {
name: "App",
data: () => ({
checkboxes: {
main: {
value: false,
label: ":2011",
},
sub: [
{
value: false,
label: "E0A",
},
{
value: false,
label: "BR",
},
{
value: false,
label: "E11350A",
},
{
value: false,
label: "E0BR",
},
],
},
}),
computed: {
mainIsChecked: function () {
return this.checkboxes.main.value;
},
allSubsAreChecked: function () {
return this.checkboxes.sub.every((checkbox) => checkbox.value);
},
},
watch: {
allSubsAreChecked: function () {
this.checkboxes.main.value = this.allSubsAreChecked;
},
},
methods: {
toggleAllCheckboxes() {
for (const checkbox of this.checkboxes.sub) {
checkbox.value = !this.mainIsChecked;
}
},
},
};
</script>
If you are working with multiple checkboxes, you should bind them to the same Array using v-model according to the Vue docs. https://v2.vuejs.org/v2/guide/forms.html#Checkbox
In your case, this means that you should have one array managing all the checkboxes except the one which controls selecting them all, that should be handled differently.
Each of those checkboxes should have a value binding as well, telling Vue which value they represent in the array.
You could also store all the possible checkbox values in an array which would help you reducing code duplication in your template.
You can render all 4 checkboxes which can be separately checked using a v-for loop, and manage them with the same v-model.
The checkbox controlling the "all checked" state should be managed with a different v-model. When that checkboxes value changes, we should check/uncheck all the other checkboxes, which can be done easily using a watcher.
Here's an example using this approach https://codesandbox.io/s/inspiring-dijkstra-fzkmn?file=/src/App.vue
export default {
name: "App",
data: () => ({
selectedValues: [],
allChecked: false,
}),
computed: {
isAllChecked() {
return false;
},
allCheckboxOptions() {
return [
{
label: "E0A",
value: "four",
},
{
label: "BR",
value: "five",
},
{
label: "E11350A",
value: "six",
},
{
label: "E0BR",
value: "seven",
},
];
},
},
methods: {
checkAll() {
this.selectedValues = this.allCheckboxOptions.map(
(option) => option.value
);
},
checkNone() {
this.selectedValues = [];
},
},
watch: {
allChecked(isAllChecked) {
if (isAllChecked) {
this.checkAll();
} else {
this.checkNone();
}
},
},
};
<template>
<div id="app">
<div class="checkbox-alignment-form-filter">
<input
type="checkbox"
id="three"
class="vh-product"
v-model="allChecked"
/>
<label class="productlist-specific" for="three"> : 2011</label>
</div>
<template v-for="(checkboxOption, index) in allCheckboxOptions">
<div
:key="checkboxOption.value"
:class="`checkbox-alignment-form-filter${index + 2}`"
>
<input
type="checkbox"
:id="checkboxOption.value"
:value="checkboxOption.value"
v-model="selectedValues"
class="vh-product"
/>
<label class="productlist-specific" for="four">
{{ checkboxOption.label }}
</label>
</div>
</template>
checked: {{ selectedValues }}
</div>
</template>

Vue dynamic v-model within v-for

I have the following fieldsets containing checkboxes:
<fieldset>
<label v-for="(count, value) in availableFilters.level"><input type="checkbox" data-filterName="level" :value="value" v-model="level" #change="(e) => handleCheckbox(e, 'level')"> {{value}} ({{count}})</label>
</fieldset>
<fieldset>
<label v-for="(count, value) in availableFilters.subject"><input type="checkbox" data-filterName="subject" :value="value" v-model="subject" #change="(e) => handleCheckbox(e, 'subject')"> {{value}} ({{count}})</label>
</fieldset>
<fieldset>
<label v-for="(count, value) in availableFilters.delivery"><input type="checkbox" data-filterName="delivery" :value="value" v-model="name" #change="(e) => handleCheckbox(e, 'delivery')"> {{value}} ({{count}})</label>
</fieldset>
Notice there's a bit of repetition here, but it works. Here's my Vue instance:
var vm = new Vue({
el: '#app',
data: {
level: [],
subject: [],
delivery: [],
availableFilters: {
level: {
"UG": 12,
"PG": 12,
}
}
},
...
I want something more like this so I don't have to repeat the same block over again:
<fieldset v-for="(filters, name) in availableFilters">
<label v-for="(count, value) in filters">
<input type="checkbox" :data-filterName="name" :value="value" v-model="name" #change="(e) => handleCheckbox(e, name, value)"> {{value}} ({{count}})
</label>
</fieldset>
However, this doesn't work and it seems that the v-model is not bound to the data property. How do I correctly pass that now? The data property name will be whatever name is.
To with situation you should put that properties (level, subject, delivery) inside an object called selected as follows :
selected: {
level: [],
subject: [],
delivery: []
}
and you should loop using v-for like :
<fieldset v-for="(filters, key,index) in availableFilters">
where the filters represents the value, key represents the key like level and ìndex represents the index such 0, using the key item we could access selected like selected[key] so we could bind the checkbox to that property easily.
Full example
new Vue({
el: '#app',
data() {
return {
selected: {
level: [],
subject: [],
delivery: []
},
availableFilters: {
level: {
"UG": 12,
"PG": 12,
},
subject: {
}
}
}
}
});
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app" class="container">
<fieldset v-for="(filters, key,index) in availableFilters">
<label v-for="(count, value) in filters">
<input type="checkbox" :data-filterName="this[filters]" :value="value" v-model="selected[key]" #change="onchange"> {{value}} ({{count}})
</label>
</fieldset>
<pre>{{selected}}</pre>
</div>

Update parent class in Vue.js

I'm a Vue.js noob and trying to figure out how to toggle a class inside a for loop for the parent of the link that is clicked.
I want to click on the "Advanced" link and then toggle a class of overlay for the section and .advanced-fields elements only in that section. I know I can add the onOff data attribute to each section but I am saving to a database and don't want to add in unnecessary data fields.
Can anyone point me in the right direction. Thanks.
Here is a simplified version of my code.
<div id="app">
<section v-bind:class="{ overlay: onOff }" v-for="section in sections">
Advanced
<div class="advanced-fields" v-bind:class="{ overlay: onOff }" v-show="onOff">
<fieldset>
<label>
ID
<input type="text" name="section[id]">
</label>
</fieldset>
<fieldset>
<label>
Class
<input type="text" name="section[css_class]">
</label>
</fieldset>
</div>
</section>
</div>
<script>
new Vue({
el: "#app",
data:{
"sections": [
{
"id": "section-1",
"css_class": ""
},
{
"id": "section-2",
"css_class": ''
}
],
"onOff": false
},
methods: {
showAdvanced: function(section) {
this.onOff = !this.onOff;
}
}
});
</script>
This answer Vue bind click that adds active class (and removes from the last one) helped me figure it out.
https://jsfiddle.net/ferne97/n1hfde94/
Here is the updated code that works properly.
<div id="app">
<section v-bind:class="{ overlay: index == isActive }" v-for="(section, index) in sections">
Advanced
<div class="advanced-fields" v-bind:class="{ overlay: index == isActive }" v-show="index == isActive">
<fieldset>
<label>
ID
<input type="text" name="section[id]" v-model="section.id">
</label>
</fieldset>
<fieldset>
<label>
Class
<input type="text" name="section[css_class]" v-model="section.css_class">
</label>
</fieldset>
</div>
</section>
<pre>{{ $data }}</pre>
</div>
<script>
new Vue({
el: "#app",
data:{
"sections": [
{
"id": "section-1",
"css_class": ""
},
{
"id": "section-2",
"css_class": ''
}
],
"isActive": null
},
methods: {
showAdvanced: function(index) {
this.isActive = this.isActive === index ? null : index
}
}
});
</script>

comparing values from two different context using mustache and canjs

Lets say I have this mustache template below. contacts and categories are basically an array of objects:
<script type="text/mustache" id="contactsList">
<ul class="clearfix">
{{#contacts}}
<li class="contact span8">
<i class="icon-remove"></i>
<form>
<div class="row">
<div class="span2">
<img src="img/canjs.jpg" width="100" height="100">
</div>
<div class="span3">
<input type="text" name="name" placeholder="Add Name" value="{{name}}">
<select name="category">
{{#categories}}
<option value="{{data}}" {{sameCategory category data}}>
{{name}}
</option>
{{/categories}}
</select>
</div>
<div class="span3">
<label>Address</label>
<input type="text" name="address" value="{{address}}">
<label>Phone</label>
<input type="text" name="phone" value="{{phone}}">
<label>Email</label>
<input type="text" name="email" value="{{email}}">
</div>
</div>
</form>
</li>
{{/contacts}}
</ul>
</script>
What I want to do is generate "selected" within the option tag by comparing the contacts|category and categories|data.
so what I did was to implement the sameCategory like this:
can.Mustache.registerHelper('sameCategory', function(contactCategoryId, categoryId) {
console.log(contactCategoryId);
console.log(categoryId);
var result = contactCategoryId == categoryId ? "selected" : "";
console.log(result);
return result;
});
Unfortunately, im getting an object for both param instead of strings so my equality condition fails. What am I doing wrong? is there a better way to do this besides registerhelper?
supporting data:
var CONTACTS = [
{
id: 1,
name: 'William',
address: '1 CanJS Way',
email: 'william#husker.com',
phone: '0123456789',
category: 'co-workers'
},
{
id: 2,
name: 'Laura',
address: '1 CanJS Way',
email: 'laura#starbuck.com',
phone: '0123456789',
category: 'friends'
},
{
id: 3,
name: 'Lee',
address: '1 CanJS Way',
email: 'lee#apollo.com',
phone: '0123456789',
category: 'family'
}
];
var CATEGORIES = [
{
id: 1,
name: 'Family',
data: 'family'
},
{
id: 2,
name: 'Friends',
data: 'friends'
},
{
id: 3,
name: 'Co-workers',
data: 'co-workers'
}
];
I took these code from the examples Diving into CanJS article.
I was checking out your code on a fiddle, and I couldn't replicate it, though perhaps you were getting can.computes that you would need to run as function before you got the values.
However, that said, the helper is entirely unnecessary with the can/view/bindings plugin. Just set can-value="category" on your select and it will auto-magically select the correct option (and update the value when changed).
Demonstrated in a fiddle: http://jsfiddle.net/air_hadoken/g725X/1/
<select name="category" can-value="category">
{{#categories}}
<option value="{{data}}" >
{{name}}
</option>
{{/categories}}
</select>

Categories

Resources