| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- <!--
- Liste déroulante avec autocompletion
- @see https://vuetifyjs.com/en/components/autocompletes/#usage
- -->
- <template>
- <main>
- <v-autocomplete
- autocomplete="search"
- :value="data"
- :items="itemsToDisplayed"
- :label="$t(fieldLabel)"
- item-text="itemTextDisplay"
- :item-value="itemValue"
- :no-data-text="$t('autocomplete_research')"
- :no-filter="noFilter"
- auto-select-first
- :multiple="multiple"
- :loading="isLoading"
- :return-object="returnObject"
- :search-input.sync="search"
- :prepend-icon="prependIcon"
- :error="error || !!violation"
- :error-messages="errorMessage || violation ? $t(violation) : ''"
- :rules="rules"
- :chips="chips"
- @input="onChange($event)"
- >
- <template v-if="slotText" #item="data">
- <v-list-item-content v-text="data.item.slotTextDisplay"></v-list-item-content>
- </template>
- </v-autocomplete>
- </main>
- </template>
- <script setup lang="ts">
- import {useNuxtApp} from "#app";
- import {computed, ComputedRef, Ref} from "@vue/reactivity";
- import {useFieldViolation} from "~/composables/form/useFieldViolation";
- import {AnyJson} from "~/types/data";
- import {onUnmounted, watch} from "@vue/runtime-core";
- import ObjectUtils from "~/services/utils/objectUtils";
- const props = defineProps({
- label: {
- type: String,
- required: false,
- default: null
- },
- field: {
- type: String,
- required: false,
- default: null
- },
- data: {
- type: [String, Number, Object, Array],
- required: false,
- default: null
- },
- items: {
- type: Array,
- required: false,
- default: () => []
- },
- readonly: {
- type: Boolean,
- required: false
- },
- itemValue: {
- type: String,
- default: 'id'
- },
- itemText: {
- type: Array,
- required: true
- },
- group:{
- type: String,
- required: false,
- default: null
- },
- slotText: {
- type: Array,
- required: false,
- default: null
- },
- returnObject: {
- type: Boolean,
- default: false
- },
- multiple: {
- type: Boolean,
- default: false
- },
- isLoading: {
- type: Boolean,
- default: false
- },
- noFilter: {
- type: Boolean,
- default: false
- },
- prependIcon: {
- type: String
- },
- translate: {
- type: Boolean,
- default: false
- },
- rules: {
- type: Array,
- required: false,
- default: () => []
- },
- chips: {
- type: Boolean,
- default: false
- },
- error: {
- type: Boolean,
- required: false
- },
- errorMessage: {
- type: String,
- required: false,
- default: null
- }
- })
- const { emit } = useNuxtApp()
- const { i18n } = useNuxtApp()
- const search: Ref<string|null> = ref(null)
- const fieldLabel = props.label ?? props.field
- const { violation, onChange } = useFieldViolation(props.field, emit)
- // On reconstruit les items à afficher...
- const itemsToDisplayed: ComputedRef<Array<AnyJson>> = computed(() => {
- const itemsByGroup:Array<Array<string>> = classItemsByGroup(props.items)
- return prepareItemsToDisplayed(itemsByGroup)
- })
- const unwatch = watch(
- search,
- useDebounce(async (newResearch, oldResearch) => {
- if(newResearch !== oldResearch && oldResearch !== null)
- emit('research', newResearch)
- }, 500)
- )
- onUnmounted(() => {
- unwatch()
- })
- /**
- * On construit l'Array à double entrée contenant les groups (headers) et les propositions
- *
- * @param items
- */
- const classItemsByGroup = (items:Array<any>): Array<Array<string>> => {
- const group = props.group as string
- const itemsByGroup: Array<Array<string>> = []
- for (const item of items) {
- if (item) {
- if (!itemsByGroup[item[group]]) {
- itemsByGroup[item[group]] = []
- }
- itemsByGroup[item[group]].push(item)
- }
- }
- return itemsByGroup
- }
- /**
- * Construction de l'Array JSON contenant toutes les propositions à afficher dans le select
- *
- * @param itemsByGroup
- */
- const prepareItemsToDisplayed = (itemsByGroup: Array<Array<string>>): Array<AnyJson> => {
- let finalItems: Array<AnyJson> = []
- for (const group in itemsByGroup) {
- // Si un groupe est présent, alors on créé le groupe options header
- if (group !== 'undefined') {
- finalItems.push({header: i18n.t(group as string)})
- }
- // On parcourt les items pour préparer les texts / slotTexts à afficher
- finalItems = finalItems.concat(itemsByGroup[group].map((item: any) => {
- const slotTextDisplay: Array<string> = []
- const itemTextDisplay: Array<string> = []
- item = ObjectUtils.cloneAndFlatten(item)
- // Si on souhaite avoir un texte différent dans les propositions que dans la sélection finale de select
- if (props.slotText) {
- for (const text of props.slotText) {
- slotTextDisplay.push(props.translate ? i18n.t(item[text as string]) : item[text as string])
- }
- }
- for (const text of props.itemText) {
- itemTextDisplay.push(props.translate ? i18n.t(item[text as string]) : item[text as string])
- }
- // On reconstruit l'objet
- return Object.assign({}, item, { itemTextDisplay: itemTextDisplay.join(' '), slotTextDisplay: slotTextDisplay.join(' ') })
- }))
- }
- return finalItems
- }
- </script>
|