0

I want this select multiple to pre-select one option, and not be able to deselect all options.

Whenever the last selected option is deselected it should be reselected. In other words when the user tries to deselect the last selected option it should visually not be deselected.

<template>
  <b-select
    if="Object.keys(doc).length !== 0 /* wait until firebase has loaded */"
    :options="computedOptions"
    v-model="model"
    multiple
    @input="onChange"
  />
</template>

<script>
//import Vue from 'vue'
import { fb } from "../fbconf";

export default {
  name: "MyMultiSelect",
  props: {
    doc: Object, // firestore document
  },
  data() {
    return {
      options: []
    };
  },

  firestore() {
    var options = fb.db.collection("options");
    return {
      options: options
    };
  },

  computed: {
    computedOptions: function() {
      return this.options.map(function(option) {
        return {
          text: option.name,
          value: option.id
        };
      });
    },

    // to make sure mySelectedOptions is an array, before this.doc is loaded
    // I use the following custom model
    // because not using 'get' below causes a warning:
    // [Vue warn]: <select multiple v-model="localValue"> expects an Array value for its binding, but got Undefined
    model: {
      get: function() {
        if (!this.doc.hasOwnProperty('mySelectedOptions')) return []; // empty array before this.doc is loaded
        else return this.doc['mySelectedOptions'];
      },
      set: function(newValue) {
         // here I can prevent the empty array from being stored
         // but visually the user can deselect all options, which is bad UX
         //if (Array.isArray(newValue) && newValue.length > 0) this.doc['mySelectedOptions'] = newValue;
      }
    },
  },

  methods: {
    onChange: function(newValue){
      // I can manually store the array as I want here
      // but I cannot in any way prevent the user from deselecting all options
      if (Array.isArray(newValue) && newValue.length > 0) this.doc['mySelectedOptions'] = newValue;
      else {
        // none of these reselects the last selected option
        var oldValue = this.doc['mySelectedOptions'];
        this.doc['mySelectedOptions'] = this.doc['mySelectedOptions'];
        //this.$forceUpdate();
        //this.$emit("change", newValue);
        //Vue.set(this.doc, 'mySelectedOptions', this.doc['mySelectedOptions']);
      }
    }
  }
};
</script>
Robin Manoli
  • 2,162
  • 2
  • 25
  • 30

1 Answers1

1

You could add watcher and when length becomes 0 just add previous value.

  watch: {
    model(val, oldVal) {
      if(val.length == 0 && oldVal.length > 0) {
        // take only one item in case there's clear button or etc.
        this.model = [oldval[0]];
      }
    }
  }
Edgarasne
  • 129
  • 2
  • this worked when removing the @input, and having the model set function update the array even when it shouldn't be stored: this.doc[this.fieldData.name] = newValue; – Robin Manoli Apr 17 '20 at 10:49
  • this.model could still be an empty array or another value with this setup, if this.doc was incorrect on page load. To fix that I added another watcher for when computedOptions are loaded, which then sets this.model to a default value if it's incorrect. – Robin Manoli Apr 17 '20 at 11:52
  • If code above is actual I'm not sure why you're using computed properties at all. Move "computedOptions" function to "firestore" method and add default value there. – Edgarasne Apr 19 '20 at 07:04