2

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.
3
  • 1
    You want ||, not &&. Commented Feb 26, 2018 at 15:35
  • When you select Monday, do you want AM and PM both to automatically be selected? Commented Feb 26, 2018 at 15:42
  • Yea and if it is untoggled, AM and PM would be unchecked. Commented Feb 26, 2018 at 15:46

3 Answers 3

3

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>

Sign up to request clarification or add additional context in comments.

4 Comments

How do you get the day state out of the component though? Would you have to work with event emissions? That's where I'm running into trouble solving this. Can you show how you would be printing out the state of the day at the app level scope?
What does the parent need? How will it use the states?
What does the parent have access to now other than an array of day names? I think it would need the ability to know if the day is enabled and what am/pm is enabled. Maybe it doesn't.
The component could emit an entire state object (containing active, am, and pm) to indicate to the parent when anything changed. Or the properties could live in the parent and the component would just emit events for each individual change. It all depends on how and where you use the data.
2

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>

4 Comments

Your component updates the parent values directly, then emits a new object to the parent. If you're going to update the values directly, you could do it without the sync. Each watch becomes a one-liner.
@RoyJ I don't think it is updating the values directly. It emits an event with a new Object and the parent modifies the value based on the event. A component shouldn't update the value directly. I suppose the watch could be reformatted to an Object.assign() to make it more compact. Am I misunderstanding something?
@RoyJ is that what the vModel bound checkboxes within the template are doing?
Yes, v-model updates the variables it is tied to. Then the emit triggers the parent to replace the whole object. I've posted a solution that adapts your approach but doesn't v-model props data.
1

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.