informed
Version:
A lightweight framework and utility for building powerful forms in React applications
1,308 lines (1,223 loc) • 70.2 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
var ObjectMap = require('./ObjectMap.js');
var debug$1 = require('./debug.js');
var utils = require('./utils.js');
var debug = debug$1.Debug('informed:FormController' + '\t');
var initializeValue = function initializeValue(value, _ref) {
var formatter = _ref.formatter,
parser = _ref.parser,
initialize = _ref.initialize,
mask = _ref.mask;
if (value != null) {
// Call users initialize if it was passed
if (initialize && !parser) {
return initialize(value);
}
if (formatter && !parser) {
var res = utils.informedFormat(value, formatter);
return res.value;
}
if (mask && !parser) {
return mask(value);
}
return value;
}
// Not needed but called out specifically
return undefined;
};
var initializeMask = function initializeMask(value, _ref2) {
var formatter = _ref2.formatter,
initialize = _ref2.initialize,
mask = _ref2.mask;
if (initialize) {
return initialize(value);
}
// Call formatter
if (formatter) {
var res = utils.informedFormat(value, formatter);
return res.value;
}
if (mask) {
return mask(value);
}
return value;
};
/* ----------------------- FormController ----------------------- */
var FormController = /*#__PURE__*/function () {
function FormController(options) {
var _this$options$current;
_rollupPluginBabelHelpers.classCallCheck(this, FormController);
// Set the options
this.options = options;
// Initialize listeners
this.subscriptions = new Map();
// Get schema stuff off of options
var _options$current = options.current,
ajv = _options$current.ajv,
schema = _options$current.schema,
fieldMap = _options$current.fieldMap,
adapter = _options$current.adapter;
// Create new ajv instance if passed
this.ajv = ajv ? new ajv({
allErrors: true
}) : null;
// TODO this fucks with json pointer stuff
// if (ajvErrors) {
// ajvErrors(this.ajv);
// }
this.ajvValidate = ajv ? this.ajv.compile(schema) : null;
// Add field map ( defaults to our field map )
this.fieldMap = adapter || fieldMap;
// This is the emitter lol
this.emitter = this;
// Map will store all fields by name
// Key => name
// Val => fieldMetaRef
// Why? so the form knows about field meta
this.fieldsMap = new Map();
// Map will store current validation request
// Key => name
// Val => {uuid, value}
// Why? So we know if validation request is stale or not
// We ALSO need to store value because of edge case:
//
// Assume sync validation "Must be at least 5 characters" and some async validation that takes 2 seconds
// 1. User types ddddd ( 5 inputs so we pass sync validation )
// 2. Because there is no sync validation async will trigger to validate username
// 3. While that occurs, user starts to Backspace the ddddd
// 4. The second user backspaces, sync has error so async never "re-occurs"
// 5. the sync request made on step 2 completes
// 6. It wipes out sync error
this.validationRequests = new Map();
this.dataRequests = new Map();
// For array fields lol
this.removalLocked = undefined;
// Initialize the controller state
this.state = {
pristine: true,
dirty: false,
submitted: false,
invalid: false,
valid: true,
submitting: false,
validating: 0,
gathering: 0,
values: {},
errors: {},
touched: {},
maskedValues: {},
dirt: {},
focused: {},
modified: {},
data: {},
initialValues: this.options.current.initialValues || {},
disabled: (_this$options$current = this.options.current.disabled) !== null && _this$options$current !== void 0 ? _this$options$current : false,
memory: {}
};
// Bind functions that will be called externally
this.getValue = this.getValue.bind(this);
this.setValue = this.setValue.bind(this);
this.setValueQuietly = this.setValueQuietly.bind(this);
this.setValues = this.setValues.bind(this);
this.setTheseValues = this.setTheseValues.bind(this);
this.resetPath = this.resetPath.bind(this);
this.getMaskedValue = this.getMaskedValue.bind(this);
this.setMaskedValue = this.setMaskedValue.bind(this);
this.setModifiedValue = this.setModifiedValue.bind(this);
this.getTouched = this.getTouched.bind(this);
this.setTouched = this.setTouched.bind(this);
this.getFocused = this.getFocused.bind(this);
this.setFocused = this.setFocused.bind(this);
this.getError = this.getError.bind(this);
this.setError = this.setError.bind(this);
this.reset = this.reset.bind(this);
this.validate = this.validate.bind(this);
this.asyncValidate = this.asyncValidate.bind(this);
this.getDirty = this.getDirty.bind(this);
this.setDirt = this.setDirt.bind(this);
this.getPristine = this.getPristine.bind(this);
this.getFormState = this.getFormState.bind(this);
this.getFormApi = this.getFormApi.bind(this);
this.getFieldState = this.getFieldState.bind(this);
this.getValid = this.getValid.bind(this);
this.on = this.on.bind(this);
this.emit = this.emit.bind(this);
this.removeListener = this.removeListener.bind(this);
this.remove = this.remove.bind(this);
this.swap = this.swap.bind(this);
this.insert = this.insert.bind(this);
this.pullOut = this.pullOut.bind(this);
this.register = this.register.bind(this);
this.deregister = this.deregister.bind(this);
this.getInitialValue = this.getInitialValue.bind(this);
this.initialize = this.initialize.bind(this);
this.reformat = this.reformat.bind(this);
this.lockRemoval = this.lockRemoval.bind(this);
this.unlockRemoval = this.unlockRemoval.bind(this);
this.resetField = this.resetField.bind(this);
this.getRemovalLocked = this.getRemovalLocked.bind(this);
this.isRemovalLocked = this.isRemovalLocked.bind(this);
this.submitForm = this.submitForm.bind(this);
this.touchAllFields = this.touchAllFields.bind(this);
this.keyDown = this.keyDown.bind(this);
this.validateAsync = this.validateAsync.bind(this);
this.gatherData = this.gatherData.bind(this);
this.validated = this.validated.bind(this);
this.debouncedValidateAsync = utils.debounceByName(this.validateAsync, this.options.current.debounceError);
this.debouncedGatherInfo = utils.debounceByName(this.gatherData, this.options.current.debounceGather);
this.getOptions = this.getOptions.bind(this);
this.validateField = this.validateField.bind(this);
this.getErrorMessage = this.getErrorMessage.bind(this);
this.clearValue = this.clearValue.bind(this);
this.clearAllValues = this.clearAllValues.bind(this);
this.clearError = this.clearError.bind(this);
this.clearAllErrors = this.clearAllErrors.bind(this);
this.getData = this.getData.bind(this);
this.setData = this.setData.bind(this);
this.getModified = this.getModified.bind(this);
this.updateValid = this.updateValid.bind(this);
this.focusFirstError = this.focusFirstError.bind(this);
this.scrollToFirstError = this.scrollToFirstError.bind(this);
this.setPristine = this.setPristine.bind(this);
this.disableForm = this.disableForm.bind(this);
this.enableForm = this.enableForm.bind(this);
this.getMemory = this.getMemory.bind(this);
this.restore = this.restore.bind(this);
this.fieldExists = this.fieldExists.bind(this);
}
_rollupPluginBabelHelpers.createClass(FormController, [{
key: "getOptions",
value: function getOptions() {
return this.options.current;
}
}, {
key: "getMemory",
value: function getMemory(name) {
return ObjectMap.ObjectMap.get(this.state.memory, name);
}
}, {
key: "getValue",
value: function getValue(name) {
return ObjectMap.ObjectMap.get(this.state.values, name);
}
}, {
key: "getMaskedValue",
value: function getMaskedValue(name) {
return ObjectMap.ObjectMap.get(this.state.maskedValues, name);
}
}, {
key: "fieldExists",
value: function fieldExists(name) {
return !!this.fieldsMap.get(name);
}
}, {
key: "setMaskedValue",
value: function setMaskedValue(name, value) {
return ObjectMap.ObjectMap.set(this.state.maskedValues, name, value);
}
}, {
key: "setModifiedValue",
value: function setModifiedValue(name, value) {
return ObjectMap.ObjectMap.set(this.state.modified, name, value);
}
}, {
key: "updateValid",
value: function updateValid() {
// Store previous state
var prevValid = this.state.valid;
// Now update
this.state.valid = ObjectMap.ObjectMap.empty(this.state.errors);
this.state.invalid = !this.state.valid;
// Call change handlers if needed
if (prevValid && !this.state.valid) {
this.emit('invalid');
}
if (!prevValid && this.state.valid) {
this.emit('valid');
}
}
}, {
key: "setValues",
value: function setValues(values) {
this.fieldsMap.forEach(function (fieldMeta) {
// Get value out of values object basd on path
var val = ObjectMap.ObjectMap.get(values, fieldMeta.current.name);
fieldMeta.current.fieldApi.setValue(val);
});
}
}, {
key: "disableForm",
value: function disableForm() {
this.disabled = true;
this.state.disabled = true;
// This will make all fields re render with updated value
this.emit('field', '_ALL_');
}
}, {
key: "enableForm",
value: function enableForm() {
this.disabled = undefined;
this.state.disabled = false;
// This will make all fields re render with updated value
this.emit('field', '_ALL_');
}
}, {
key: "setTheseValues",
value: function setTheseValues(values) {
this.fieldsMap.forEach(function (fieldMeta) {
// Get value out of values object basd on path
var val = ObjectMap.ObjectMap.get(values, fieldMeta.current.name);
// Only set if it is there
if (val != null) {
fieldMeta.current.fieldApi.setValue(val);
}
});
}
}, {
key: "resetPath",
value: function resetPath(path) {
this.fieldsMap.forEach(function (fieldMeta) {
// Only reset if parent path
if (utils.isChild(path, fieldMeta.current.name)) {
fieldMeta.current.fieldApi.reset();
}
});
}
}, {
key: "restore",
value: function restore(name) {
this.setValue(name, this.getMemory(name));
}
}, {
key: "setValueQuietly",
value: function setValueQuietly(name, value) {
this.setValue(name, value, undefined, undefined, true);
}
}, {
key: "setValue",
value: function setValue(name, value, e, key, quiet) {
var _this$fieldsMap$get;
debug("setValue ".concat(name), value);
var hasTrigger = e && _rollupPluginBabelHelpers["typeof"](e) === 'object' && e.triggers;
// Avoid set loops by determining if this field originated this set
if (hasTrigger && e.triggers.includes(name)) {
debug("NOT setting ".concat(name, " as it exists in the transitive path ").concat(JSON.stringify(e.triggers, null, 2)));
return;
}
// Get meta for field
var meta = ((_this$fieldsMap$get = this.fieldsMap.get(name)) === null || _this$fieldsMap$get === void 0 ? void 0 : _this$fieldsMap$get.current) || {};
// Remember Cursor position!
// Need try catch because of Safari Bullshit issue
try {
if (e && e.target && e.target.selectionStart) {
meta.setCursor(e.target.selectionStart, key);
}
} catch (e) {
// Need try catch because of Safari Bullshit issue
if (!(e instanceof TypeError)) {
throw e;
}
}
// Before we update the value lets save the previous one in a variable
var previousValue = this.getValue(name);
if (value === '') {
if (meta.allowEmptyString) {
var emptyValue = (meta === null || meta === void 0 ? void 0 : meta.type) === 'number' ? 0 : value;
// Override emptyValue if explicitly set
if (meta.emptyValue) {
emptyValue = meta.emptyValue;
}
debug("Setting ".concat(name, "'s value to ").concat(emptyValue, " because allowEmptyString is set"));
ObjectMap.ObjectMap.set(this.state.values, name, emptyValue);
// Special if check for modified
if (meta.getInitialValue && meta.getInitialValue() != emptyValue) {
ObjectMap.ObjectMap.set(this.state.modified, name, emptyValue);
} else {
debug("Removing ".concat(name, "'s modified"));
ObjectMap.ObjectMap["delete"](this.state.modified, emptyValue);
}
ObjectMap.ObjectMap.set(this.state.maskedValues, name, value);
} else {
debug("Setting ".concat(name, "'s value to undefiend"));
ObjectMap.ObjectMap.set(this.state.values, name, undefined);
ObjectMap.ObjectMap.set(this.state.modified, name, undefined);
ObjectMap.ObjectMap.set(this.state.maskedValues, name, undefined);
}
} else if ((meta === null || meta === void 0 ? void 0 : meta.type) === 'number' && value !== undefined) {
var val = value;
var maskedVal = value;
// call mask if passed
if (meta.mask && !meta.maskOnBlur) {
maskedVal = meta.mask(val);
}
// // Only parse if parser was passed
if (meta.parser) {
val = val != null ? utils.informedParse(val, meta.parser) : val;
}
debug("Setting ".concat(name, "'s value to ").concat(+val));
ObjectMap.ObjectMap.set(this.state.values, name, +val);
// Special if check for modified
if (meta.getInitialValue && meta.getInitialValue() != val ||
// Always set for modifiedOnMount
meta.modifyOnMount) {
ObjectMap.ObjectMap.set(this.state.modified, name, +val);
} else {
// Note: Important that we set to undefined and NOT call delete
// Why? See readme 4.44.2
debug("Removing ".concat(name, "'s modified"));
ObjectMap.ObjectMap.set(this.state.modified, name, undefined);
}
debug("Setting ".concat(name, "'s maskedValue to"), +maskedVal);
ObjectMap.ObjectMap.set(this.state.maskedValues, name, +maskedVal);
} else {
var _val = value;
var _maskedVal = value;
// Only clean if clean was passed
if (meta.clean) {
_val = meta.clean(_val);
_maskedVal = _val;
}
// Call formatter parser if passed
if (meta.formatter) {
var res = utils.informedFormat(_val, meta.formatter, this.getMaskedValue(name), meta.dir);
meta.setCursorOffset(res.offset, key);
_maskedVal = res.value;
_val = _maskedVal;
}
// call mask if passed
if (meta.mask && !meta.maskOnBlur) {
_val = meta.mask(_val);
_maskedVal = _val;
}
// // Only parse if parser was passed
if (meta.parser) {
_val = _val != null ? utils.informedParse(_val, meta.parser) : _val;
}
// vvvvvvvvvvvvv VALUE UPDATE OCCURS HERE vvvvvvvvvvvv
debug("Setting ".concat(name, "'s value to"), _val);
ObjectMap.ObjectMap.set(this.state.values, name, _val);
// Special if check for modified
// We want to set even if field is not on screen ( does not have getter for initial )
if (!meta.getInitialValue || meta.getInitialValue() != _val ||
// Always set for modifiedOnMount
meta.modifyOnMount) {
debug("Setting ".concat(name, "'s modified to"), _val);
ObjectMap.ObjectMap.set(this.state.modified, name, _val);
} else {
// Note: Important that we set to undefined and NOT call delete
// Why? See readme 4.44.2
debug("Removing ".concat(name, "'s modified"));
ObjectMap.ObjectMap.set(this.state.modified, name, undefined);
}
debug("Setting ".concat(name, "'s maskedValue to"), _maskedVal);
ObjectMap.ObjectMap.set(this.state.maskedValues, name, _maskedVal);
}
// We only need to call validate if the user gave us one
// and they want us to validate on change
// Example validateOn = "change" ("change-change")==> true
// Example validateOn = "blur" ("blur-blur") ==> false
// Example validateOn = "submit" ("submit-submit")==> false
// Example validateOn = "change-blur" ==> true
// Example validateOn = "change-submit" ==> true
// Example validateOn = "blur-submit" ==> false
if (meta.validate && meta.validateOn.includes('change')) {
var _val2 = ObjectMap.ObjectMap.get(this.state.values, name);
debug("Validating after change ".concat(name, " ").concat(_val2));
ObjectMap.ObjectMap.set(this.state.errors, name, meta.validate(_val2, this.state.values));
}
// Same thing but for YUP schema
// I dont think we need this anymore as its done by the generate function ..... TODO maybe remove
if (meta.yupSchema && meta.validateOn.includes('change')) {
// Only call if we dont already have error
if (this.getError(name) === undefined) {
var _val3 = ObjectMap.ObjectMap.get(this.state.values, name);
debug("Validating YUP after change ".concat(name, " ").concat(_val3));
ObjectMap.ObjectMap.set(this.state.errors, name, utils.validateYupField(meta.yupSchema, _val3));
}
}
// We only need to call asyncValidate if
// 1. the user gave us one
// 2. they want us to validate on change
// 3. We don't have a sync error
// Example validateOn = "change" ("change-change")==> true
// Example validateOn = "blur" ("blur-blur") ==> false
// Example validateOn = "submit" ("submit-submit")==> false
// Example validateOn = "change-blur" ==> false
// Example validateOn = "change-submit" ==> false
// Example validateOn = "blur-submit" ==> false
if (meta.asyncValidate && meta.validateOn === 'change') {
// Get error to determine if we even want to validateAsync
if (this.getError(name) === undefined) this.debouncedValidateAsync(name);
}
// Always remember to update pristine and valid here
if (!quiet) {
this.state.pristine = false;
this.state.dirty = !this.state.pristine;
ObjectMap.ObjectMap.set(this.state.dirt, name, true);
}
// Remember to update valid
this.updateValid();
// Call users onChange if it exists
if (meta.onChange) {
var fieldState = this.getFieldState(name);
// Add the previous value to the field state object
fieldState.previousValue = previousValue;
meta.onChange(fieldState, e);
}
// Call users onNativeChange if we had native event and func
if (e && meta.onNativeChange) {
var _fieldState = this.getFieldState(name);
// Add the previous value to the field state object
_fieldState.previousValue = previousValue;
meta.onNativeChange(_fieldState, e);
}
// Emit native event
// Transitive sets are special
// Example:
// A Triggers B
// B Triggers C
// C Triggers A
//
// A ( trigger1 ) ----> B ( trigger2 )----> C ( trigger3 ) ----> A ( trigger ) ----> B LOOP
//
// To aviod this we need to pass the triggers to the event
if (hasTrigger) {
this.emit('field-native', name, [].concat(_rollupPluginBabelHelpers.toConsumableArray(e.triggers), [name]));
} else if (e) {
this.emit('field-native', name, [name]);
}
if (meta.gatherData && !meta.gatherOnBlur) {
// Get error to determine if we even want to validateAsync
this.debouncedGatherInfo(name);
}
// Normal field event
this.emit('field', name);
// Special event when fields value changes
this.emit('field-value', name);
this.emit('field-modified', name);
this.emit('field-value-set', name);
}
}, {
key: "validateField",
value: function validateField(name) {
var _this$fieldsMap$get2;
// Get meta for field
var meta = (_this$fieldsMap$get2 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get2 === void 0 ? void 0 : _this$fieldsMap$get2.current;
if (!meta) return;
if (meta.validate) {
var val = ObjectMap.ObjectMap.get(this.state.values, name);
debug("Validating field ".concat(name, " via validateField with value ").concat(val));
ObjectMap.ObjectMap.set(this.state.errors, name, meta.validate(val, this.state.values));
}
// Same thing but for YUP schema
if (meta.yupSchema) {
// Only call if we dont already have error
if (this.getError(name) === undefined) {
var _val4 = ObjectMap.ObjectMap.get(this.state.values, name);
debug("Validating YUP field via validateField ".concat(name, " ").concat(_val4));
ObjectMap.ObjectMap.set(this.state.errors, name, utils.validateYupField(meta.yupSchema, _val4));
}
}
// TODO maybe do async validation here !?!?!?!
// Remember to update valid
this.updateValid();
this.emit('field', name);
}
}, {
key: "getModified",
value: function getModified(name) {
return ObjectMap.ObjectMap.get(this.state.modified, name);
}
}, {
key: "getFocused",
value: function getFocused(name) {
return ObjectMap.ObjectMap.get(this.state.focused, name);
}
}, {
key: "setFocused",
value: function setFocused(name, value, e) {
var _this$fieldsMap$get3;
debug("Setting ".concat(name, "'s focused to ").concat(value));
// Get meta for field
var meta = ((_this$fieldsMap$get3 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get3 === void 0 ? void 0 : _this$fieldsMap$get3.current) || {};
// Update the state
ObjectMap.ObjectMap.set(this.state.focused, name, value);
// Call users onFoucs if it exists
if (meta.onFocus) {
var fieldState = this.getFieldState(name);
meta.onFocus(fieldState, e);
}
// emit field update
this.emit('field', name);
}
}, {
key: "getTouched",
value: function getTouched(name) {
return ObjectMap.ObjectMap.get(this.state.touched, name);
}
}, {
key: "setTouched",
value: function setTouched(name, value, e) {
var _this$fieldsMap$get4;
debug("Setting ".concat(name, "'s touched to ").concat(value));
// Get meta for field
var meta = ((_this$fieldsMap$get4 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get4 === void 0 ? void 0 : _this$fieldsMap$get4.current) || {};
// Update the state
ObjectMap.ObjectMap.set(this.state.touched, name, value);
// Update value if maskOnBlur and we have mask
if (meta.mask && meta.maskOnBlur) {
var val = ObjectMap.ObjectMap.get(this.state.values, name);
var maskedVal = val;
maskedVal = meta.mask(val);
// // Only parse if parser was passed
if (meta.parser) {
val = val != null ? utils.informedParse(val, meta.parser) : val;
}
debug("Setting ".concat(name, "'s value to"), maskedVal);
ObjectMap.ObjectMap.set(this.state.values, name, maskedVal);
debug("Setting ".concat(name, "'s maskedValue to"), maskedVal);
ObjectMap.ObjectMap.set(this.state.maskedValues, name, maskedVal);
}
// We only need to call validate if the user gave us one
// and they want us to validate on blur
// Example validateOn = "change" ("change-change")==> true
// Example validateOn = "blur" ("blur-blur") ==> true
// Example validateOn = "submit" ("submit-submit")==> false
// Example validateOn = "change-blur" ==> true
// Example validateOn = "change-submit" ==> true
// Example validateOn = "blur-submit" ==> true
if (meta.validate && (meta.validateOn.includes('blur') || meta.validateOn.includes('change'))) {
var _val5 = ObjectMap.ObjectMap.get(this.state.values, name);
debug("Validating after blur ".concat(name, " ").concat(_val5));
ObjectMap.ObjectMap.set(this.state.errors, name, meta.validate(_val5, this.state.values));
}
// We only need to call asyncValidate if
// 1. the user gave us one
// 2. they want us to validate on blur
// 3. We don't have a sync error
// Example validateOn = "change" ("change-change")==> true
// Example validateOn = "blur" ("blur-blur") ==> true
// Example validateOn = "submit" ("submit-submit")==> false
// Example validateOn = "change-blur" ==> true
// Example validateOn = "change-submit" ==> false
// Example validateOn = "blur-submit" ==> false
if (meta.asyncValidate && (meta.validateOn === 'blur' || meta.validateOn === 'change-blur' || meta.validateOn === 'change')) {
// Get error to determine if we even want to validateAsync
if (this.getError(name) === undefined) {
this.validateAsync(name);
}
}
// Remember to update valid
this.updateValid();
// Gather data on blur if user passed gatherOnBlur
if (meta.gatherData && meta.gatherOnBlur) {
this.debouncedGatherInfo(name);
}
// Call users onBlur if it exists
if (meta.onBlur) {
var fieldState = this.getFieldState(name);
meta.onBlur(fieldState, e);
}
this.emit('field', name);
}
}, {
key: "getData",
value: function getData(name) {
return ObjectMap.ObjectMap.get(this.state.data, name);
}
}, {
key: "setData",
value: function setData(name, value) {
debug("Setting ".concat(name, "'s data to ").concat(value));
ObjectMap.ObjectMap.set(this.state.data, name, value);
this.emit('field', name);
}
}, {
key: "getError",
value: function getError(name) {
return ObjectMap.ObjectMap.get(this.state.errors, name);
}
}, {
key: "setError",
value: function setError(name, value) {
debug("Setting ".concat(name, "'s error to ").concat(value));
ObjectMap.ObjectMap.set(this.state.errors, name, value);
this.state.valid = ObjectMap.ObjectMap.empty(this.state.errors);
this.state.invalid = !this.state.valid;
this.emit('field', name);
}
}, {
key: "getInitialValue",
value: function getInitialValue(name) {
return ObjectMap.ObjectMap.get(this.state.initialValues, name);
}
}, {
key: "getDirty",
value: function getDirty(name) {
return !!ObjectMap.ObjectMap.get(this.state.dirt, name);
}
}, {
key: "setDirt",
value: function setDirt(name, value) {
return ObjectMap.ObjectMap.set(this.state.dirt, name, value);
}
}, {
key: "getPristine",
value: function getPristine(name) {
return !this.getDirty(name);
}
}, {
key: "getValid",
value: function getValid(name) {
// Valid when we have no error
return ObjectMap.ObjectMap.get(this.state.errors, name) === undefined;
}
}, {
key: "getFormState",
value: function getFormState() {
return this.state;
}
}, {
key: "clearValue",
value: function clearValue(name) {
this.setValue(name, undefined);
this.emit('clear', name);
}
}, {
key: "clearAllValues",
value: function clearAllValues() {
var _this = this;
this.fieldsMap.forEach(function (fieldMeta) {
_this.clearValue(fieldMeta.current.name);
});
}
}, {
key: "clearError",
value: function clearError(name) {
this.setError(name, undefined);
}
}, {
key: "clearAllErrors",
value: function clearAllErrors() {
var _this2 = this;
this.fieldsMap.forEach(function (fieldMeta) {
_this2.clearError(fieldMeta.current.name);
});
}
}, {
key: "setPristine",
value: function setPristine(pristine) {
this.state.pristine = pristine;
this.state.dirty = !this.state.pristine;
// Just need to trigger a form state update
this.emit('field');
}
}, {
key: "getFormApi",
value: function getFormApi() {
return {
getValue: this.getValue,
setValue: this.setValue,
setValueQuietly: this.setValueQuietly,
getMaskedValue: this.getMaskedValue,
setMaskedValue: this.setMaskedValue,
setModifiedValue: this.setModifiedValue,
getTouched: this.getTouched,
setTouched: this.setTouched,
getError: this.getError,
setError: this.setError,
getFocused: this.getFocused,
setFocused: this.setFocused,
getData: this.getData,
setData: this.setData,
getModified: this.getModified,
resetField: this.resetField,
reset: this.reset,
getFormState: this.getFormState,
getPristine: this.getPristine,
getDirty: this.getDirty,
setDirt: this.setDirt,
validateField: this.validateField,
getFieldState: this.getFieldState,
getInitialValue: this.getInitialValue,
touchAllFields: this.touchAllFields,
validate: this.validate,
asyncValidate: this.asyncValidate,
setValues: this.setValues,
setTheseValues: this.setTheseValues,
resetPath: this.resetPath,
submitForm: this.submitForm,
clearValue: this.clearValue,
clearAllValues: this.clearAllValues,
clearError: this.clearError,
clearAllErrors: this.clearAllErrors,
focusFirstError: this.focusFirstError,
setPristine: this.setPristine,
disable: this.disableForm,
enable: this.enableForm,
restore: this.restore,
getMemory: this.getMemory,
fieldExists: this.fieldExists
};
}
}, {
key: "getFieldState",
value: function getFieldState(name) {
var _this$fieldsMap$get5;
// Get meta for field
var meta = ((_this$fieldsMap$get5 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get5 === void 0 ? void 0 : _this$fieldsMap$get5.current) || {};
var error = this.getError(name);
var focused = !!this.getFocused(name);
var modified = !!this.getModified(name);
var dirty = this.getDirty(name);
var valid = this.getValid(name);
var touched = !!this.getTouched(name);
var pristine = !dirty;
var validating = !!this.validationRequests.get(name);
var gathering = !!this.dataRequests.get(name);
var memory = this.getMemory(name);
var showError = false;
if (meta && meta.showErrorIfError) {
showError = error !== undefined;
} else if (meta && meta.showErrorIfDirty) {
showError = error !== undefined && (dirty || touched);
} else if (meta && meta.showErrorIfTouched) {
showError = error !== undefined && touched;
}
// $relevant
// $focused
return {
value: this.getValue(name),
maskedValue: this.getMaskedValue(name),
modified: modified,
touched: touched,
error: this.getError(name),
data: this.getData(name),
pristine: pristine,
dirty: dirty,
valid: valid,
invalid: !valid,
showError: showError,
validating: validating,
gathering: gathering,
focused: focused,
memory: memory
};
}
}, {
key: "remove",
value: function remove(name) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var meta = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
debug('Remove', name);
if (!this.removalLocked) {
var _options$value = options.value,
keepValue = _options$value === void 0 ? false : _options$value,
_options$error = options.error,
keepError = _options$error === void 0 ? false : _options$error,
_options$touched = options.touched,
keepTouched = _options$touched === void 0 ? false : _options$touched;
// If user passed in remember for this field add it to memory
if (meta.remember) {
var valueToRemember = this.getValue(name);
// Only remember if there is something to remember ( that way we dont wipe previous memory )
if (valueToRemember != undefined) {
debug('Remembering', name, valueToRemember);
ObjectMap.ObjectMap.set(this.state.memory, name, valueToRemember);
}
}
if (!keepValue) {
debug('Delete Value', name);
ObjectMap.ObjectMap["delete"](this.state.values, name);
debug('Delete Modified', name);
ObjectMap.ObjectMap["delete"](this.state.modified, name);
debug('Delete Masked', name);
ObjectMap.ObjectMap["delete"](this.state.maskedValues, name);
}
if (!keepTouched) {
debug('Delete Touched', name);
ObjectMap.ObjectMap["delete"](this.state.touched, name);
}
if (!keepError) {
debug('Delete Errors', name);
ObjectMap.ObjectMap["delete"](this.state.errors, name);
}
debug('Delete Dirt', name);
ObjectMap.ObjectMap["delete"](this.state.dirt, name);
debug('Delete Focused', name);
ObjectMap.ObjectMap["delete"](this.state.focused, name);
debug('Delete Info', name);
ObjectMap.ObjectMap["delete"](this.state.data, name);
// Remember to update valid
this.updateValid();
// Final field change
this.emit('field', name);
// Special event when fields value changes
this.emit('field-value', name);
this.emit('field-modified', name);
} else {
debug('Removal locked so NOT removing', name);
}
}
}, {
key: "swap",
value: function swap(name, a, b) {
debug('Swap', name, a, b);
debug('Swap Values');
ObjectMap.ObjectMap.swap(this.state.values, name, a, b);
debug('Swap Modified');
ObjectMap.ObjectMap.swap(this.state.modified, name, a, b);
debug('Swap MaskedValues');
ObjectMap.ObjectMap.swap(this.state.maskedValues, name, a, b);
debug('Swap Touched');
ObjectMap.ObjectMap.swap(this.state.touched, name, a, b);
debug('Swap Errors');
ObjectMap.ObjectMap.swap(this.state.errors, name, a, b);
debug('Swap Dirt');
ObjectMap.ObjectMap.swap(this.state.dirt, name, a, b);
debug('Swap Focused');
ObjectMap.ObjectMap.swap(this.state.focused, name, a, b);
debug('Swap Data');
ObjectMap.ObjectMap.swap(this.state.data, name, a, b);
// DO NOT emit event here we want to delay it on purpose because otherwise relevance will trigger with bad state
// this.emit("field", name);
this.state.pristine = false;
this.state.dirty = !this.state.pristine;
}
}, {
key: "insert",
value: function insert(name, index, value) {
debug('Insert', name, index, value);
debug('Insert into Values');
ObjectMap.ObjectMap.insert(this.state.values, name, index, value);
debug('Insert into Modified');
ObjectMap.ObjectMap.insert(this.state.modified, name, index, undefined);
debug('Insert into MaskedValues');
ObjectMap.ObjectMap.insert(this.state.maskedValues, name, index, value);
debug('Insert into Touched');
ObjectMap.ObjectMap.insert(this.state.touched, name, index, undefined);
debug('Insert into Errors');
ObjectMap.ObjectMap.insert(this.state.errors, name, index, undefined);
debug('Insert into Dirt');
ObjectMap.ObjectMap.insert(this.state.dirt, name, index, undefined);
debug('Insert into Focused');
ObjectMap.ObjectMap.insert(this.state.focused, name, index, undefined);
debug('Insert into Data');
ObjectMap.ObjectMap.insert(this.state.data, name, index, undefined);
// Update pristine and dirty state
this.state.pristine = false;
this.state.dirty = !this.state.pristine;
}
}, {
key: "pullOut",
value: function pullOut(name) {
debug('Pull', name);
debug("Pull ".concat(name, " from values"));
ObjectMap.ObjectMap["delete"](this.state.values, name);
debug("Pull ".concat(name, " from modified"));
ObjectMap.ObjectMap["delete"](this.state.modified, name);
debug("Pull ".concat(name, " from maskedValues"));
ObjectMap.ObjectMap["delete"](this.state.maskedValues, name);
debug("Pull ".concat(name, " from touched"));
ObjectMap.ObjectMap["delete"](this.state.touched, name);
debug("Pull ".concat(name, " from errors"));
ObjectMap.ObjectMap["delete"](this.state.errors, name);
debug("Pull ".concat(name, " from dirt"));
ObjectMap.ObjectMap["delete"](this.state.dirt, name);
debug("Pull ".concat(name, " from focused"));
ObjectMap.ObjectMap["delete"](this.state.focused, name);
debug("Pull ".concat(name, " from data"));
ObjectMap.ObjectMap["delete"](this.state.data, name);
// DO NOT emit event here we want to delay it on purpose because otherwise relevance will trigger with bad state
// this.emit("field", name);
this.state.pristine = false;
this.state.dirty = !this.state.pristine;
}
}, {
key: "register",
value: function register(name, meta) {
debug('Register', name, meta);
// Register the meta
if (!this.fieldsMap.get(name)) {
this.fieldsMap.set(name, meta);
this.emit('field', name);
}
}
}, {
key: "deregister",
value: function deregister(name) {
if (this.fieldsMap.get(name)) {
debug('De-Register', name);
this.fieldsMap["delete"](name);
this.emit('field', name);
}
}
// Third parameter is to prevent any form renders when it first gets initialized
}, {
key: "initialize",
value: function initialize(name, meta) {
var emit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
debug('Initialize', name, 'emit:', emit, 'state:', this.state);
// Initialize value if needed
// If we already have value i.e "saved"
// use that ( it was not removed on purpose! )
// Otherwise use the fields initial value
if (this.getValue(name) === undefined && meta.current.initialValue != null && (meta.current.initializeValueIfPristine ? this.state.pristine : true)) {
var _meta$current = meta.current,
formatter = _meta$current.formatter,
parser = _meta$current.parser,
initialize = _meta$current.initialize,
clean = _meta$current.clean,
mask = _meta$current.mask,
modifyOnMount = _meta$current.modifyOnMount;
// Clean value if we have clean function
var cleanedValue = clean ? clean(meta.current.initialValue) : meta.current.initialValue;
var initialValue = initializeValue(cleanedValue, {
formatter: formatter,
parser: parser,
initialize: initialize,
mask: mask
});
var initialMask = initializeMask(cleanedValue, {
formatter: formatter,
initialize: initialize,
mask: mask
});
debug("Initializing ".concat(name, "'s value to ").concat(initialValue));
ObjectMap.ObjectMap.set(this.state.values, name, initialValue);
debug("Initializing ".concat(name, "'s maskedValue to ").concat(initialMask));
ObjectMap.ObjectMap.set(this.state.maskedValues, name, initialMask);
if (modifyOnMount) {
debug("Initializing ".concat(name, "'s modified to ").concat(initialValue, " because modifyOnMount was passed."));
ObjectMap.ObjectMap.set(this.state.modified, name, initialValue);
}
}
// Might need to set initial error
if (meta.current.validate && meta.current.validateOnMount) {
var val = ObjectMap.ObjectMap.get(this.state.values, name);
debug("Validating on mount ".concat(name, " ").concat(val), this.state);
ObjectMap.ObjectMap.set(this.state.errors, name, meta.current.validate(val, this.state.values));
}
// validateOnMount="sync" DONT validateOnMount={true} DO
if (meta.current.asyncValidate && meta.current.validateOnMount === true) {
// Get error to determine if we even want to validateAsync
if (this.getError(name) === undefined) this.validateAsync(name);
}
// Check if the form is valid
this.state.valid = ObjectMap.ObjectMap.empty(this.state.errors);
this.state.invalid = !this.state.valid;
if (meta.current.gatherData && meta.current.gatherOnMount === true) {
// Get error to determine if we even want to validateAsync
this.debouncedGatherInfo(name);
}
if (emit) this.emit('field', name);
// Special event when fields value changes ( this if first time so its technically a change to initial value)
if (emit) this.emit('field-value', name);
// Specifically did NOT call field-modified here
}
}, {
key: "validated",
value: function validated(name, res) {
debug("Setting ".concat(name, "'s error to ").concat(res, " with ").concat(this.state.validating, " validations left"));
ObjectMap.ObjectMap.set(this.state.errors, name, res);
// Remember to update valid
this.updateValid();
// Clear out validating
this.validationRequests["delete"](name);
// If we are not still validating, and we were submitting, then submit form
// If we are async validating then dont submit yet
if (this.state.validating > 0) {
debug("Still validating ".concat(this.state.validating, " others so just update state."));
this.emit('field', name);
return;
}
// If we were submitting
if (this.state.submitting) {
// Check validity and perform submission if valid
if (this.valid()) {
debug('Submit', this.state);
this.emit('field', name);
this.emit('submit');
} else {
debug('Fail', this.state);
if (this.options.current.focusOnInvalid) {
this.focusFirstError();
}
if (this.options.current.scrollOnInvalid) {
this.scrollToFirstError();
}
this.emit('field', name);
this.emit('failure');
}
this.state.submitting = false;
}
// If we had done function
if (this.done) {
// Call done only if valid
if (this.valid()) this.done();
// Then always clear
this.done = undefined;
}
// Always update
this.emit('field', name);
}
}, {
key: "gathered",
value: function gathered(name, res) {
debug("Setting ".concat(name, "'s data to ").concat(res, " with ").concat(this.state.gathering, " gatherers left"));
ObjectMap.ObjectMap.set(this.state.data, name, res);
// Clear out gathering
this.dataRequests["delete"](name);
// Always update
this.emit('field', name);
this.emit('field-value', name);
}
}, {
key: "gatheredError",
value: function gatheredError(name, err) {
debug("Setting ".concat(name, "'s error to ").concat(err, " with ").concat(this.state.gathering, " gatherers left"));
ObjectMap.ObjectMap.set(this.state.errors, name, err);
// Clear out gathering
this.dataRequests["delete"](name);
// Remember to update valid
this.updateValid();
// Always update
this.emit('field', name);
this.emit('field-value', name);
}
}, {
key: "validateAsync",
value: function validateAsync(name) {
var _this$fieldsMap$get6,
_this3 = this;
debug('VALIDATING ASYNC', name);
// Get meta for field
var meta = (_this$fieldsMap$get6 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get6 === void 0 ? void 0 : _this$fieldsMap$get6.current;
// Get the value
var value = this.getValue(name);
if (meta && meta.asyncValidate) {
this.state.validating = this.state.validating + 1;
var uuid = utils.uuidv4();
debug('REQUEST', uuid);
this.validationRequests.set(name, {
uuid: uuid,
value: value
});
// Because we may have been debounced need to update field here
this.emit('field', name);
meta.asyncValidate(value, this.state.values).then(function (res) {
_this3.state.validating = _this3.state.validating - 1;
var stale = _this3.validationRequests.get(name).uuid !== uuid;
// What in the hell is invalid and why do I need it??
// 1. User types ddddd ( 5 inputs so we pass sync validation )
// 2. Because there is no sync validation async will trigger to validate username
// 3. While that occurs, user starts to Backspace the ddddd
// 4. The second user backspaces, sync has error so async never "re-occurs"
// 5. the sync request made on step 2 completes
// 6. It wipes out sync error
var invalid = _this3.validationRequests.get(name).value !== _this3.getValue(name);
if (!stale && !invalid) {
debug('FINISH', uuid);
_this3.validated(name, res);
} else {
debug("".concat(stale ? 'STALE' : 'INVALID', " THEN"), uuid, value, _this3.getValue(name));
}
})["catch"](function (err) {
_this3.state.validating = _this3.state.validating - 1;
var stale = _this3.validationRequests.get(name).uuid !== uuid;
var invalid = _this3.validationRequests.get(name).value !== _this3.getValue(name);
if (!stale && !invalid) {
debug('FINISH', uuid);
_this3.validated(name, err.message);
} else {
debug("".concat(stale ? 'STALE' : 'INVALID', " THEN"), uuid, value, _this3.getValue(name));
}
});
}
}
}, {
key: "gatherData",
value: function gatherData(name) {
var _this$fieldsMap$get7,
_this4 = this;
debug('EXECUTING INFO ASYNC', name);
// Get meta for field
var meta = (_this$fieldsMap$get7 = this.fieldsMap.get(name)) === null || _this$fieldsMap$get7 === void 0 ? void 0 : _this$fieldsMap$get7.current;
// Get the value
var value = this.getValue(name);
if (meta && meta.gatherData) {
this.state.gathering = this.state.gathering + 1;
var uuid = utils.uuidv4();
debug('DATA REQUEST', uuid);
this.dataRequests.set(name, {
uuid: uuid,
value: value
});
// Because we may have been debounced need to update field here
this.emit('field', name);
meta.gatherData(value, this.state).then(function (res) {
_this4.state.gathering = _this4.state.gathering - 1;
var stale = _this4.dataRequests.get(name).uuid !== uuid;
// What in the hell is invalid and why do I need it??
// because the value can be outdated
var invalid = _this4.dataRequests.get(name).value !== _this4.getValue(name);
if (!stale && !invalid) {
debug('DATA FINISH', uuid);
_this4.gathered(name, res);
} else {
debug("".concat(stale ? 'STALE' : 'INVALID', " THEN"), uuid, value, _this4.getValue(name));
}
})["catch"](function (err) {
_this4.state.gathering = _this4.state.gathering - 1;
var stale = _this4.dataRequests.get(name).uuid !== uuid;
var invalid = _this4.dataRequests.get(name).value !== _this4.getValue(name);
if (!stale && !invalid) {
debug('DATA FINISH', uuid);
_this4.gatheredError(name, err.message);
} else {
debug("".concat(stale ? 'STALE' : 'INVALID', " THEN"), uuid, value, _this4.getValue(name));
}
});
}
}
}, {
key: "reset",
value: function reset() {
var _ref3, _ref4, _this$disabled;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
debug('----------------------------- Resetting Form -----------------------------');
// There are cases where we dont want to blow away all the form values
if (this.options.current.resetOnlyOnscreen) {
var _this$options$current2;
debug('Resetting only onscreen inputs');
this.state.initialValues = (_this$options$current2 = this.options.current.initialValues) !== null && _this$options$current2 !== void 0 ? _this$options$current2 : {};
this.fieldsMap.forEach(function (fieldMeta) {
fieldMeta.current.fieldApi.reset({
resetValue: resetValues
});
});
this.emit('reset');
return;
}
var values = options.values,
_options$resetValues = options.resetValues,
resetValues = _options$resetValues === void 0 ? true : _options$resetValues;
this.state = {
pristine: true,
dirty: false,
submitted: false,
invalid: false,
valid: true,
submitting: false,
validating: 0,
gathering: 0,
values: resetValues ? {} : this.state.values,
errors: {},
touched: {},
maskedValues: resetValues ? {} : this.state.maskedValues,
dirt: {},
focused: {},
modified: {},
data