UNPKG

react-formstate-fp

Version:

An API to build, query, update, and bind formstate in React. Develop forms DRYly in React.

524 lines (417 loc) 20.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault")["default"]; Object.defineProperty(exports, "__esModule", { value: true }); exports._setContainerScopesChanged = _setContainerScopesChanged; exports._synclyValidate = _synclyValidate; exports.addModelKey = addModelKey; exports.asynclyValidate = asynclyValidate; exports.asynclyValidateForm = asynclyValidateForm; exports.cancelFormSubmission = cancelFormSubmission; exports.changeAndValidate = changeAndValidate; exports.deleteModelKey = deleteModelKey; exports.deleteModelKeyAndValidateParentScope = deleteModelKeyAndValidateParentScope; exports.driveFormSubmission = driveFormSubmission; exports.getPromises = getPromises; exports.handleBlur = handleBlur; exports.handleChange = handleChange; exports.startFormSubmission = startFormSubmission; exports.synclyValidate = synclyValidate; exports.synclyValidateForm = synclyValidateForm; exports.validateForm = void 0; exports.validateRequired = validateRequired; var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2")); var _helperFunctions = require("./helperFunctions.js"); var _schemaAndLookup = require("./schemaAndLookup.js"); var _fieldAndScopeStatus = require("./fieldAndScopeStatus.js"); var _formStatus = require("./formStatus.js"); // // synclyValidate // function synclyValidate(formstate, modelKey, form) { var id = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; if (!id) { id = (0, _schemaAndLookup.getId)(formstate, modelKey); } if (!(0, _fieldAndScopeStatus.isSynclyValidated)(formstate, modelKey)) { formstate = _synclyValidate(formstate, modelKey, form, id); } // validate container scopes, in order of precision, with the root scope last. var rootModelKey = (0, _schemaAndLookup.getRootModelKey)(formstate, id); // Cannot use a relative model key for scopes above the nested scope... var nestedScopeId = formstate.nestedScopeId; formstate = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: null }); (0, _helperFunctions.sortKeysByScopeLength)(formstate.lookup.scopes, formstate.lookup.rootModelKeysById).forEach(function (rootScopeKey) { if ((0, _helperFunctions.inScope)(rootModelKey, rootScopeKey)) { if (!(0, _fieldAndScopeStatus.isSynclyValidated)(formstate, rootScopeKey)) { formstate = _synclyValidate(formstate, rootScopeKey, form, formstate.lookup.idsByRootModelKey[rootScopeKey]); } } }); return (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: nestedScopeId }); } // // synclyValidateForm // function synclyValidateForm(formstate, form) { // Cannot use a relative model key for scopes above the nested scope... var nestedScopeId = formstate.nestedScopeId; formstate = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: null }); (0, _helperFunctions.sortKeysByScopeLength)(formstate.lookup.rootModelKeysById, formstate.lookup.rootModelKeysById).forEach(function (rootModelKey) { if (!(0, _fieldAndScopeStatus.isSynclyValidated)(formstate, rootModelKey)) { var id = formstate.lookup.idsByRootModelKey[rootModelKey]; formstate = _synclyValidate(formstate, rootModelKey, form, id); } }); return (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: nestedScopeId }); } var validateForm = synclyValidateForm; exports.validateForm = validateForm; function _synclyValidate(formstate, modelKey, form, id) { var validationSchema = formstate.validationSchemas[id] || form.validationSchemas && form.validationSchemas[id]; if (!validationSchema) { return (0, _fieldAndScopeStatus.setSynclyValid)(formstate, modelKey); } var value = (0, _fieldAndScopeStatus.getValue)(formstate, modelKey); if (validationSchema.required) { var result = validateRequired(value, validationSchema.requiredMessage); if (result) { return (0, _fieldAndScopeStatus.setSynclyInvalid)(formstate, modelKey, result); } } if (validationSchema.validate) { var nestedScopeId = formstate.nestedScopeId; var scopedFormstate = formstate, scopedForm = form; if (formstate.nestedScopeId !== validationSchema.nestedScopeId) { var _createNestedScope = (0, _schemaAndLookup.createNestedScope)(validationSchema.nestedScopeId, formstate, form); var _createNestedScope2 = (0, _slicedToArray2["default"])(_createNestedScope, 2); scopedFormstate = _createNestedScope2[0]; scopedForm = _createNestedScope2[1]; } var _result = validationSchema.validate(value, scopedFormstate, scopedForm, id); if ((0, _helperFunctions.isNonEmptyString)(_result)) { return (0, _fieldAndScopeStatus.setSynclyInvalid)(formstate, modelKey, _result); } if ((0, _helperFunctions.isObject)(_result) && _result.model) { formstate = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, _result), {}, { nestedScopeId: nestedScopeId }); if ((0, _schemaAndLookup.isScope)(formstate, id) && !(0, _fieldAndScopeStatus.isSynclyValidated)(formstate, modelKey)) { formstate = (0, _fieldAndScopeStatus.setSynclyValid)(formstate, modelKey); } return formstate; } if (_result) { throw new Error('Validation functions should return an error message, an updated formstate object, or nothing.'); } } return (0, _fieldAndScopeStatus.setSynclyValid)(formstate, modelKey); } function validateRequired(value, configuredMessage) { if (!(0, _helperFunctions.exists)(value)) { return configuredMessage; } if (typeof value === 'string' && value.trim() === '') { return configuredMessage; } if (Array.isArray(value) && value.length === 0) { return configuredMessage; } } // // asynclyValidate // function asynclyValidate(formstate, modelKey, form) { var id = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; if ((0, _fieldAndScopeStatus.isSynclyInvalid)(formstate, modelKey) || (0, _fieldAndScopeStatus.isAsynclyValidated)(formstate, modelKey) || (0, _fieldAndScopeStatus.isWaiting)(formstate, modelKey)) { return formstate; } if (!id) { id = (0, _schemaAndLookup.getId)(formstate, modelKey); } var validationSchema = formstate.validationSchemas[id] || form.validationSchemas && form.validationSchemas[id]; var validateAsync = validationSchema && validationSchema.validateAsync; if (!validateAsync) { return formstate; } var _validateAsync = (0, _slicedToArray2["default"])(validateAsync, 2), validateAsyncFunction = _validateAsync[0], whenToRun = _validateAsync[1]; if (whenToRun === 'onSubmit' && !(0, _fieldAndScopeStatus.isSubmitting)(formstate, modelKey)) { return formstate; } if (whenToRun === 'onBlur' && !(0, _fieldAndScopeStatus.isBlurred)(formstate, modelKey) && !(0, _fieldAndScopeStatus.isSubmitting)(formstate, modelKey)) { return formstate; } var nestedScopeId = formstate.nestedScopeId; var scopedFormstate = formstate, scopedForm = form; if (formstate.nestedScopeId !== validationSchema.nestedScopeId) { var _createNestedScope3 = (0, _schemaAndLookup.createNestedScope)(validationSchema.nestedScopeId, formstate, form); var _createNestedScope4 = (0, _slicedToArray2["default"])(_createNestedScope3, 2); scopedFormstate = _createNestedScope4[0]; scopedForm = _createNestedScope4[1]; } var result = validateAsyncFunction((0, _fieldAndScopeStatus.getValue)(formstate, modelKey), scopedFormstate, scopedForm, id); var asyncResultErrorMessage = 'An asynchronous validation function should return a formstate object to update formstate synchronously, or an array of [formstate, asyncToken, promise] to proceed with asynchronous validation.'; if (!(0, _helperFunctions.isObject)(result) && !Array.isArray(result)) { throw new Error(asyncResultErrorMessage); } if ((0, _helperFunctions.isObject)(result)) { return (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, result), {}, { nestedScopeId: nestedScopeId }); } var _result2 = (0, _slicedToArray2["default"])(result, 3), waitingFormstate = _result2[0], asyncToken = _result2[1], promise = _result2[2]; if (!(0, _helperFunctions.isObject)(waitingFormstate) || !(0, _helperFunctions.isNonEmptyString)(asyncToken) || !(0, _helperFunctions.isObject)(promise) || !(0, _helperFunctions.exists)(promise.constructor) || promise.constructor.name !== 'Promise') { throw new Error(asyncResultErrorMessage); } return (0, _fieldAndScopeStatus.setPromise)((0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, waitingFormstate), {}, { nestedScopeId: nestedScopeId }), asyncToken, promise); } // // asynclyValidateForm // function asynclyValidateForm(formstate, form) { // Cannot use a relative model key for scopes above the nested scope... var nestedScopeId = formstate.nestedScopeId; formstate = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: null }); Object.keys(formstate.lookup.rootModelKeysById).forEach(function (id) { var rootModelKey = (0, _schemaAndLookup.getRootModelKey)(formstate, id); formstate = asynclyValidate(formstate, rootModelKey, form, id); }); return (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: nestedScopeId }); } // // getPromises // function getPromises(formstate) { return Object.keys(formstate.formStatus.promises).map(function (asyncToken) { return formstate.formStatus.promises[asyncToken]; }); } // // changeAndValidate // function changeAndValidate(formstate, modelKey, value, form) { var id = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null; formstate = (0, _fieldAndScopeStatus.setValueAndClearStatus)(formstate, modelKey, value); formstate = (0, _fieldAndScopeStatus.setChanged)(formstate, modelKey); formstate = synclyValidate(formstate, modelKey, form, id); return asynclyValidate(formstate, modelKey, form, id); } // // handlers // function handleChange(form, value, id) { form.setFormstate(function (formstate) { if ((0, _formStatus.isInputDisabled)(formstate)) { return formstate; } return changeAndValidate(formstate, (0, _schemaAndLookup.getModelKey)(formstate, id), value, form, id); }); } function handleBlur(form, id) { form.setFormstate(function (formstate) { if ((0, _formStatus.isInputDisabled)(formstate)) { return formstate; } var modelKey = (0, _schemaAndLookup.getModelKey)(formstate, id); formstate = (0, _fieldAndScopeStatus.setBlurred)(formstate, modelKey); if (!(0, _formStatus.isFormSubmitting)(formstate)) { // && !isFormSubmittedAndUnchanged(formstate)) { if (form.validateOnBlur) { formstate = synclyValidate(formstate, modelKey, form, id); formstate = asynclyValidate(formstate, modelKey, form, id); } else if ((0, _fieldAndScopeStatus.isChanged)(formstate, modelKey)) { // If the form is set to NOT validate onBlur, and if they tab right through it, nothing should happen. // In that case, if there is async validation to run, at worst it'll run on submit. formstate = asynclyValidate(formstate, modelKey, form, id); } } return formstate; }); } // // startFormSubmission // function startFormSubmission(formstate) { if (formstate.nestedScopeId) { throw new Error('Nested form components should not submit the form.'); } if ((0, _formStatus.isFormSubmitting)(formstate)) { throw new Error('The formstate provided to startFormSubmission is already submitting! Please check for this before you call startFormSubmission.'); } return (0, _formStatus.setInputDisabled)((0, _formStatus.setFormSubmitting)(formstate)); } // // cancelFormSubmission // function cancelFormSubmission(formstate) { return (0, _formStatus.setInputEnabled)((0, _formStatus.setFormSubmitted)(formstate)); } // // driveFormSubmission // function driveFormSubmission(form, submitValidModel) { form.setFormstate(function (formstate) { if ((0, _formStatus.isFormSubmitting)(formstate)) { return formstate; } formstate = startFormSubmission(formstate); formstate = synclyValidateForm(formstate, form); // if (isModelInvalid(formstate)) {return cancelFormSubmission(formstate);} formstate = asynclyValidateForm(formstate, form); Promise.all(getPromises(formstate)).then(function () { form.setFormstate(function (validatedFs) { if (!(0, _formStatus.isModelValid)(validatedFs)) { return cancelFormSubmission(validatedFs); } Promise.resolve().then(function () { submitValidModel(validatedFs.model, form); }); return validatedFs; }); }); return formstate; }); } // // addModelKey // function addModelKey(formstate, modelKey, initialModel) { var formValidationSchema = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var rootModelKey = (0, _schemaAndLookup.convertToRootModelKey)(formstate, modelKey); formstate = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { lookup: (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate.lookup), {}, { rootModelKeysById: (0, _objectSpread2["default"])({}, formstate.lookup.rootModelKeysById), idsByRootModelKey: (0, _objectSpread2["default"])({}, formstate.lookup.idsByRootModelKey), scopes: (0, _objectSpread2["default"])({}, formstate.lookup.scopes) }), validationSchemas: (0, _objectSpread2["default"])({}, formstate.validationSchemas), model: (0, _helperFunctions.modelAdd)(formstate.model, rootModelKey, initialModel) // formStatus // statuses // nestedScopeId }); var normalizedFormValidationSchema = { fields: {}, scopes: {} }; (0, _schemaAndLookup.normalizeSchemaKeys)(formValidationSchema, normalizedFormValidationSchema, 'fields', rootModelKey); (0, _schemaAndLookup.normalizeSchemaKeys)(formValidationSchema, normalizedFormValidationSchema, 'scopes', rootModelKey); (0, _schemaAndLookup.buildLookup)(formstate, rootModelKey, initialModel, normalizedFormValidationSchema); (0, _schemaAndLookup.buildValidationSchemas)(formstate, normalizedFormValidationSchema, 'fields'); (0, _schemaAndLookup.buildValidationSchemas)(formstate, normalizedFormValidationSchema, 'scopes'); // Cannot use a relative model key for scopes above the nested scope... var nestedScopeId = formstate.nestedScopeId; formstate = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: null }); formstate = _setContainerScopesChanged(formstate, rootModelKey); return (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: nestedScopeId }); } // // deleteModelKey // function deleteModelKey(formstate, modelKey) { var rootModelKeyToDelete = (0, _schemaAndLookup.convertToRootModelKey)(formstate, modelKey); var purgedFormstate = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { lookup: { rootModelKeysById: {}, idsByRootModelKey: {}, scopes: {} }, statuses: {}, validationSchemas: {}, model: (0, _helperFunctions.modelDelete)(formstate.model, rootModelKeyToDelete) // formStatus // nestedScopeId }); var _parseParentScope = (0, _helperFunctions.parseParentScope)(rootModelKeyToDelete), _parseParentScope2 = (0, _slicedToArray2["default"])(_parseParentScope, 2), parentScope = _parseParentScope2[0], i = _parseParentScope2[1]; var deletingFromArray = Array.isArray((0, _helperFunctions.modelGet)(formstate.model, parentScope)); Object.keys(formstate.lookup.idsByRootModelKey).forEach(function (rootModelKey) { if (!(0, _helperFunctions.inScope)(rootModelKey, rootModelKeyToDelete)) { var id = formstate.lookup.idsByRootModelKey[rootModelKey]; var shiftedRootModelKey = null; if (deletingFromArray && (0, _helperFunctions.inScope)(rootModelKey, parentScope) && rootModelKey !== parentScope) { var indexAndRemainder = rootModelKey.slice(parentScope.length + 1); var _parseRootScope = (0, _helperFunctions.parseRootScope)(indexAndRemainder), _parseRootScope2 = (0, _slicedToArray2["default"])(_parseRootScope, 2), j = _parseRootScope2[0], remainder = _parseRootScope2[1]; if (Number(j) > Number(i)) { var shiftedIndex = String(Number(j) - 1); var shiftedIndexAndRemainder = (0, _helperFunctions.addScope)(shiftedIndex, remainder); shiftedRootModelKey = (0, _helperFunctions.addScope)(parentScope, shiftedIndexAndRemainder); } } purgedFormstate.lookup.rootModelKeysById[id] = shiftedRootModelKey || rootModelKey; purgedFormstate.lookup.idsByRootModelKey[shiftedRootModelKey || rootModelKey] = id; if (formstate.lookup.scopes[id]) { purgedFormstate.lookup.scopes[id] = true; } var status = formstate.statuses[id]; if (status) { purgedFormstate.statuses[id] = status; } var validationSchema = formstate.validationSchemas[id]; if (validationSchema) { purgedFormstate.validationSchemas[id] = validationSchema; } } }); // Cannot use a relative model key for scopes above the nested scope... var nestedScopeId = formstate.nestedScopeId; formstate = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, purgedFormstate), {}, { nestedScopeId: null }); formstate = _setContainerScopesChanged(formstate, rootModelKeyToDelete); return (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: nestedScopeId }); } // This assumes nestedScopeId already set to null. // This is to handle a case where you delete the nested scope. // function _setContainerScopesChanged(formstate, rootModelKey) { var _parseParentScope3 = (0, _helperFunctions.parseParentScope)(rootModelKey), _parseParentScope4 = (0, _slicedToArray2["default"])(_parseParentScope3, 1), parentScope = _parseParentScope4[0]; Object.keys(formstate.lookup.scopes).forEach(function (scopeId) { var rootScopeKey = (0, _schemaAndLookup.getRootModelKey)(formstate, scopeId); if ((0, _helperFunctions.inScope)(parentScope, rootScopeKey)) { formstate = (0, _fieldAndScopeStatus.clearStatus)(formstate, rootScopeKey); formstate = (0, _fieldAndScopeStatus.setTouched)(formstate, rootScopeKey, 'changed'); } }); return formstate; } // Too easy to mess up the signature when calling this. It's easy enough to just call synclyValidate yourself... // // export function addModelKeyAndValidate(formstate, modelKey, form, initialModel, formValidationSchema = {}) { // formstate = addModelKey(formstate, modelKey, initialModel, formValidationSchema); // return synclyValidate(formstate, modelKey, form); // } // In this case, you cannot get to the parent scope in a nested form without a whole lot of hassle, so this one is more helpful. // function deleteModelKeyAndValidateParentScope(formstate, modelKey, form) { // Do this before the nestedScopeId modelKey potentially gets deleted. var rootModelKey = (0, _schemaAndLookup.convertToRootModelKey)(formstate, modelKey); var _parseParentScope5 = (0, _helperFunctions.parseParentScope)(rootModelKey), _parseParentScope6 = (0, _slicedToArray2["default"])(_parseParentScope5, 1), parentScope = _parseParentScope6[0]; // Cannot use a relative model key for scopes above the nested scope... var nestedScopeId = formstate.nestedScopeId; formstate = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: null }); formstate = deleteModelKey(formstate, rootModelKey); formstate = synclyValidate(formstate, parentScope, form, formstate.lookup.idsByRootModelKey[parentScope]); return (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, formstate), {}, { nestedScopeId: nestedScopeId }); }