| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- <!--
- 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(label_field)"
- 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"
- :rules="rules"
- @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 lang="ts">
- import {computed, defineComponent, ComputedRef, Ref, ref, watch, onUnmounted, useContext} from '@nuxtjs/composition-api'
- import { AnyJson } from '~/types/interfaces'
- import * as _ from 'lodash'
- import {$objectProperties} from "~/services/utils/objectProperties";
- import {$useError} from "~/composables/form/useError";
- export default defineComponent({
- props: {
- 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: () => []
- },
- },
- setup (props, { emit }) {
- const {app:{i18n}} = useContext()
- const search:Ref<string|null> = ref(null)
- const {error, onChange} = $useError(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, _.debounce(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 itemsByGroup:Array<Array<string>> = []
- for (const item of items){
- if(item){
- if(!itemsByGroup[item[props.group as string]])
- itemsByGroup[item[props.group as string]] = []
- itemsByGroup[item[props.group as string]].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 group est présent, alors on créer le group options header
- if(group !== 'undefined'){
- finalItems.push({header: i18n.t(group as string)})
- }
- //On parcours 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 = $objectProperties.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
- }
- return {
- label_field: props.label ?? props.field,
- itemsToDisplayed,
- search,
- error,
- onChange
- }
- }
- })
- </script>
|