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

Commit

Permalink
Merge pull request #189 from asigloo/feature/improve-component-design…
Browse files Browse the repository at this point in the history
…-architecture

Feature/improve component design architecture
  • Loading branch information
alvarosabu authored Nov 7, 2020
2 parents f894ca5 + a12aae8 commit f209ef6
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 139 deletions.
1 change: 1 addition & 0 deletions dev/typescript/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ export default defineComponent({
value: string;
disabled?: boolean;
}[];
form.fields.name.value = 'Alvaro';
} catch (e) {
console.error(e);
}
Expand Down
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
"vue-select": "3.10.8"
},
"dependencies": {
"deep-clone": "^3.0.3",
"deep-object-diff": "^1.1.0",
"rollup-plugin-scss": "^2.6.1"
}
}
153 changes: 98 additions & 55 deletions src/components/dynamic-form/DynamicForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
:control="control"
:submited="submited"
@change="valueChange"
@blur="onBlur"
>
<template v-slot:customField="props">
<div
Expand All @@ -25,7 +26,6 @@
:name="slot"
:control="normalizedControls[slot]"
:onChange="props.onChange"
:onFocus="props.onFocus"
:onBlur="props.onBlur"
></slot>
</div>
Expand All @@ -38,23 +38,22 @@
import {
defineComponent,
PropType,
reactive,
ref,
Ref,
computed,
onMounted,
watch,
inject,
toRaw,
} from 'vue';
import { DynamicForm } from './form';
import { diff } from 'deep-object-diff';
import DynamicInput from '../dynamic-input/DynamicInput.vue';
import { FieldTypes, FormControl, InputType } from '@/core/models';
import { DynamicForm, FieldTypes, FormControl, InputType } from '@/core/models';
import { dynamicFormsSymbol } from '@/useApi';
import { removeEmpty } from '@/core/utils/helpers';
import { deepClone, hasValue, removeEmpty } from '@/core/utils/helpers';
/* import { warn } from '../../core/utils/warning';
*/
const props = {
form: {
type: Object as PropType<DynamicForm>,
Expand All @@ -80,31 +79,11 @@ export default defineComponent({
components,
setup(props, ctx) {
const { options } = inject(dynamicFormsSymbol);
const cache = deepClone(toRaw(props.form.fields));
const controls: Ref<FormControl<InputType>[]> = ref([]);
const formValues = reactive({});
const submited = ref(false);
onMounted(() => {
mapControls();
initValues();
});
// TODO: enable again when plugin theme option is available
/* const validTheme = computed(
() => options.theme && AVAILABLE_THEMES.includes(options.theme),
);
if (!validTheme.value) {
warn(
`There isn't a theme: ${
options.theme
} just yet, please choose one of the available themes: ${AVAILABLE_THEMES.join(
', ',
)}`,
);
} */
const deNormalizedScopedSlots = computed(() => Object.keys(ctx.slots));
const normalizedControls = computed(() => {
Expand All @@ -120,6 +99,22 @@ export default defineComponent({
return !hasInvalidControls;
});
const formValues = computed(() => {
return removeEmpty(
controls.value.reduce((prev, curr) => {
const obj = {};
obj[curr.name] =
curr.type === FieldTypes.NUMBER
? parseFloat(`${curr.value}`)
: curr.value;
return {
...prev,
...obj,
};
}, {}),
);
});
const errors = computed(() => {
return controls.value
? controls.value.reduce((prev, curr) => {
Expand Down Expand Up @@ -160,12 +155,7 @@ export default defineComponent({
}
});
function valueChange(changedValue: Record<string, unknown>) {
Object.assign(formValues, changedValue);
ctx.emit('change', removeEmpty(formValues));
}
function mapControls(empty?: boolean) {
function mapControls(empty = false) {
const controlArray =
Object.entries(props.form?.fields).map(
([key, field]: [string, InputType]) =>
Expand All @@ -192,6 +182,69 @@ export default defineComponent({
controls.value = controlArray;
}
}
function findControlByName(name: string | unknown) {
const updatedCtrl = controls.value.find(control => control.name === name);
return updatedCtrl;
}
function valueChange(event: Record<string, unknown>) {
if (event && hasValue(event.value)) {
const updatedCtrl = findControlByName(event.name);
if (updatedCtrl) {
updatedCtrl.value = event.value as string;
updatedCtrl.dirty = true;
validateControl(updatedCtrl);
}
ctx.emit('change', formValues.value);
}
}
function onBlur(control: FormControl<InputType>) {
const updatedCtrl = findControlByName(control.name);
if (updatedCtrl) {
updatedCtrl.touched = true;
}
}
function validateControl(control: FormControl<InputType>) {
if (control.validations) {
const validation = control.validations.reduce((prev, curr) => {
const val =
typeof curr.validator === 'function'
? curr.validator(control)
: null;
if (val !== null) {
const [key, value] = Object.entries(val)[0];
const obj = {};
obj[key] = {
value,
text: curr.text,
};
return {
...prev,
...obj,
};
}
return {
...prev,
};
}, {});
control.errors = validation;
control.valid = Object.keys(validation).length === 0;
}
}
function detectChanges(fields) {
const changes = diff(cache, deepClone(fields));
Object.entries(changes).forEach(([key, value]) => {
let ctrl = findControlByName(key);
if (ctrl) {
Object.entries(value).forEach(([change, newValue]) => {
ctrl[change] = newValue;
});
}
});
}
function resetForm() {
mapControls(true);
Expand All @@ -208,33 +261,22 @@ export default defineComponent({
}
}
function initValues() {
Object.assign(
formValues,
controls.value
? controls.value.reduce((prev, curr) => {
const obj = {};
obj[curr.name] =
curr.type === FieldTypes.NUMBER
? parseFloat(`${curr.value}`)
: curr.value;
return {
...prev,
...obj,
};
}, {})
: {},
);
ctx.emit('changed', formValues);
}
watch(
() => props.form.fields,
fields => {
detectChanges(fields);
},
{
deep: true,
},
);
watch(props.form.fields, () => {
onMounted(() => {
mapControls();
});
return {
controls,
form: props.form,
valueChange,
formValues,
handleSubmit,
Expand All @@ -244,6 +286,7 @@ export default defineComponent({
normalizedControls,
submited,
formattedOptions,
onBlur,
};
},
});
Expand Down
7 changes: 0 additions & 7 deletions src/components/dynamic-form/form.ts

This file was deleted.

71 changes: 12 additions & 59 deletions src/components/dynamic-input/DynamicInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,7 @@ import {
FieldTypes,
} from '@/core/models';
import {
isEmpty,
entries,
values,
keys,
isEvent,
isArray,
isObject,
} from '@/core/utils/helpers';
import { values, keys, isArray, isObject } from '@/core/utils/helpers';
import { useInputEvents } from '@/composables/input-events';
import { dynamicFormsSymbol } from '@/useApi';
Expand All @@ -61,6 +53,8 @@ const props = {
export type ControlAttribute<T extends InputType> = {
control: FormControl<T>;
onChange: (value: unknown) => void;
onFocus: (value: unknown) => void;
onBlur: (value: unknown) => void;
};
export default defineComponent({
Expand All @@ -78,6 +72,8 @@ export default defineComponent({
control: props?.control,
style: props?.control.customStyles,
onChange: valueChange,
onBlur: () => emit('blur', props.control),
onFocus: () => emit('focus', props.control),
};
});
Expand All @@ -91,6 +87,12 @@ export default defineComponent({
{
'form-group--inline': props?.control?.type === FieldTypes.CHECKBOX,
},
{
'form-group--success':
props?.control?.valid &&
props?.control?.dirty &&
props?.control?.touched,
},
{
'form-group--error': showErrors.value,
},
Expand Down Expand Up @@ -131,57 +133,8 @@ export default defineComponent({
return [];
});
function validate() {
if (
props.control &&
props.control.validations &&
isEmpty(props.control.validations)
) {
const validation = props.control.validations.reduce((prev, curr) => {
const val =
typeof curr.validator === 'function'
? curr.validator(props.control)
: null;
if (val !== null) {
const [key, value] = entries(val)[0];
const obj = {};
obj[key] = {
value,
text: curr.text,
};
return {
...prev,
...obj,
};
}
return {
...prev,
};
}, {});
props.control.errors = validation;
props.control.valid = Object.keys(validation).length === 0;
}
}
function valueChange($event) {
let value;
const newValue = {};
if (isEvent($event)) {
$event.stopPropagation();
value =
props.control.type === 'checkbox'
? $event.target.checked
: $event.target.value;
} else {
value = $event;
}
if (props.control) {
newValue[props.control.name] = value;
validate();
emit('change', newValue);
}
emit('change', $event);
}
return () => {
Expand Down
Loading

0 comments on commit f209ef6

Please sign in to comment.