diff --git a/src/components/Feature/Edit/FeatureExtraForm.vue b/src/components/Feature/Edit/FeatureExtraForm.vue index bf5f514db22c0161868453759a3020c57760fc6c..e6ce1e41ac2de211ff9c6e4153f9ae4f2c2fda6c 100644 --- a/src/components/Feature/Edit/FeatureExtraForm.vue +++ b/src/components/Feature/Edit/FeatureExtraForm.vue @@ -160,7 +160,6 @@ <script> import { mapState, mapActions, mapMutations } from 'vuex'; import Multiselect from 'vue-multiselect'; -import { isEqual } from 'lodash'; import Dropdown from '@/components/Dropdown.vue'; @@ -223,25 +222,31 @@ export default { }, 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) { - // In fast edition, prerecordedlist is not updated, thus the value stay the same - // in this case we renitialize the field. This doesn't impact usual behavior if user reselect the same option + // Check if the field object exists. if (this.field) { + // Handle pre-recorded list fields specifically. if (this.field.field_type === 'pre_recorded_list') { - // if both values are defined but their values changed, value in form should be updated - if (newValue && oldValue && (newValue.label !== oldValue.label || newValue !== oldValue) // prevent case of having a label or directly the value - // if any of them is undefined, the form value should be updated, because in this case the value changed - // otherwise (if they are both undefined) the watcher would not called, thus we don't need to prevent this case - || !newValue || !oldValue) - { - this.initPrerecordedXform(); + // 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') { - // if array values changed, value in form should be updated, otherwise at edition in fast browsing mode, it would overide current value with previous value loaded at component creation - if (isEqual(newValue, oldValue)) { - this.initMultipleCheckboxXform(); - } + // 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; } }, @@ -310,26 +315,39 @@ export default { this.selectedPrerecordedValue = null; } }, - - updateStore_extra_form(evt) { + /** + * 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 (evt && evt.target) { // if called from the template + // 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 = evt.target.checked; //* if checkbox, use "checked" + newValue = val.target.checked; } else { - newValue = evt.target.value; + // For other input types, use the 'value' property. + newValue = val.target.value; } - } else if (this.field.field_type === 'multi_choices_list') { // special case where the value is stored in the state + } 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 newValue was sent directly - newValue = evt; + } else { + // If the function is called directly with a value (not from an event). + newValue = val; } - //* set the value selected + // 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 { - this.UPDATE_EXTRA_FORM({ ...this.field, ['value']: newValue }); + // Otherwise, update the Vuex store with the new value for the extra form field. + this.UPDATE_EXTRA_FORM({ ...this.field, value: newValue }); } } }, @@ -361,15 +379,30 @@ export default { 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.push(name); + 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> diff --git a/src/store/modules/feature.store.js b/src/store/modules/feature.store.js index 910ed900bc70754fff43b3f40b7164566dfac5d0..cbeab55f9b587a24ab1ca3049e48d626afc36b7d 100644 --- a/src/store/modules/feature.store.js +++ b/src/store/modules/feature.store.js @@ -423,24 +423,32 @@ const feature = { return false; }); }, - + /** + * Initializes extra forms based on the current feature type and its custom fields. + * This function retrieves custom fields for the current feature type, assigns values to them based on the current feature's properties, + * and commits them to the store to be displayed in the form. + * + * @param {Object} context - The Vuex action context, including state, rootGetters, and commit function. + */ INIT_EXTRA_FORMS({ state, rootGetters, commit }) { - const feature = state.currentFeature; - const featureType = rootGetters['feature-type/feature_type']; - const customFields = featureType.customfield_set; + const feature = state.currentFeature; // Current feature being edited or viewed. + const featureType = rootGetters['feature-type/feature_type']; // Retrieves the feature type from root getters. + const customFields = featureType.customfield_set; // Custom fields defined for the feature type. if (customFields) { - //* retrieve 'name', 'options', 'position' from current feature_type data to display in the form commit('SET_EXTRA_FORMS', - activateFieldsNforceValues( + activateFieldsNforceValues( // A hypothetical function to activate fields and enforce their values. customFields.map((field) => { - //* add value field to extra forms from feature_type and existing values if feature is defined - field.value = feature.properties ? feature.properties[field.name] : findXformValue(feature, field); - //* if a boolean has a null value, set it to false to allow comparison with false later on (for instance with forced value) - if (field.field_type === 'boolean' && field.value === null) field.value = false; - return field; + // Determines the initial value for the field + let value = feature.properties ? feature.properties[field.name] : findXformValue(feature, field); + // If the field is a boolean and the value is null, sets it to false + if (field.field_type === 'boolean' && value === null) { + value = false; + } + // Returns a new object with the updated value and the rest of the field's properties + return { ...field, value }; }) - ).sort((a, b) => a.position - b.position) //* order according to user defined position + ).sort((a, b) => a.position - b.position) // Sorts fields by their user-defined position. ); } }, diff --git a/src/views/Feature/FeatureDetail.vue b/src/views/Feature/FeatureDetail.vue index e026674d96e069f8287afa7a1fe9fbb9699c20c5..a26746e9a43f492076bac483bc1cbf1916fe8139 100644 --- a/src/views/Feature/FeatureDetail.vue +++ b/src/views/Feature/FeatureDetail.vue @@ -269,23 +269,36 @@ export default { ...mapState('map', [ 'basemaps', ]), - + /** + * Checks if there are any unsaved changes in the form compared to the current feature's properties. + * This function is useful for prompting the user before they navigate away from a page with unsaved changes. + * + * @returns {boolean} - Returns true if there are unsaved changes; otherwise, returns false. + */ hasUnsavedChange() { - if (this.project.fast_edition_mode && this.form && this.currentFeature && this.currentFeature.properties) { + // Ensure we are in edition mode and all required objects are present. + if (this.project && this.project.fast_edition_mode && + this.form && this.currentFeature && this.currentFeature.properties) { + // Check for changes in title, description, and status. if (this.form.title !== this.currentFeature.properties.title) return true; if (this.form.description.value !== this.currentFeature.properties.description) return true; if (this.form.status.value !== this.currentFeature.properties.status) return true; + + // Iterate over extra forms to check for any changes. for (const xForm of this.$store.state.feature.extra_forms) { const originalValue = this.currentFeature.properties[xForm.name]; + // Check if the form value has changed, considering edge cases for undefined, null, or empty values. if ( - !isEqual(xForm.value, originalValue) && // check if values changed - !(!xForm.value && !originalValue) // if the value is not defined, null, an empty string or a boolean with false, no need to warn the user before leaving page without saving (when a conditional field is unactivated or in case of boolean not set by user the value is null which is equivalent to false) + !isEqual(xForm.value, originalValue) && // Check if values have changed. + !(!xForm.value && !originalValue) // Ensure both aren't undefined/null/empty, treating null as equivalent to false for unactivated conditionals or unset booleans. ) { - console.log('In custom form [',xForm.name, '], the current form value [', xForm.value, '] differs from original value [', originalValue, ']'); + // Log the difference for debugging purposes. + console.log(`In custom form [${xForm.name}], the current form value [${xForm.value}] differs from original value [${originalValue}]`); return true; } } } + // If none of the above conditions are met, return false indicating no unsaved changes. return false; },