<template> <div :class="['field', { 'disabled': field.disabled }]" :data-field_type="field.field_type" data-test="extra-form" name="extra-form" > <div v-if="field && field.field_type === 'boolean'" :class="['ui checkbox', { 'disabled': field.disabled }]" > <!-- JSON.parse is used in case of receiving a string 'true' or 'false'--> <input :id="field.name" type="checkbox" :checked="JSON.parse(field.value)" :name="field.name" @change="updateStore_extra_form" > <label :for="field.name"> {{ displayLabels ? field.label : '' }} </label> </div> <template v-else> <label v-if="displayLabels" :for="field.name" :class="{ required: field.is_mandatory }" > {{ field.label }} </label> <input v-if="field && field.field_type === 'char'" :id="field.name" :value="field.value" type="text" :name="field.name" :required="field.is_mandatory" @blur="updateStore_extra_form" > <textarea v-else-if="field && field.field_type === 'text'" :value="field.value" :name="field.name" :required="field.is_mandatory" rows="3" @blur="updateStore_extra_form" /> <input v-else-if="field && field.field_type === 'integer'" :id="field.name" :value="field.value" type="number" :name="field.name" :required="field.is_mandatory" @change="updateStore_extra_form" > <input v-else-if="field && field.field_type === 'decimal'" :id="field.name" :value="field.value" type="number" step=".01" :name="field.name" :required="field.is_mandatory" @change="updateStore_extra_form" > <input v-else-if="field && field.field_type === 'date'" :id="field.name" :value="field.value" type="date" :name="field.name" :required="field.is_mandatory" @change="updateStore_extra_form" > <Dropdown v-else-if="field && field.field_type === 'list'" :options="field.options" :selected="selected_extra_form_list" :selection.sync="selected_extra_form_list" :required="field.is_mandatory" :search="true" :clearable="true" /> <div v-else-if="field && field.field_type === 'multi_choices_list'" class="checkbox_list" > <div v-for="option in field.options" :key="option.id || option" class="ui checkbox" > <input :id="option.id || option" type="checkbox" :checked="field.value && field.value.includes(option.id || option)" :name="option.id || option" @change="selectMultipleCheckbox" > <label :for="option.id || option"> {{ option.name || option }} </label> </div> </div> <Multiselect v-else-if="field && field.field_type === 'pre_recorded_list'" v-model="selectedPrerecordedValue" :options="selectedPrerecordedListValues[field.options[0]] || []" :options-limit="10" :allow-empty="!field.is_mandatory" track-by="label" label="label" :reset-after="false" select-label="" selected-label="" deselect-label="" :searchable="true" :placeholder="'Recherchez une valeur de la liste pré-définie ...'" :show-no-results="true" :loading="loadingPrerecordedListValues" :clear-on-select="false" :preserve-search="false" @search-change="search" @select="selectPrerecordedValue" > <template slot="clear"> <div v-if="selectedPrerecordedValue" class="multiselect__clear" @click.prevent.stop="clearPrerecordedValue" > <i class="close icon" aria-hidden="true" /> </div> </template> <span slot="noResult"> Aucun résultat. </span> <span slot="noOptions"> Saisissez les premiers caractères ... </span> </Multiselect> </template> </div> </template> <script> import { mapState, mapActions, mapMutations } from 'vuex'; import Multiselect from 'vue-multiselect'; import Dropdown from '@/components/Dropdown.vue'; export default { name: 'ExtraForm', components: { Dropdown, Multiselect }, props: { field: { type: Object, default: null, }, useValueOnly: { type: Boolean, default: false, } }, data() { return { error: null, prerecordedListSearchQuery: null, loadingPrerecordedListValues: false, selectedPrerecordedValue: null, selectedMultipleCheckbox: [], }; }, computed: { ...mapState('feature-type', [ 'selectedPrerecordedListValues' ]), ...mapState('feature', [ 'extra_forms', ]), selected_extra_form_list: { get() { return this.field.value || ''; }, set(newValue) { //* set the value selected in the dropdown if (this.useValueOnly) { this.$emit('update:value', newValue.id || newValue); } else { const newExtraForm = this.field; newExtraForm['value'] = newValue; this.$store.commit('feature/UPDATE_EXTRA_FORM', newExtraForm); } }, }, displayLabels() { return this.$route.name === 'editer-signalement' || this.$route.name === 'ajouter-signalement' || this.$route.name === 'editer-attribut-signalement'; }, }, watch: { /** * Watches for changes in the 'field.value' and updates the form state accordingly. * This watcher handles specific field types, ensuring their values are correctly initialized * and updated in scenarios like fast edition mode where certain values might not be auto-refreshed. * * @param {*} newValue - The new value of the field. * @param {*} oldValue - The previous value of the field before the change. */ 'field.value': function(newValue, oldValue) { // Check if the field object exists. if (this.field) { // Handle pre-recorded list fields specifically. if (this.field.field_type === 'pre_recorded_list') { // Update the form value if both new and old values are defined and different, // or if either value is undefined, indicating a change. if ((newValue && oldValue && (newValue.label !== oldValue.label || newValue !== oldValue)) || !newValue || !oldValue) { this.initPrerecordedXform(); // Reinitialize the field to reflect the updated value. } } else if (this.field.field_type === 'multi_choices_list') { // For multi-choice lists, reinitialize the field if the array values have changed. // This is crucial in fast edition modes to prevent overriding the current value with a stale value. this.initMultipleCheckboxXform(); } // Reset any error states for the field. this.error = null; } }, prerecordedListSearchQuery(newValue) { this.loadingPrerecordedListValues = true; this.GET_SELECTED_PRERECORDED_LIST_VALUES({ name: this.field.options[0], pattern: newValue }) .then(() => { this.loadingPrerecordedListValues = false; }) .catch(() => { this.loadingPrerecordedListValues = false; }); } }, created() { if (this.field) { if (this.field.field_type === 'pre_recorded_list') { this.initPrerecordedXform(); } else if (this.field.field_type === 'multi_choices_list') { this.initMultipleCheckboxXform(); } } }, mounted() { // autoset field to false if is a boolean, since user doesn't need to select it, when false value is expected if (this.field.field_type === 'boolean' && (this.field.value === undefined || this.field.value === null)) { this.updateStore_extra_form(false); } }, methods: { ...mapActions('feature-type', [ 'GET_SELECTED_PRERECORDED_LIST_VALUES' ]), ...mapMutations('feature', [ 'UPDATE_EXTRA_FORM', 'SET_EXTRA_FORMS', ]), initMultipleCheckboxXform() { this.selectedMultipleCheckbox = typeof this.field.value === 'string' ? this.field.value.split(',') : this.field.value || []; }, initPrerecordedXform() { const { options, value } = this.field; this.loadingPrerecordedListValues = true; this.GET_SELECTED_PRERECORDED_LIST_VALUES({ name: options[0], pattern: '' }) .then(() => { this.loadingPrerecordedListValues = false; }) .catch(() => { this.loadingPrerecordedListValues = false; }); if (value) { this.selectedPrerecordedValue = { label: value.label ? value.label : value }; } else { this.selectedPrerecordedValue = null; } }, /** * Updates the Vuex store or component state with the new value for a form field. * This function handles different types of form fields including boolean, multi-choice lists, and others. * * @param {Event|*} val - The new value or an event object. */ updateStore_extra_form(val) { // Check if the field object is defined. if (this.field) { let newValue; // If the function is triggered by an event from a template input. if (val && val.target) { // For boolean fields (like checkboxes), use the 'checked' property. if (this.field.field_type === 'boolean') { newValue = val.target.checked; } else { // For other input types, use the 'value' property. newValue = val.target.value; } } else if (this.field.field_type === 'multi_choices_list') { // For multi-choice lists, the value is stored in component state. newValue = this.selectedMultipleCheckbox; } else { // If the function is called directly with a value (not from an event). newValue = val; } // Set the new value for the field. if (this.useValueOnly) { // If the component is used to update directly a returned value, emit an event with the new value. this.$emit('update:value', newValue); } else { // Otherwise, update the Vuex store with the new value for the extra form field. this.UPDATE_EXTRA_FORM({ ...this.field, value: newValue }); } } }, checkForm() { let isValid = true; if (this.field && this.field.is_mandatory && !this.field.value) { isValid = false; this.error = 'Ce champ est obligatoire'; } else { this.error = null; } return isValid; }, search(text) { this.prerecordedListSearchQuery = text; }, selectPrerecordedValue(e) { this.selectedPrerecordedValue = e; this.prerecordedListSearchQuery = null; this.updateStore_extra_form({ target: { value: this.selectedPrerecordedValue.label } }); }, clearPrerecordedValue() { this.selectedPrerecordedValue = null; this.prerecordedListSearchQuery = null; this.updateStore_extra_form({ target: { value: null } }); }, /** * Handles the selection and deselection of checkboxes in a form. * This function updates an array to track the selected checkboxes by their names. * It's typically called on the change event of each checkbox. * * @param {Event} e - The event object from the checkbox input. */ selectMultipleCheckbox(e) { // Destructure the 'checked' status and 'name' of the checkbox from the event target. const { checked, name } = e.target; // If the checkbox is checked, add its name to the array of selected checkboxes. // Cloning the array to allow unsaved changes detection (it wasn't working with Array.push) if (checked) { this.selectedMultipleCheckbox = [...this.selectedMultipleCheckbox, name]; } else { // If the checkbox is unchecked, remove its name from the array. this.selectedMultipleCheckbox = this.selectedMultipleCheckbox.filter((el) => el !== name); } // Call a method to update the Vuex store or component state with the latest selection. this.updateStore_extra_form(); }, }, }; </script> <style lang="less" scoped> label.required:after { content: ' *'; color: rgb(209, 0, 0); } .checkbox_list { display: flex; flex-direction: column; .ui.checkbox { margin: .25rem 0; font-weight: normal; } } </style> <style> .multiselect__placeholder { position: absolute; width: calc(100% - 48px); overflow: hidden; text-overflow: ellipsis; } .multiselect__tags { position: relative; } /* keep font-weight from overide of semantic classes */ .multiselect__placeholder, .multiselect__content, .multiselect__tags { font-weight: initial !important; } /* keep placeholder eigth */ .multiselect .multiselect__placeholder { margin-bottom: 9px !important; padding-top: 1px; } /* keep placeholder height when opening dropdown without selection */ input.multiselect__input { padding: 3px 0 0 0 !important; } /* keep placeholder height when opening dropdown with already a value selected */ .multiselect__tags .multiselect__single { padding: 1px 0 0 0 !important; margin-bottom: 9px; } </style>