Wednesday, 13 September 2023

Adding native validation to Vue Headless UI Listbox (Select) component?

I am trying to add Native validation support to Vue3 or Nuxt 3 Vue Headless UI Listbox (Select) component. As per my understanding its not directly supported so I would like to know which is the best and easy way.

Can you please guide me on how to adopt native validation to ListBox?

DropDown.vue:

<template>
  <div class="flex-col items-center">
    <label class="w-1/6 pb-1 text-sm font-semibold"></label>
    <Listbox
      v-model="item"
      @update:modelValue="onItemChange"
      :required="required"
      :default-value="defaultSelectionRef"
    >
      <div class="relative z-10 w-full">
        <ListboxButton
          class="dropdown relative w-full pl-3 text-sm text-left sm:text-sm rounded h-12 border border-gray-300 dark:border-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600"
          :required="required"
        >
          <span class="block truncate dark:text-white"></span>
          <span
            class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
          >
            <Icon icon="fe:arrow-down" />
          </span>
        </ListboxButton>
        <transition
          leave-active-class="transition duration-100 ease-in"
          leave-from-class="opacity-100"
          leave-to-class="opacity-0"
        >
          <ListboxOptions
            class="absolute mt-1 w-full overflow-auto rounded shadow-md bg-white py-1 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm dark:bg-slate-800 dark:text-white"
            :required="required"
          >
            <ListboxOption
              v-slot="{ active }"
              v-for="option in options"
              :key="option.value"
              :value="option"
              as="template"
              :disabled="option.disabled"
              :required="required"
            >
              <li
                :class="[
                  active ? 'bg-[#E5F0FC] dark:bg-slate-700' : '',
                  'relative select-none py-2 pl-4 pr-4 cursor-pointer',
                ]"
                :required="required"
              >
                <span :class="option.class"></span>
              </li>
            </ListboxOption>
          </ListboxOptions>
        </transition>
      </div>
    </Listbox>
  </div>
</template>

<script setup>
import { Icon } from "@iconify/vue";
import {
  Listbox,
  ListboxButton,
  ListboxOptions,
  ListboxOption,
} from "@headlessui/vue";

const props = defineProps({
  label: {
    type: String, // label for the dropdown field
    required: false,
  },
  defaultSelection: {
    type: Object, // default selected item in drop down
    required: false,
  },
  options: {
    type: Array, // array of options in the drop down
    required: true,
  },
  required: {
    type: Boolean, // condition based on which dropdown becomes required or not
    required: false,
  },
  modelValue: {
    type: [String, Number, Object, null], // name of the corresponding model for dropdown field
    required: false,
  },
});

const defaultSelectionRef = computed(() => {
  //Get existing model value for the dropdown
  const modelValue = props.modelValue;

  //If there is no existing value return 1st element of dropdown options array
  if (modelValue === undefined || modelValue === null) {
    return props.options[0];
  }

  //If there is existing value then return matching element from options array
  return (
    props.options.find((obj) => obj.value === modelValue) ||
    props.options.find((obj) => obj.value === modelValue.value)
  );
});

const item = ref(defaultSelectionRef.value);

const emits = defineEmits(["onItemChange", "update:modelValue"]);

watch(defaultSelectionRef, (newValue) => {
  item.value = newValue;
});

// Emit the updated modelValue when the selectedOption changes
watchEffect(() => {
  emits("update:modelValue", item.value.value);
});

const onItemChange = () => {
  emits("onItemChange", props.model, item.value);
};
</script>

Usage in pages/test.vue:

<template>
    <form @submit.prevent="submitForm">
      <div class="overflow-x-auto shadow-md sm:rounded-lg">
        <table class="w-full text-sm dark:text-white">
          <caption />
          <th id="tableHeader" />
          <tbody>
            <tr class="bg-white dark:bg-gray-800 dark:border-gray-700">
              <td
                class="px-4 py-4 whitespace-nowrap text-center bg-gray-500 text-white dark:text-black"
              >
                <DropDown
                  v-model="formData.type"
                  :label="$t('pages.modal.select-type')"
                  :options="options"
                  :required="true"
                />
              </td>
            </tr>
          </tbody>
        </table>
      </div>
  
      <button
        type="submit"
        class="mt-2 mb-2 text-green-700 border-green-700 focus:ring-green-300 hover:bg-green-700 dark:hover:bg-green-500 dark:focus:ring-green-800 dark:border-green-500 dark:text-green-500 block rounded-full hover:text-white border focus:ring-4 focus:outline-none font-medium text-sm px-5 py-2.5 text-center dark:hover:text-white"
      >
        
      </button>
    </form>
  </template>
  
  <script setup>
  //Import static values for dropdowns
  import {
    options,
  } from "~/public/Static/DefaultValues";
  const formData = ref({});
  
  const submitForm = async (event) => {
    console.log("Form submitted successfully");
  };
  </script>
  
  <style>
  </style>

I want to prevent the form from being submitted if the user has not selected the value. I am trying to figure out a direct approach to see if I can directly validate and show native message. I know it can be done by some additional methods and coding but trying to avoid it as much as possible and finding best and easy way.



from Adding native validation to Vue Headless UI Listbox (Select) component?

No comments:

Post a Comment