Autocomplete.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <!--
  2. Liste déroulante avec autocompletion
  3. @see https://vuetifyjs.com/en/components/autocompletes/#usage
  4. -->
  5. <template>
  6. <main>
  7. <v-autocomplete
  8. autocomplete="search"
  9. :value="data"
  10. :items="itemsToDisplayed"
  11. :label="$t(label_field)"
  12. item-text="itemTextDisplay"
  13. :item-value="itemValue"
  14. :no-data-text="$t('autocomplete_research')"
  15. :no-filter="noFilter"
  16. auto-select-first
  17. :multiple="multiple"
  18. :loading="isLoading"
  19. :return-object="returnObject"
  20. :search-input.sync="search"
  21. :prepend-icon="prependIcon"
  22. :error="error"
  23. :rules="rules"
  24. :menu-props="menuProps"
  25. @input="onChange($event)"
  26. >
  27. <template v-if="slotText" #item="data">
  28. <v-list-item-content v-text="data.item.slotTextDisplay"></v-list-item-content>
  29. </template>
  30. </v-autocomplete>
  31. </main>
  32. </template>
  33. <script lang="ts">
  34. import {computed, defineComponent, ComputedRef, Ref, ref, watch, onUnmounted, useContext, onMounted} from '@nuxtjs/composition-api'
  35. import { AnyJson } from '~/types/interfaces'
  36. import * as _ from 'lodash'
  37. import {$objectProperties} from "~/services/utils/objectProperties";
  38. import {$useError} from "~/composables/form/useError";
  39. export default defineComponent({
  40. props: {
  41. label: {
  42. type: String,
  43. required: false,
  44. default: null
  45. },
  46. field: {
  47. type: String,
  48. required: false,
  49. default: null
  50. },
  51. data: {
  52. type: [String, Number, Object, Array],
  53. required: false,
  54. default: null
  55. },
  56. items: {
  57. type: Array,
  58. required: false,
  59. default: () => []
  60. },
  61. readonly: {
  62. type: Boolean,
  63. required: false
  64. },
  65. itemValue: {
  66. type: String,
  67. default: 'id'
  68. },
  69. itemText: {
  70. type: Array,
  71. required: true
  72. },
  73. group:{
  74. type: String,
  75. required: false,
  76. default: null
  77. },
  78. slotText: {
  79. type: Array,
  80. required: false,
  81. default: null
  82. },
  83. returnObject: {
  84. type: Boolean,
  85. default: false
  86. },
  87. multiple: {
  88. type: Boolean,
  89. default: false
  90. },
  91. isLoading: {
  92. type: Boolean,
  93. default: false
  94. },
  95. noFilter: {
  96. type: Boolean,
  97. default: false
  98. },
  99. prependIcon: {
  100. type: String
  101. },
  102. translate: {
  103. type: Boolean,
  104. default: false
  105. },
  106. menuProps: {
  107. type: Object,
  108. default: false
  109. },
  110. rules: {
  111. type: Array,
  112. required: false,
  113. default: () => []
  114. },
  115. },
  116. setup (props, { emit }) {
  117. const {app:{i18n}} = useContext()
  118. const search:Ref<string|null> = ref(null)
  119. const {error, onChange} = $useError(props.field, emit)
  120. // On reconstruit les items à afficher...
  121. const itemsToDisplayed: ComputedRef<Array<AnyJson>> = computed(() => {
  122. const itemsByGroup:Array<Array<string>> = classItemsByGroup(props.items)
  123. return prepareItemsToDisplayed(itemsByGroup)
  124. })
  125. const unwatch = watch(search, _.debounce(async (newResearch, oldResearch) => {
  126. if(newResearch !== oldResearch && oldResearch !== null)
  127. emit('research', newResearch)
  128. }, 500))
  129. onUnmounted(() => {
  130. unwatch()
  131. })
  132. /**
  133. * On construit l'Array à double entrée contenant les groups (headers) et les propositions
  134. * @param items
  135. */
  136. const classItemsByGroup = (items:Array<any>): Array<Array<string>> => {
  137. const itemsByGroup:Array<Array<string>> = []
  138. for (const item of items){
  139. if(item){
  140. if(!itemsByGroup[item[props.group as string]])
  141. itemsByGroup[item[props.group as string]] = []
  142. itemsByGroup[item[props.group as string]].push(item)
  143. }
  144. }
  145. return itemsByGroup
  146. }
  147. /**
  148. * Construction de l'Array JSON contenant toutes les propositions à afficher dans le select
  149. * @param itemsByGroup
  150. */
  151. const prepareItemsToDisplayed = (itemsByGroup:Array<Array<string>>):Array<AnyJson> => {
  152. let finalItems:Array<AnyJson> = []
  153. for(const group in itemsByGroup){
  154. //Si un group est présent, alors on créer le group options header
  155. if(group !== 'undefined'){
  156. finalItems.push({header: i18n.t(group as string)})
  157. }
  158. //On parcours les items pour préparer les texts/slotTexts à afficher
  159. finalItems = finalItems.concat(itemsByGroup[group].map((item: any) => {
  160. const slotTextDisplay: Array<string> = []
  161. const itemTextDisplay: Array<string> = []
  162. item = $objectProperties.cloneAndFlatten(item)
  163. //Si on souhaite avoir un texte différent dans les propositions que dans la sélection finale de select
  164. if(props.slotText){
  165. for (const text of props.slotText) {
  166. slotTextDisplay.push(props.translate ? i18n.t(item[text as string]) : item[text as string])
  167. }
  168. }
  169. for (const text of props.itemText) {
  170. itemTextDisplay.push(props.translate ? i18n.t(item[text as string]) : item[text as string])
  171. }
  172. //On reconstruit l'objet
  173. return Object.assign({}, item, { itemTextDisplay: itemTextDisplay.join(' '), slotTextDisplay: slotTextDisplay.join(' ') })
  174. }))
  175. }
  176. return finalItems
  177. }
  178. return {
  179. label_field: props.label ?? props.field,
  180. itemsToDisplayed,
  181. search,
  182. error,
  183. onChange
  184. }
  185. }
  186. })
  187. </script>