Autocomplete.vue 5.5 KB

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