Performance improvements for groups within v-form (#14188)

* added explicit change check before updating the internal value

* bunch of attempted fixes

* Revert "bunch of attempted fixes"

This reverts commit 17b7a5340f.

* nested v-form performance improvements
preventing re-rendering of some groups

* tries to fix accordion always re-rendering

* abnstracted the fix to its own functions

* abstracted the fix to its own functions

* fixes bug with rendering and saving data in accordion groups

* removed unneeded if statement

Co-authored-by: ian <licitdev@gmail.com>
This commit is contained in:
Brainslug
2022-07-07 15:55:35 +02:00
committed by GitHub
parent c3a19c44cb
commit e3643a2c5d
4 changed files with 103 additions and 59 deletions

View File

@@ -71,7 +71,7 @@
import { getJSType } from '@/utils/get-js-type';
import { Field, ValidationError } from '@directus/shared/types';
import { isEqual } from 'lodash';
import { computed, ref } from 'vue';
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import FormFieldInterface from './form-field-interface.vue';
import FormFieldLabel from './form-field-label.vue';
@@ -117,22 +117,7 @@ const isDisabled = computed(() => {
return false;
});
const defaultValue = computed(() => {
const value = props.field?.schema?.default_value;
if (value !== undefined) return value;
return undefined;
});
const internalValue = computed(() => {
if (props.modelValue !== undefined) return props.modelValue;
if (props.initialValue !== undefined) return props.initialValue;
return defaultValue.value;
});
const isEdited = computed<boolean>(() => {
return props.modelValue !== undefined && isEqual(props.modelValue, props.initialValue) === false;
});
const { internalValue, isEdited, defaultValue } = useComputedValues();
const { showRaw, rawValue, copyRaw, pasteRaw } = useRaw();
@@ -221,6 +206,34 @@ function useRaw() {
return { showRaw, rawValue, copyRaw, pasteRaw };
}
function useComputedValues() {
const defaultValue = computed<any>(() => props.field?.schema?.default_value);
const internalValue = ref<any>(getInternalValue());
const isEdited = ref<boolean>(getIsEdited());
watch(
() => props.modelValue,
() => {
const newVal = getInternalValue();
if (!isEqual(internalValue.value, newVal)) {
internalValue.value = newVal;
}
isEdited.value = getIsEdited();
}
);
return { internalValue, isEdited, defaultValue };
function getInternalValue(): any {
if (props.modelValue !== undefined) return props.modelValue;
if (props.initialValue !== undefined) return props.initialValue;
return defaultValue.value;
}
function getIsEdited(): boolean {
return props.modelValue !== undefined && isEqual(props.modelValue, props.initialValue) === false;
}
}
</script>
<style lang="scss" scoped>

View File

@@ -79,7 +79,7 @@ import { applyConditions } from '@/utils/apply-conditions';
import { extractFieldFromFunction } from '@/utils/extract-field-from-function';
import { Field, FieldMeta, ValidationError } from '@directus/shared/types';
import { assign, cloneDeep, isEqual, isNil, omit, pick } from 'lodash';
import { computed, defineComponent, onBeforeUpdate, PropType, provide, ref, watch, unref } from 'vue';
import { computed, defineComponent, onBeforeUpdate, PropType, provide, ref, watch, Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FormField from './form-field.vue';
import ValidationErrors from './validation-errors.vue';
@@ -150,19 +150,7 @@ export default defineComponent({
setup(props, { emit }) {
const { t } = useI18n();
const fieldsStore = useFieldsStore();
const fields = computed(() => {
if (props.collection) {
return fieldsStore.getFieldsForCollection(props.collection);
}
if (props.fields) {
return props.fields;
}
throw new Error('[v-form]: You need to pass either the collection or fields prop.');
});
const fields = useComputedFields();
const values = computed(() => {
return Object.assign({}, props.initialValues, props.modelValue);
@@ -328,6 +316,35 @@ export default defineComponent({
}
}
function useComputedFields(): Ref<Field[]> {
const fieldsStore = useFieldsStore();
const fields = ref<Field[]>(getFields());
watch(
() => props.fields,
() => {
const newVal = getFields();
if (!isEqual(fields.value, newVal)) {
fields.value = newVal;
}
}
);
return fields;
function getFields(): Field[] {
if (props.collection) {
return fieldsStore.getFieldsForCollection(props.collection);
}
if (props.fields) {
return props.fields;
}
throw new Error('[v-form]: You need to pass either the collection or fields prop.');
}
}
function setValue(fieldKey: string, value: any) {
const field = formFields.value?.find((field) => field.field === fieldKey);

View File

@@ -106,19 +106,8 @@ export default defineComponent({
const fieldsInSection = computed(() => {
return props.fields
.filter((field) => {
if (field.meta?.group === props.group && field.meta?.id !== props.field.meta?.id) return false;
return true;
})
.map((field) => {
if (field.meta?.id === props.field.meta?.id) {
return merge({}, field, {
hideLabel: true,
});
}
return field;
});
.filter((field) => field.meta?.group === props.group && field.meta?.id === props.field.meta?.id)
.map((field) => merge({}, field, { hideLabel: true }));
});
const edited = computed(() => {

View File

@@ -1,11 +1,11 @@
<template>
<v-item-group v-model="selection" scope="group-accordion" class="group-accordion" :multiple="accordionMode === false">
<accordion-section
v-for="accordionField in rootFields"
v-for="accordionField in groupFields"
:key="accordionField.field"
:field="accordionField"
:fields="fields"
:values="values"
:fields="groupFields"
:values="groupValues"
:initial-values="initialValues"
:disabled="disabled"
:batch-mode="batchMode"
@@ -24,7 +24,7 @@
<script lang="ts">
import { Field } from '@directus/shared/types';
import { defineComponent, PropType, computed, ref, watch } from 'vue';
import { defineComponent, PropType, ref, watch } from 'vue';
import { ValidationError } from '@directus/shared/types';
import AccordionSection from './accordion-section.vue';
import { isEqual } from 'lodash';
@@ -77,7 +77,6 @@ export default defineComponent({
type: String,
default: null,
},
accordionMode: {
type: Boolean,
default: true,
@@ -90,21 +89,18 @@ export default defineComponent({
},
emits: ['apply'],
setup(props) {
const rootFields = computed(() => {
return props.fields.filter((field) => field.meta?.group === props.field.meta?.field);
});
const selection = ref<string[]>([]);
const { groupFields, groupValues } = useComputedGroup();
watch(
() => props.start,
(start) => {
if (start === 'opened') {
selection.value = rootFields.value.map((field) => field.field);
selection.value = groupFields.value.map((field) => field.field);
}
if (start === 'first') {
selection.value = [rootFields.value[0].field];
selection.value = [groupFields.value[0].field];
}
},
{ immediate: true }
@@ -116,21 +112,50 @@ export default defineComponent({
if (!props.validationErrors) return;
if (isEqual(newVal, oldVal)) return;
const includedFieldsWithErrors = props.validationErrors.filter((validationError) =>
rootFields.value.find((rootField) => rootField.field === validationError.field)
groupFields.value.find((rootField) => rootField.field === validationError.field)
);
if (includedFieldsWithErrors.length > 0) selection.value = [includedFieldsWithErrors[0].field];
}
);
return { rootFields, selection, toggleAll };
return { groupFields, groupValues, selection, toggleAll };
function toggleAll() {
if (props.accordionMode === true) return;
if (selection.value.length === rootFields.value.length) {
if (selection.value.length === groupFields.value.length) {
selection.value = [];
} else {
selection.value = rootFields.value.map((field) => field.field);
selection.value = groupFields.value.map((field) => field.field);
}
}
function useComputedGroup() {
const groupFields = ref<Field[]>(limitFields());
const groupValues = ref<Record<string, any>>({});
watch(
() => props.fields,
() => {
const newVal = limitFields();
if (!isEqual(groupFields.value, newVal)) {
groupFields.value = newVal;
}
}
);
watch(
() => props.values,
(newVal) => {
if (!isEqual(groupValues.value, newVal)) {
groupValues.value = newVal;
}
}
);
return { groupFields, groupValues };
function limitFields(): Field[] {
return props.fields.filter((field) => field.meta?.group === props.field.meta?.field);
}
}
},