Léonard Krief
Léonard Krief

Reputation: 23

Computed field inside a vue-multiselect v-model?

I am trying to build a multiselect component to nicely display tags in my app.
I receive an objectTags parameter form the parent component, but this field needs to be slightly refactored to be correctly displayed, so I need to make it a computed() object. Here is my code:

<template>
  <div>
    <label class="block text-sm font-medium text-gray-700 mb-2">{{ label }}</label>
    <Field v-slot="{ field, handleChange }" :name="name" :value="veeValue">
      <multiselect
        v-model="value"
        :clear-on-select="true"
        :close-on-select="false"
        :multiple="true"
        :options="extendedOptions"
        :placeholder="placeholder"
        :tag-placeholder="tagPlaceholder"
        :taggable="true"
        :label="labelKey"
        :track-by="trackByKey"
        :open-direction="'below'"
        :name="name"
        @select="(option) => toggleOption(option, field, handleChange)"
        @remove="(option) => toggleOption(option, field, handleChange)"
      >
        <template #tag="{option, remove}">
          <span class="multiselect__tag" :key="option[labelKey] + '-' + option[trackByKey]"
              :style="{ backgroundColor: option.color, color: '#001657' }">
            <span v-text="option[labelKey]"></span>
            <i tabindex="1"
              @keypress.enter.prevent="removeElement(option, remove, field, handleChange)"
              @mousedown.prevent="removeElement(option, remove, field, handleChange)"
              class="multiselect__tag-icon"></i>
          </span>
        </template>
        <template #option="{option}">
          <!-- Child row -->
          <span v-if="option[parentKey]" class="pl-3">
            <span>{{ option[labelKey] }}</span>
            <span class="ml-2">{{ option.category }}</span>
          </span>
          <!-- Parent row -->
          <span v-else :class="topLevelClass">
            <span>{{ option[labelKey] }}</span>
            <span class="ml-2">{{ option.category }}</span>
          </span>
        </template>
      </multiselect>
    </Field>
  </div>
</template>

<script setup>
import Multiselect from "vue-multiselect";
import { computed, ref, onMounted } from "vue";
import { Field } from "vee-validate";

const props = defineProps({
  name: {
    type: String,
    required: true
  },
  categorizedTags: {
    type: Array,
    required: true
  },
  objectTags:{
    type: Array,
    default: [],
  },
  options: {
    type: Array,
    required: true
  },
  selected: {
    type: Array,
    default: [],
  },
  label: String,
  tagPlaceholder: String | null,
  placeholder: String | null,
  labelKey: {
    type: String,
    default: "name"
  },
  childrenKey: {
    type: String,
    default: "children"
  },
  parentKey: {
    type: String,
    default: "parent_id"
  },
  trackByKey: {
    type: String,
    default: "id"
  },
  topLevelClass:{
    type: String,
    default: 'font-bold'
  },
});

const value = ref(props.selected);
const veeValue = (props.selected || []).map((option) => option[props.trackByKey])

function removeElement(option, multiselectRemove, veeField, handleChange) {
  multiselectRemove(option);
  toggleOption(option, veeField, handleChange);
}

function toggleOption(option, veeField, handleChange) {
  const isAlreadyChecked = veeField.value.includes(option[props.trackByKey]);

  handleChange(
    isAlreadyChecked ?
      veeField.value.filter(optionId => optionId !== option[props.trackByKey]) :
      [...veeField.value, option[props.trackByKey]]
  );
}

const extendedOptions = computed(() => {
  let _options = [];
  computedTags.value.forEach((option) => {
    _options.push(option);
    _options.push(...option[props.childrenKey] || []);
  });
  return _options;
});

const computedTags = computed(() => {
  const extractTags = (tags, category) => {
    let allTags = [];

    tags.forEach(tag => {
      allTags.push({
        id: tag.id,
        name: tag.name,
        category: category.name,
        color: category.color
      });

      if (tag.children && tag.children.length > 0) {
        allTags = allTags.concat(extractTags(tag.children, category));
      }
    });

    return allTags;
  };

  let allTags = [];

  props.categorizedTags.forEach(category => {
    if (category.root_tags && category.root_tags.length > 0) {
      allTags = allTags.concat(extractTags(category.root_tags, category));
    }
  });

  return allTags;
});

// const objectComputedTags = computed(() => {
//   get: () => {

//     let allTags = [];
//     if (!props.objectTags) {
//       return allTags;
//     }
    
//     props.objectTags.forEach(tag => {
//       allTags.push({
//         id: tag.id,
//         name: tag.name,
//         category_id: tag.category.id,
//         category: tag.category.name,
//         color: tag.category.color
//       });
//     });
    
//     allTags.sort((a, b) => {
//       if (a.category_id < b.category_id) return -1;
//       else if (a.category_id > b.category_id) return 1;
//       else return a.name.localeCompare(b.name);
//     });
    
//     return allTags;
//   }
//   });


</script>
<style scoped>

</style>

If I try to modify my code to replace the currently used v-model by my new one, I get errors in my console about calling invalid field objectComputedValues.value (even though this field exists).
I am new to Vue so I hope I got it right but this is apparently due to the fact that computed values cannot be passed that easily into v-models.

When I try to modify my code to replace the currently used v-model by my new one (replace props.selected with objectComputedValues.value), I get errors in my console about calling invalid field objectComputedValues.value even though this field exists.
I am new to Vue so I hope I got it right but this is apparently due to the fact that computed values cannot be passed that easily into v-models.
I tried to add a getter and setter to my objectComputedValues callback function but it did not work.
I apparently found a solution using old style Vue, where I should have defined my component in a defineComponent() and my objectComputedValues as a data() field that I would update at mount, but I would prefer to keep my current syntax.
Any help would be appreciated :)

Upvotes: 0

Views: 112

Answers (0)

Related Questions