Autocomplete.vue 5.6 KB

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