Skip to content
This repository was archived by the owner on Jun 27, 2023. It is now read-only.

Commit ff9127d

Browse files
committed
feat(dynamics-forms): fix anti-pattern structure & fieldchange detection
1 parent cc8e39b commit ff9127d

File tree

12 files changed

+210
-122
lines changed

12 files changed

+210
-122
lines changed

dev/typescript/App.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export default defineComponent({
230230
value: string;
231231
disabled?: boolean;
232232
}[];
233+
form.fields.name.value = 'Alvaro';
233234
} catch (e) {
234235
console.error(e);
235236
}

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@
8888
"vue-select": "3.10.8"
8989
},
9090
"dependencies": {
91+
"deep-clone": "^3.0.3",
92+
"deep-object-diff": "^1.1.0",
9193
"rollup-plugin-scss": "^2.6.1"
9294
}
9395
}

src/components/dynamic-form/DynamicForm.vue

Lines changed: 94 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
:control="control"
1414
:submited="submited"
1515
@change="valueChange"
16+
@blur="onBlur"
1617
>
1718
<template v-slot:customField="props">
1819
<div
@@ -25,7 +26,6 @@
2526
:name="slot"
2627
:control="normalizedControls[slot]"
2728
:onChange="props.onChange"
28-
:onFocus="props.onFocus"
2929
:onBlur="props.onBlur"
3030
></slot>
3131
</div>
@@ -38,20 +38,21 @@
3838
import {
3939
defineComponent,
4040
PropType,
41-
reactive,
4241
ref,
4342
Ref,
4443
computed,
4544
onMounted,
4645
watch,
4746
inject,
47+
toRaw,
4848
} from 'vue';
49-
import { DynamicForm } from './form';
49+
import { diff } from 'deep-object-diff';
50+
5051
import DynamicInput from '../dynamic-input/DynamicInput.vue';
5152
52-
import { FieldTypes, FormControl, InputType } from '@/core/models';
53+
import { DynamicForm, FieldTypes, FormControl, InputType } from '@/core/models';
5354
import { dynamicFormsSymbol } from '@/useApi';
54-
import { removeEmpty } from '@/core/utils/helpers';
55+
import { deepClone, hasValue, removeEmpty } from '@/core/utils/helpers';
5556
5657
/* import { warn } from '../../core/utils/warning';
5758
*/
@@ -80,29 +81,11 @@ export default defineComponent({
8081
components,
8182
setup(props, ctx) {
8283
const { options } = inject(dynamicFormsSymbol);
84+
const cache = deepClone(toRaw(props.form.fields));
8385
8486
const controls: Ref<FormControl<InputType>[]> = ref([]);
8587
const submited = ref(false);
8688
87-
onMounted(() => {
88-
mapControls();
89-
});
90-
// TODO: enable again when plugin theme option is available
91-
92-
/* const validTheme = computed(
93-
() => options.theme && AVAILABLE_THEMES.includes(options.theme),
94-
);
95-
96-
if (!validTheme.value) {
97-
warn(
98-
`There isn't a theme: ${
99-
options.theme
100-
} just yet, please choose one of the available themes: ${AVAILABLE_THEMES.join(
101-
', ',
102-
)}`,
103-
);
104-
} */
105-
10689
const deNormalizedScopedSlots = computed(() => Object.keys(ctx.slots));
10790
10891
const normalizedControls = computed(() => {
@@ -174,21 +157,7 @@ export default defineComponent({
174157
}
175158
});
176159
177-
function valueChange(event: Record<string, unknown>) {
178-
if (event) {
179-
const newControl = controls.value.find(
180-
control => control.name === event.name,
181-
);
182-
if (newControl) {
183-
newControl.value = event.value as string;
184-
newControl.dirty = event.value !== null;
185-
}
186-
console.log('DynamicForms:controls', controls.value);
187-
ctx.emit('change', formValues.value);
188-
}
189-
}
190-
191-
function mapControls(empty?: boolean) {
160+
function mapControls(empty = false) {
192161
const controlArray =
193162
Object.entries(props.form?.fields).map(
194163
([key, field]: [string, InputType]) =>
@@ -215,6 +184,69 @@ export default defineComponent({
215184
controls.value = controlArray;
216185
}
217186
}
187+
function findControlByName(name: string | unknown) {
188+
const updatedCtrl = controls.value.find(control => control.name === name);
189+
return updatedCtrl;
190+
}
191+
192+
function valueChange(event: Record<string, unknown>) {
193+
if (event && hasValue(event.value)) {
194+
const updatedCtrl = findControlByName(event.name);
195+
if (updatedCtrl) {
196+
updatedCtrl.value = event.value as string;
197+
updatedCtrl.dirty = true;
198+
validateControl(updatedCtrl);
199+
}
200+
ctx.emit('change', formValues.value);
201+
}
202+
}
203+
204+
function onBlur(control: FormControl<InputType>) {
205+
const updatedCtrl = findControlByName(control.name);
206+
if (updatedCtrl) {
207+
updatedCtrl.touched = true;
208+
}
209+
}
210+
211+
function validateControl(control: FormControl<InputType>) {
212+
if (control.validations) {
213+
const validation = control.validations.reduce((prev, curr) => {
214+
const val =
215+
typeof curr.validator === 'function'
216+
? curr.validator(control)
217+
: null;
218+
if (val !== null) {
219+
const [key, value] = Object.entries(val)[0];
220+
const obj = {};
221+
obj[key] = {
222+
value,
223+
text: curr.text,
224+
};
225+
return {
226+
...prev,
227+
...obj,
228+
};
229+
}
230+
return {
231+
...prev,
232+
};
233+
}, {});
234+
control.errors = validation;
235+
control.valid = Object.keys(validation).length === 0;
236+
}
237+
}
238+
239+
function detectChanges(fields) {
240+
const changes = diff(cache, deepClone(fields));
241+
Object.entries(changes).forEach(([key, value]) => {
242+
let ctrl = findControlByName(key);
243+
if (ctrl) {
244+
Object.entries(value).forEach(([change, newValue]) => {
245+
ctrl[change] = newValue;
246+
});
247+
}
248+
});
249+
}
218250
219251
function resetForm() {
220252
mapControls(true);
@@ -231,13 +263,22 @@ export default defineComponent({
231263
}
232264
}
233265
234-
watch(props.form.fields, () => {
266+
watch(
267+
() => props.form.fields,
268+
fields => {
269+
detectChanges(fields);
270+
},
271+
{
272+
deep: true,
273+
},
274+
);
275+
276+
onMounted(() => {
235277
mapControls();
236278
});
237279
238280
return {
239281
controls,
240-
form: props.form,
241282
valueChange,
242283
formValues,
243284
handleSubmit,
@@ -247,7 +288,19 @@ export default defineComponent({
247288
normalizedControls,
248289
submited,
249290
formattedOptions,
291+
onBlur,
250292
};
251293
},
294+
/* watch: {
295+
form: {
296+
handler(newVal, oldVal) {
297+
console.log({
298+
newVal,
299+
oldVal,
300+
});
301+
},
302+
deep: true,
303+
},
304+
}, */
252305
});
253306
</script>

src/components/dynamic-form/form.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/components/dynamic-input/DynamicInput.vue

Lines changed: 11 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,7 @@ import {
2626
FieldTypes,
2727
} from '@/core/models';
2828
29-
import {
30-
isEmpty,
31-
entries,
32-
values,
33-
keys,
34-
isEvent,
35-
isArray,
36-
isObject,
37-
} from '@/core/utils/helpers';
29+
import { values, keys, isArray, isObject } from '@/core/utils/helpers';
3830
import { useInputEvents } from '@/composables/input-events';
3931
import { dynamicFormsSymbol } from '@/useApi';
4032
@@ -61,6 +53,8 @@ const props = {
6153
export type ControlAttribute<T extends InputType> = {
6254
control: FormControl<T>;
6355
onChange: (value: unknown) => void;
56+
onFocus: (value: unknown) => void;
57+
onBlur: (value: unknown) => void;
6458
};
6559
6660
export default defineComponent({
@@ -78,6 +72,8 @@ export default defineComponent({
7872
control: props?.control,
7973
style: props?.control.customStyles,
8074
onChange: valueChange,
75+
onBlur: () => emit('blur', props.control),
76+
onFocus: () => emit('focus', props.control),
8177
};
8278
});
8379
@@ -91,6 +87,12 @@ export default defineComponent({
9187
{
9288
'form-group--inline': props?.control?.type === FieldTypes.CHECKBOX,
9389
},
90+
{
91+
'form-group--success':
92+
props?.control?.valid &&
93+
props?.control?.dirty &&
94+
props?.control?.touched,
95+
},
9496
{
9597
'form-group--error': showErrors.value,
9698
},
@@ -131,61 +133,8 @@ export default defineComponent({
131133
return [];
132134
});
133135
134-
/* function validate() {
135-
if (
136-
props.control &&
137-
props.control.validations &&
138-
isEmpty(props.control.validations)
139-
) {
140-
const validation = props.control.validations.reduce((prev, curr) => {
141-
const val =
142-
typeof curr.validator === 'function'
143-
? curr.validator(props.control)
144-
: null;
145-
if (val !== null) {
146-
const [key, value] = entries(val)[0];
147-
const obj = {};
148-
obj[key] = {
149-
value,
150-
text: curr.text,
151-
};
152-
return {
153-
...prev,
154-
...obj,
155-
};
156-
}
157-
return {
158-
...prev,
159-
};
160-
}, {});
161-
props.control.errors = validation;
162-
props.control.valid = Object.keys(validation).length === 0;
163-
}
164-
} */
165-
166136
function valueChange($event) {
167-
console.log('DynamicInput:change', $event);
168137
emit('change', $event);
169-
/* let value;
170-
const newValue = {};
171-
172-
console.log('DynamicInput:change', $event);
173-
174-
if (isEvent($event)) {
175-
$event.stopPropagation();
176-
value =
177-
props.control.type === 'checkbox'
178-
? $event.target.checked
179-
: $event.target.value;
180-
} else {
181-
value = $event;
182-
}
183-
184-
if (props.control) {
185-
newValue[props.control.name] = value;
186-
validate();
187-
emit('change', newValue);
188-
} */
189138
}
190139
191140
return () => {

src/components/select-input/SelectInput.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<script lang="ts">
2-
import { defineComponent, h, PropType } from 'vue';
2+
import { defineComponent, h, PropType, computed } from 'vue';
33
import { FormControl, SelectInput } from '@/core/models';
44
import { useInputEvents } from '@/composables/input-events';
5+
import { isArray, isObject } from '@/core/utils/helpers';
56
67
const props = {
78
control: Object as PropType<FormControl<SelectInput>>,
@@ -14,7 +15,14 @@ export default defineComponent({
1415
return () => {
1516
const { onChange, onFocus, onBlur } = useInputEvents(props, emit);
1617
17-
const options = props?.control?.options?.map(({ key, value, disabled }) =>
18+
const formattedOptions = computed(() => {
19+
if (isObject(props?.control?.options)) {
20+
return Object.values(props?.control?.options);
21+
}
22+
return props?.control?.options;
23+
});
24+
25+
const options = formattedOptions.value.map(({ key, value, disabled }) =>
1826
h('option', { key, value: key, disabled }, value),
1927
);
2028
return h(

0 commit comments

Comments
 (0)