Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Submit component and fix for isValid state #28

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/components/Field.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
{name}
type={type}
value={$form[name]}
checked={$form[name]}
on:change={handleChange}
on:blur={handleChange}
{...$$props} />
11 changes: 11 additions & 0 deletions lib/components/Submit.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
import { getContext } from "svelte";
import { key } from "./key";

const { state } = getContext(key);

</script>

<button {...$$props} type="submit" disabled={!$state.isValid}>
<slot />
</button>
316 changes: 161 additions & 155 deletions lib/createForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,168 +5,174 @@ const NO_ERROR = "";
const IS_TOUCHED = true;

export const createForm = config => {
const initialValues = config.initialValues || {};

if (Object.keys(initialValues).length < 1) {
const provided = JSON.stringify(initialValues);
console.warn(
`createForm requires initialValues to be a non empty object or array, provided ${provided}`
);
return;
}

const validationSchema = config.validationSchema;
const validateFn = config.validate;
const onSubmit = config.onSubmit;

const initial = {
values: () => util.cloneDeep(initialValues),
errors: () => util.assignDeep(initialValues, NO_ERROR),
touched: () => util.assignDeep(initialValues, !IS_TOUCHED)
};

const form = writable(initial.values());
const errors = writable(initial.errors());
const touched = writable(initial.touched());

const isSubmitting = writable(false);
const isValidating = writable(false);

const isValid = derived([errors, touched], ([$errors, $touched]) => {
const allTouched = util
.getValues($touched)
.every(field => field === IS_TOUCHED);
const noErrors = util.getValues($errors).every(field => field === NO_ERROR);
return allTouched && noErrors;
});

function isCheckbox(el) {
return el.getAttribute && el.getAttribute('type') === 'checkbox';
}

function handleChange(event) {
const el = event.target;
const field = el.name;
const value = isCheckbox(el) ? el.checked : el.value;

updateTouched(field, true);

if (validationSchema) {
isValidating.set(true);
return util
.reach(validationSchema, field)
.validate(value)
.then(() => util.update(errors, field, ""))
.catch(err => util.update(errors, field, err.message))
.finally(() => {
updateField(field, value);
isValidating.set(false);
});
const initialValues = config.initialValues || {};

if (Object.keys(initialValues).length < 1) {
const provided = JSON.stringify(initialValues);
console.warn(
`createForm requires initialValues to be a non empty object or array, provided ${provided}`
);
return;
}

if (validateFn) {
isValidating.set(true);
return Promise.resolve()
.then(() => validateFn({ [field]: value }))
.then(errs => util.update(errors, field, errs[field]))
.finally(() => {
updateField(field, value);
isValidating.set(false);
})
}
const validationSchema = config.validationSchema;
const validateFn = config.validate;
const onSubmit = config.onSubmit;

const initial = {
values: () => util.cloneDeep(initialValues),
errors: () => util.assignDeep(initialValues, NO_ERROR),
touched: () => util.assignDeep(initialValues, !IS_TOUCHED)
};

const form = writable(initial.values());
const errors = writable(initial.errors());
const touched = writable(initial.touched());

let context;
form.subscribe(val => context = val);

const isSubmitting = writable(false);
const isValidating = writable(false);

const isValid = derived([errors, touched], ([$errors, $touched]) => {
/*
const allTouched = util
.getValues($touched)
.every(field => field === IS_TOUCHED);
*/
const noErrors = util.getValues($errors).every(field => field === NO_ERROR);
return /* allTouched && */ noErrors;
});

updateField(field, value);
}
function isCheckbox(el) {
return el.getAttribute && el.getAttribute('type') === 'checkbox';
}

function handleSubmit(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
function handleChange(event) {
const el = event.target;
const field = el.name;
const value = isCheckbox(el) ? el.checked : el.value;

updateTouched(field, true);

if (validationSchema) {
isValidating.set(true);
return validationSchema
.validateAt(field, context)
.then(() => util.update(errors, field, ""))
.catch(err => util.update(errors, field, err.message))
.finally(() => {
updateField(field, value);
isValidating.set(false);
});
}

if (validateFn) {
isValidating.set(true);
return Promise.resolve()
.then(() => validateFn({
[field]: value
}))
.then(errs => util.update(errors, field, errs[field]))
.finally(() => {
updateField(field, value);
isValidating.set(false);
})
}

updateField(field, value);
}

isSubmitting.set(true);
function handleSubmit(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}

return util.subscribeOnce(form).then(values => {
if (typeof validateFn === "function") {
isValidating.set(true);
isSubmitting.set(true);

return Promise.resolve()
.then(() => validateFn(values))
.then(err =>
util.isEmpty(err) ? clearErrorsAndSubmit(values) : errors.set(err)
)
.finally(() => isValidating.set(false));
}

if (validationSchema) {
isValidating.set(true);

return validationSchema
.validate(values, { abortEarly: false })
.then(() => clearErrorsAndSubmit(values))
.catch(yupErrs => {
if (yupErrs && yupErrs.inner) {
yupErrs.inner.forEach(error =>
util.update(errors, error.path, error.message)
);
return util.subscribeOnce(form).then(values => {
if (typeof validateFn === "function") {
isValidating.set(true);

return Promise.resolve()
.then(() => validateFn(values))
.then(err =>
util.isEmpty(err) ? clearErrorsAndSubmit(values) : errors.set(err)
)
.finally(() => isValidating.set(false));
}
isSubmitting.set(false);
})
.finally(() => isValidating.set(false));
}

clearErrorsAndSubmit(values);
});
}

function handleReset() {
form.set(initial.values());
errors.set(initial.errors());
touched.set(initial.touched());
}

function clearErrorsAndSubmit(values) {
return Promise.resolve()
.then(() => errors.set(util.assignDeep(values, "")))
.then(() => onSubmit(values, form, errors))
.finally(() => isSubmitting.set(false));
}

/**
* Handler to imperatively update the value of a form field
*/
function updateField(field, value) {
util.update(form, field, value);
}

/**
* Handler to imperatively update the touched value of a form field
*/
function updateTouched(field, value) {
util.update(touched, field, value);
}

return {
form,
errors,
touched,
isValid,
isSubmitting,
isValidating,
handleChange,
handleSubmit,
handleReset,
updateField,
updateTouched,
state: derived(
[form, errors, touched, isValid, isValidating, isSubmitting],
([$form, $errors, $touched, $isValid, $isValidating, $isSubmitting]) => ({
form: $form,
errors: $errors,
touched: $touched,
isValid: $isValid,
isSubmitting: $isSubmitting,
isValidating: $isValidating
})
)
};
};
if (validationSchema) {
isValidating.set(true);

return validationSchema
.validate(values, { abortEarly: false })
.then(() => clearErrorsAndSubmit(values))
.catch(yupErrs => {
if (yupErrs && yupErrs.inner) {
yupErrs.inner.forEach(error =>
util.update(errors, error.path, error.message)
);
}
isSubmitting.set(false);
})
.finally(() => isValidating.set(false));
}

clearErrorsAndSubmit(values);
});
}

function handleReset() {
form.set(initial.values());
errors.set(initial.errors());
touched.set(initial.touched());
}

function clearErrorsAndSubmit(values) {
return Promise.resolve()
.then(() => errors.set(util.assignDeep(values, "")))
.then(() => onSubmit(values, form, errors))
.finally(() => isSubmitting.set(false));
}

/**
* Handler to imperatively update the value of a form field
*/
function updateField(field, value) {
util.update(form, field, value);
}

/**
* Handler to imperatively update the touched value of a form field
*/
function updateTouched(field, value) {
util.update(touched, field, value);
}

return {
form,
errors,
touched,
isValid,
isSubmitting,
isValidating,
handleChange,
handleSubmit,
handleReset,
updateField,
updateTouched,
state: derived(
[form, errors, touched, isValid, isValidating, isSubmitting],
([$form, $errors, $touched, $isValid, $isValidating, $isSubmitting]) => ({
form: $form,
errors: $errors,
touched: $touched,
isValid: $isValid,
isSubmitting: $isSubmitting,
isValidating: $isValidating
})
)
};
};
17 changes: 12 additions & 5 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
export { createForm } from "./createForm";
export { default as Form } from "./components/Form.svelte";
export { default as Field } from "./components/Field.svelte";
export { default as Select } from "./components/Select.svelte";
export { default as ErrorMessage } from "./components/ErrorMessage.svelte";
export { createForm }
from "./createForm";
export { default as Form }
from "./components/Form.svelte";
export { default as Field }
from "./components/Field.svelte";
export { default as Select }
from "./components/Select.svelte";
export { default as ErrorMessage }
from "./components/ErrorMessage.svelte";
export { default as Submit }
from "./components/Submit.svelte";