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

Commit f209ef6

Browse files
authored
Merge pull request #189 from asigloo/feature/improve-component-design-architecture
Feature/improve component design architecture
2 parents f894ca5 + a12aae8 commit f209ef6

File tree

13 files changed

+222
-139
lines changed

13 files changed

+222
-139
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: 98 additions & 55 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,23 +38,22 @@
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
56-
/* import { warn } from '../../core/utils/warning';
57-
*/
5857
const props = {
5958
form: {
6059
type: Object as PropType<DynamicForm>,
@@ -80,31 +79,11 @@ export default defineComponent({
8079
components,
8180
setup(props, ctx) {
8281
const { options } = inject(dynamicFormsSymbol);
82+
const cache = deepClone(toRaw(props.form.fields));
8383
8484
const controls: Ref<FormControl<InputType>[]> = ref([]);
85-
const formValues = reactive({});
8685
const submited = ref(false);
8786
88-
onMounted(() => {
89-
mapControls();
90-
initValues();
91-
});
92-
// TODO: enable again when plugin theme option is available
93-
94-
/* const validTheme = computed(
95-
() => options.theme && AVAILABLE_THEMES.includes(options.theme),
96-
);
97-
98-
if (!validTheme.value) {
99-
warn(
100-
`There isn't a theme: ${
101-
options.theme
102-
} just yet, please choose one of the available themes: ${AVAILABLE_THEMES.join(
103-
', ',
104-
)}`,
105-
);
106-
} */
107-
10887
const deNormalizedScopedSlots = computed(() => Object.keys(ctx.slots));
10988
11089
const normalizedControls = computed(() => {
@@ -120,6 +99,22 @@ export default defineComponent({
12099
return !hasInvalidControls;
121100
});
122101
102+
const formValues = computed(() => {
103+
return removeEmpty(
104+
controls.value.reduce((prev, curr) => {
105+
const obj = {};
106+
obj[curr.name] =
107+
curr.type === FieldTypes.NUMBER
108+
? parseFloat(`${curr.value}`)
109+
: curr.value;
110+
return {
111+
...prev,
112+
...obj,
113+
};
114+
}, {}),
115+
);
116+
});
117+
123118
const errors = computed(() => {
124119
return controls.value
125120
? controls.value.reduce((prev, curr) => {
@@ -160,12 +155,7 @@ export default defineComponent({
160155
}
161156
});
162157
163-
function valueChange(changedValue: Record<string, unknown>) {
164-
Object.assign(formValues, changedValue);
165-
ctx.emit('change', removeEmpty(formValues));
166-
}
167-
168-
function mapControls(empty?: boolean) {
158+
function mapControls(empty = false) {
169159
const controlArray =
170160
Object.entries(props.form?.fields).map(
171161
([key, field]: [string, InputType]) =>
@@ -192,6 +182,69 @@ export default defineComponent({
192182
controls.value = controlArray;
193183
}
194184
}
185+
function findControlByName(name: string | unknown) {
186+
const updatedCtrl = controls.value.find(control => control.name === name);
187+
return updatedCtrl;
188+
}
189+
190+
function valueChange(event: Record<string, unknown>) {
191+
if (event && hasValue(event.value)) {
192+
const updatedCtrl = findControlByName(event.name);
193+
if (updatedCtrl) {
194+
updatedCtrl.value = event.value as string;
195+
updatedCtrl.dirty = true;
196+
validateControl(updatedCtrl);
197+
}
198+
ctx.emit('change', formValues.value);
199+
}
200+
}
201+
202+
function onBlur(control: FormControl<InputType>) {
203+
const updatedCtrl = findControlByName(control.name);
204+
if (updatedCtrl) {
205+
updatedCtrl.touched = true;
206+
}
207+
}
208+
209+
function validateControl(control: FormControl<InputType>) {
210+
if (control.validations) {
211+
const validation = control.validations.reduce((prev, curr) => {
212+
const val =
213+
typeof curr.validator === 'function'
214+
? curr.validator(control)
215+
: null;
216+
if (val !== null) {
217+
const [key, value] = Object.entries(val)[0];
218+
const obj = {};
219+
obj[key] = {
220+
value,
221+
text: curr.text,
222+
};
223+
return {
224+
...prev,
225+
...obj,
226+
};
227+
}
228+
return {
229+
...prev,
230+
};
231+
}, {});
232+
control.errors = validation;
233+
control.valid = Object.keys(validation).length === 0;
234+
}
235+
}
236+
237+
function detectChanges(fields) {
238+
const changes = diff(cache, deepClone(fields));
239+
Object.entries(changes).forEach(([key, value]) => {
240+
let ctrl = findControlByName(key);
241+
if (ctrl) {
242+
Object.entries(value).forEach(([change, newValue]) => {
243+
ctrl[change] = newValue;
244+
});
245+
}
246+
});
247+
}
195248
196249
function resetForm() {
197250
mapControls(true);
@@ -208,33 +261,22 @@ export default defineComponent({
208261
}
209262
}
210263
211-
function initValues() {
212-
Object.assign(
213-
formValues,
214-
controls.value
215-
? controls.value.reduce((prev, curr) => {
216-
const obj = {};
217-
obj[curr.name] =
218-
curr.type === FieldTypes.NUMBER
219-
? parseFloat(`${curr.value}`)
220-
: curr.value;
221-
return {
222-
...prev,
223-
...obj,
224-
};
225-
}, {})
226-
: {},
227-
);
228-
ctx.emit('changed', formValues);
229-
}
264+
watch(
265+
() => props.form.fields,
266+
fields => {
267+
detectChanges(fields);
268+
},
269+
{
270+
deep: true,
271+
},
272+
);
230273
231-
watch(props.form.fields, () => {
274+
onMounted(() => {
232275
mapControls();
233276
});
234277
235278
return {
236279
controls,
237-
form: props.form,
238280
valueChange,
239281
formValues,
240282
handleSubmit,
@@ -244,6 +286,7 @@ export default defineComponent({
244286
normalizedControls,
245287
submited,
246288
formattedOptions,
289+
onBlur,
247290
};
248291
},
249292
});

src/components/dynamic-form/form.ts

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

src/components/dynamic-input/DynamicInput.vue

Lines changed: 12 additions & 59 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,57 +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-
let value;
168-
const newValue = {};
169-
170-
if (isEvent($event)) {
171-
$event.stopPropagation();
172-
value =
173-
props.control.type === 'checkbox'
174-
? $event.target.checked
175-
: $event.target.value;
176-
} else {
177-
value = $event;
178-
}
179-
180-
if (props.control) {
181-
newValue[props.control.name] = value;
182-
validate();
183-
emit('change', newValue);
184-
}
137+
emit('change', $event);
185138
}
186139
187140
return () => {

0 commit comments

Comments
 (0)