mobx-react-form
Version:
Reactive MobX Form State Management
202 lines (171 loc) • 5.8 kB
text/typescript
import _ from "lodash";
import { ObservableMap, values as mobxValues, keys as mobxKeys } from "mobx";
import { FieldInterface } from "./models/FieldInterface";
import { AllowedFieldPropsTypes, FieldPropsEnum, FieldPropsOccurrence } from "./models/FieldProps";
import { props } from "./props";
const getObservableMapValues = (observableMap: ObservableMap):
ReadonlyArray<FieldInterface> => mobxValues(observableMap);
const getObservableMapKeys = (observableMap: ObservableMap):
ReadonlyArray<FieldInterface> => mobxKeys(observableMap);
const checkObserveItem =
(change: any) =>
({ key, to, type, exec }: any) =>
change.type === type &&
change.name === key &&
change.newValue === to &&
exec.apply(change, [change]);
const checkObserve = (collection: object[]) => (change: any) =>
collection.map(checkObserveItem(change));
const checkPropOccurrence = ({ type, data }: any): boolean => {
let $check: any;
switch (type) {
case FieldPropsOccurrence.some: $check = ($data: object) => _.some($data, Boolean); break;
case FieldPropsOccurrence.every: $check = ($data: object) => _.every($data, Boolean); break;
default: throw new Error('Occurrence not found for specified prop');
}
return $check(data);
};
const hasProps = ($type: string, $data: any): boolean => {
let $props;
switch ($type) {
case AllowedFieldPropsTypes.computed:
$props = props.computed;
break;
case AllowedFieldPropsTypes.observable:
$props = [
FieldPropsEnum.fields,
...props.computed,
...props.editable,
];
break;
case AllowedFieldPropsTypes.editable:
$props = [
...props.editable,
...props.validation,
...props.functions,
...props.handlers,
];
break;
case AllowedFieldPropsTypes.all:
$props = [
FieldPropsEnum.id,
FieldPropsEnum.key,
FieldPropsEnum.name,
FieldPropsEnum.path,
...props.computed,
...props.editable,
...props.validation,
...props.functions,
...props.handlers,
];
break;
default:
$props = null;
}
return _.intersection($data, $props).length > 0;
};
/**
Check Allowed Properties
*/
const allowedProps = (type: string, data: string[]): void => {
if (hasProps(type, data)) return;
const $msg = "The selected property is not allowed";
throw new Error(`${$msg} (${JSON.stringify(data)})`);
};
/**
Throw Error if undefined Fields
*/
const throwError = (path: string, fields: any, msg: null | string = null): void => {
if (!_.isNil(fields)) return;
const $msg = _.isNil(msg) ? "The selected field is not defined" : msg;
throw new Error(`${$msg} (${path})`);
};
const pathToStruct = (path: string): string => {
let struct;
struct = _.replace(path, new RegExp("[.]\\d+($|.)", "g"), "[].");
struct = _.replace(struct, "..", ".");
struct = _.trim(struct, ".");
return struct;
};
const isArrayFromStruct = (struct: string[], structPath: string): boolean => {
if (isArrayOfStrings(struct)) return !!struct
.filter((s) => s.startsWith(structPath))
.find((s) => s.substring(structPath.length) === "[]")
|| _.endsWith(struct?.find((e) => e === structPath), '[]');
else return false;
};
const hasSome = (obj: any, keys: any): boolean =>
_.some(keys, _.partial(_.has, obj));
const isEmptyArray = (field: any): boolean =>
_.isEmpty(field) && Array.isArray(field);
const isArrayOfStrings = (struct: any): boolean =>
Array.isArray(struct) && _.every(struct, _.isString);
const isArrayOfObjects = (fields: any): boolean =>
Array.isArray(fields) && _.every(fields, _.isPlainObject);
const getKeys = (fields: any) =>
_.union(..._.map(_.values(fields), (values) => _.keys(values)));
const hasUnifiedProps = ({ fields }: any) =>
!isArrayOfStrings({ fields }) && hasProps(AllowedFieldPropsTypes.editable, getKeys(fields));
const hasSeparatedProps = (initial: any): boolean =>
hasSome(initial, props.separated) || hasSome(initial, props.validation);
const allowNested = (field: any, strictProps: boolean): boolean =>
_.isObject(field) &&
!_.isDate(field) &&
!_.has(field, FieldPropsEnum.fields) &&
!_.has(field, FieldPropsEnum.class) &&
(!hasSome(field, [
...props.editable,
...props.handlers,
...props.validation,
...props.functions,
]) || strictProps);
const parseIntKeys = (fields: any) =>
_.map(getObservableMapKeys(fields), _.ary(_.toNumber, 1));
const hasIntKeys = (fields: any): boolean =>
_.every(parseIntKeys(fields), _.isInteger);
const maxKey = (fields: any): number => {
const max = _.max(parseIntKeys(fields));
return _.isUndefined(max) ? 0 : max + 1;
};
const uniqueId = (field: any): string =>
_.uniqueId([_.replace(field.path, new RegExp("\\.", "g"), "-"), "--"].join(""));
const isEvent = (obj: any): boolean => {
if (_.isNil(obj) || typeof Event === "undefined") return false;
return obj instanceof Event || !_.isNil(obj.target);
};
const hasFiles = ($: any): boolean =>
$.target.files && $.target.files.length !== 0;
const isBool = ($: any, val: any): boolean =>
_.isBoolean(val) && _.isBoolean($.target.checked);
const $try = (...args: any) => {
let found: any | null | undefined = undefined;
args.map(( val: any ) =>
found === undefined && !_.isUndefined(val) && (found = val));
return found;
};
export {
props,
checkObserve,
checkPropOccurrence,
hasProps,
allowedProps,
throwError,
isArrayOfStrings,
isEmptyArray,
isArrayOfObjects,
pathToStruct,
isArrayFromStruct,
hasUnifiedProps,
hasSeparatedProps,
allowNested,
parseIntKeys,
hasIntKeys,
maxKey,
uniqueId,
isEvent,
hasFiles,
isBool,
$try,
getObservableMapKeys,
getObservableMapValues,
};