tomaytotomato
tomaytotomato

Reputation: 4028

Vue.js toggle button based on the result of two checkbox values?

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:

enter image description here

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.

  1. User toggles Monday off, AM and PM become unchecked
  2. User toggles Tuesday on, unchecks AM and PM, then Tuesday becomes untoggled.

Upvotes: 2

Views: 4050

Answers (3)

zero298
zero298

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

Roy J
Roy J

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

Roy J
Roy J

Reputation: 43881

Here I have modified @zero298's solution to use settable computeds instead of v-modeling (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

Related Questions