Reputation: 4028
I am trying to implement a toggle feature where a user can enable a day and then select AM or PM with checkboxes.
The problem I am having is trying to de-toggle the button if the user unchecks AM and PM.
Screenshot:
Markup
<div class="form-group-checkboxes col-xs-1 pt14">
<toggle v-model="availability.monday.active" theme="custom" color="blue"></toggle>
</div>
<div class="form-group-checkboxes col-xs-2">
<h4>Mon</h4>
</div>
<div class="form-group-checkboxes col-xs-1 mt10">
<label class="checkbox-inline"><input type="checkbox" name="mondayAM"
class="pull-left"
:disabled="availability.monday.active ===false"
v-model="availability.monday.am">AM</label>
</div>
<div class="form-group-checkboxes col-xs-1 ml20 mt10">
<label class="checkbox-inline"><input type="checkbox" name="mondayPM"
class="pull-left"
:disabled="availability.monday.active ===false"
v-model="availability.monday.pm">PM</label>
Data:
monday: {
active: true,
am: true,
pm: true,
},
tuesday: {
active: true,
am: true,
pm: true,
},
wednesday: {
active: true,
am: true,
pm: true,
},
I tried creating a computed property that checks the AM
and PM
property values.
toggleMonday() {
return this.availability.monday.am === true && this.availability.monday.pm === true;
},
However that did not provide the toggle experience required.
TLDR, how can I allow different elements with their own binded values to affect each other
e.g.
Upvotes: 2
Views: 4050
Reputation: 26878
I think this may be a use case for the .sync
modifier. I am going to assume that you want the properties of your "day" object to be visible to the parent scope. In that case, you should attach them to the day component in such a way that all the properties synchronize as they are changed.
Consider this example:
Vue.component("daytoggle", {
props: ["dayname", "dayo"],
watch: {
"dayo.enabled": function(newVal) {
if (!newVal) {
this.$emit("update:dayo", {
enabled: false,
times: {
am: false,
pm: false
}
});
} else {
this.$emit("update:dayo", {
enabled: true,
times: {
am: true,
pm: true
}
});
}
},
"dayo.times.am": function(newVal) {
if (!newVal && !this.dayo.times.pm) {
this.$emit("update:dayo", {
enabled: false,
times: {
am: false,
pm: false
}
})
}
},
"dayo.times.pm": function(newVal) {
if (!newVal && !this.dayo.times.am) {
this.$emit("update:dayo", {
enabled: false,
times: {
am: false,
pm: false
}
})
}
}
}
});
const app = new Vue({
el: "#app",
data() {
return {
days: [{
weekday: "Monday",
props: {
enabled: true,
times: {
am: true,
pm: true
}
}
},
{
weekday: "Tuesday",
props: {
enabled: true,
times: {
am: true,
pm: true
}
}
},
{
weekday: "Wednesday",
props: {
enabled: true,
times: {
am: true,
pm: true
}
}
}
]
}
}
});
.day {
color: red;
}
.day.enabled {
color: green;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<main id="app">
<section>
<p>This data is populated in such a way that each day creates a "daytoggle" component and gives each instance a "day".</p>
<daytoggle v-for="(day, idx) in days" :dayname="day.weekday" :dayo.sync="day.props" :key="idx" inline-template>
<div>
<label><span>{{dayname}}</span> <input type="checkbox" v-model="dayo.enabled"></label>
<label><span>AM</span> <input type="checkbox" v-model="dayo.times.am" :disabled="!dayo.enabled"></label>
<label><span>PM</span> <input type="checkbox" v-model="dayo.times.pm" :disabled="!dayo.enabled"></label>
</div>
</daytoggle>
</section>
<section>
<p>This portion is populated by data pulled from the app level scope.</p>
<ul>
<li v-for="day in days">
<div><span class="day" :class="{'enabled': day.props.enabled}">{{day.weekday}}</span> <span v-if="day.props.times.am">AM</span> <span v-if="day.props.times.pm">PM</span></div>
</li>
</ul>
<section>
</main>
Upvotes: 2
Reputation: 43881
Set active
to be a computed with a setter. The getter should return true if AM or PM is selected. If this
is the day data:
get() { return this.am || this.pm; }
The setter should set both AM and PM to whatever its new value is:
set(newValue) { this.am = newValue; this.pm = newValue; }
And since you have multiple days with the same structure, you should make it a component so you only have to write it once. Here is a minimal example:
new Vue({
el: '#app',
data: {
days: ['Monday', 'Tuesday', 'Wednesday']
},
components: {
daySelector: {
template: '#day-selector-template',
props: ['day'],
data() {
return {
am: false,
pm: false
};
},
computed: {
active: {
get() {
return this.am || this.pm;
},
set(newValue) {
this.am = this.pm = newValue;
}
}
}
}
}
});
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
<day-selector v-for="day in days" :day="day" :key="day">
</day-selector>
</div>
<template id="day-selector-template">
<div>
{{day}}
<input type="checkbox" v-model="active">
<input type="checkbox" v-model="am">
<input type="checkbox" v-model="pm">
</div>
</template>
Upvotes: 3
Reputation: 43881
Here I have modified @zero298's solution to use settable computeds instead of v-model
ing (and thus modifying) props data. Each computed getter returns the corresponding prop member. Each setter emits the dayo structure with appropriate changes to the parent, which, by virtue of .sync
, updates the parent object.
Vue.component("daytoggle", {
props: ["dayname", "dayo"],
computed: {
enabled: {
get() {
return this.dayo.enabled;
},
set(newVal) {
this.doEmit(newVal, newVal);
}
},
am: {
get() {
return this.dayo.times.am;
},
set(newVal) {
this.doEmit(newVal, this.pm);
}
},
pm: {
get() {
return this.dayo.times.pm;
},
set(newVal) {
this.doEmit(this.am, newVal);
}
}
},
methods: {
doEmit(am, pm) {
this.$emit('update:dayo', {
enabled: am || pm,
times: {
am,
pm
}
});
}
}
});
const app = new Vue({
el: "#app",
data() {
return {
days: [{
weekday: "Monday",
props: {
enabled: true,
times: {
am: true,
pm: true
}
}
},
{
weekday: "Tuesday",
props: {
enabled: true,
times: {
am: true,
pm: true
}
}
},
{
weekday: "Wednesday",
props: {
enabled: true,
times: {
am: true,
pm: true
}
}
}
]
}
}
});
.day {
color: red;
}
.day.enabled {
color: green;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<main id="app">
<section>
<p>This data is populated in such a way that each day creates a "daytoggle" component and gives each instance a "day".</p>
<daytoggle v-for="(day, idx) in days" :dayname="day.weekday" :dayo.sync="day.props" :key="idx" inline-template>
<div>
<label><span>{{dayname}}</span> <input type="checkbox" v-model="enabled"></label>
<label><span>AM</span> <input type="checkbox" v-model="am" :disabled="!dayo.enabled"></label>
<label><span>PM</span> <input type="checkbox" v-model="pm" :disabled="!dayo.enabled"></label>
</div>
</daytoggle>
</section>
<section>
<p>This portion is populated by data pulled from the app level scope.</p>
<ul>
<li v-for="day in days">
<div><span class="day" :class="{'enabled': day.props.enabled}">{{day.weekday}}</span> <span v-if="day.props.times.am">AM</span> <span v-if="day.props.times.pm">PM</span></div>
</li>
</ul>
</section>
</main>
Upvotes: 1