@tomino/dynamic-form-semantic-ui
Version:
Semantic UI form renderer based on dynamic form generation
416 lines (374 loc) • 11.5 kB
text/typescript
import {
FormComponentCatalogue,
EditorComponentCatalogue,
JSONSchema,
FormElement,
DataSet,
FormComponentProps,
Handlers
} from '@tomino/dynamic-form';
import React from 'react';
import { toJS } from 'mobx';
import { BoundProp, CommonPropNames } from '@tomino/dynamic-form';
import { ContextType, Context } from './context';
export function merge(...catalogues: EditorComponentCatalogue[]): EditorComponentCatalogue;
export function merge(...catalogues: FormComponentCatalogue[]): FormComponentCatalogue;
export function merge(...catalogues: any[]): any {
if (!catalogues.some(c => c.createComponent)) {
throw new Error('The catalogue needs to define a createComponent function!');
}
return {
createComponent: catalogues.find(c => c.createComponent).createComponent,
cssClass: catalogues.map(c => (c.cssClass ? c.cssClass + ' ' : '')).join(''),
components: Object.assign({}, ...catalogues.map(c => c.components)) as any
};
}
const composites = ['definitions', 'properties', 'items'];
export function schemaDatasetToJS(schema: any, faker = true): JSONSchema {
let result = cleanSchemaDataset(toJS(schema), faker);
if (!result) {
return null;
}
if (result.type === 'object' && !result.properties) {
result.properties = {};
}
return result;
}
export function formDatasetToJS(form: FormElement) {
return cleanForm(toJS(form));
}
function cleanForm(form: any) {
if (!form) {
return null;
}
let result: any = {};
for (let key of Object.getOwnPropertyNames(form)) {
if (key === 'parent' || key === 'isSelected') {
continue;
}
let value = form[key];
if (Array.isArray(value)) {
if (value.length > 0) {
result[key] = value.map(v => (typeof v === 'object' ? cleanForm(v) : v));
}
} else if (value != null && typeof value === 'object') {
if (Object.getOwnPropertyNames(value).length > 0) {
result[key] = cleanForm(value);
}
} else if (value) {
result[key] = value;
}
}
if (Object.keys(result).length === 0) {
return undefined;
}
return result;
}
function cleanSchemaDataset(schema: any, faker: boolean): JSONSchema {
const cleaned: any = {};
if (schema == null) {
return;
}
let keys = Object.getOwnPropertyNames(schema || {});
for (let key of keys) {
let value = schema[key];
// match
if (value == null || value === '') {
continue;
}
if (key === 'errors' || key === 'imports' || typeof schema[key] === 'function') {
// do nothing
} else if (key === 'reference') {
if (faker) {
cleaned.properties = cleanSchemaDataset(schema.reference.properties, faker);
}
// else {
// cleaned.reference = schema.$ref.split('/').pop();
// }
} else if (composites.indexOf(key) >= 0) {
if ((faker || !schema.$ref) && value && Object.getOwnPropertyNames(value).length > 0) {
cleaned[key] = cleanSchemaDataset(schema[key], faker);
}
} else if (key === '$enum') {
if (schema.$enum.length > 0) {
cleaned.enum = schema.$enum.map((e: any) => e.value);
cleaned.$enum = schema.$enum.map((e: any) => ({
text: e.text,
value: e.value,
icon: e.icon
}));
}
} else if (Array.isArray(value)) {
if (value.length > 0) {
cleaned[key] = value.map(v => (typeof v === 'object' ? cleanSchemaDataset(v, faker) : v));
}
} else if (typeof value === 'object') {
if (Object.getOwnPropertyNames(value).length > 0) {
cleaned[key] = cleanSchemaDataset(value, faker);
}
} else {
cleaned[key] = value;
}
}
if (faker) {
if (schema.properties) {
cleaned.required = Object.getOwnPropertyNames(schema.properties);
}
}
return cleaned;
}
export function simpleHandle<T>(
props: FormComponentProps,
handleName: string,
context: T,
args?: any
) {
return handle(props.handlers, handleName, props.owner, props, props.formElement, context, args);
}
export function handle<T, U>(
handlers: Handlers<DataSet<T>, U>,
handle: string | number | symbol,
owner: DataSet<T>,
props: FormComponentProps<T>,
formElement: FormElement<T>,
context: U,
args?: any
) {
if (!handlers[handle as string]) {
console.error('Handler does not exist: ' + (handle as string));
return;
}
return handle && handlers[handle as string]
? handlers[handle as string]({ owner, props, formElement, context, args })
: null;
}
// type ValueType<C> = {
// formElement: FormElement<C>;
// handlers: any;
// owner: DataSet;
// };
export function bindGetValue(props: FormComponentProps, context: ContextType) {
return function<C>(
element: FormElement<C>,
propName?: keyof C | CommonPropNames,
defaultValue?: any
) {
return getPropValue(props, element, context, propName, defaultValue);
};
}
export function getValues<C>(
props: FormComponentProps<C>,
...propNames: (keyof C | CommonPropNames)[]
): any[] {
// eslint-disable-next-line react-hooks/rules-of-hooks
const context = React.useContext(Context);
// eslint-disable-next-line react-hooks/rules-of-hooks
// return React.useMemo(
// () => propNames.map(p => getPropValue(props, props.formElement, context, p)),
// // eslint-disable-next-line react-hooks/exhaustive-deps
// [context, props.formElement]
// );
return propNames.map(p => getPropValue(props, props.formElement, context, p));
}
export function getValue<C>(
props: FormComponentProps<C>,
context: ContextType,
propName?: keyof C | CommonPropNames,
defaultValue?: any,
path: string = ''
): any {
return getPropValue(props, props.formElement, context, propName, defaultValue, path);
}
export function getPropValue<C>(
props: FormComponentProps<C>,
formElement: FormElement<C>,
context: ContextType,
propName: keyof C | CommonPropNames = undefined,
defaultValue: any = undefined,
path = ''
) {
if (!propName) {
propName = 'value' as any;
}
let prop: BoundProp = formElement.props ? (formElement.props as any)[propName] : null;
if (prop == null) {
return defaultValue;
}
if (typeof prop !== 'object' || Array.isArray(prop)) {
return prop; // props.owner.getValue(prop);
}
if (prop.value != null) {
return prop.value;
} else if (prop.handler) {
return handle(props.handlers, prop.handler, props.owner, props, props.formElement, context);
} else if (prop.source) {
return props.owner.getValue
? props.owner.getValue(prop.source + path)
: props.owner[prop.source];
}
return defaultValue === null ? '' : defaultValue;
}
export function safeGetValue<C>(
props: FormComponentProps<C>,
context: any,
propName: keyof C = null,
defaultValue: any = null
) {
let value = getValue(props, context, propName, defaultValue);
if (value == null) {
return value;
}
return value.toString();
}
export function setValue<C>(
props: FormComponentProps<C>,
context: any,
value: any,
propName: keyof C = undefined,
path = ''
) {
setPropValue(props, props.owner, context, value, propName, path);
}
export function setPropValue<C>(
props: FormComponentProps<C>,
owner: DataSet<C>,
context: any,
value: any,
propName: keyof C = undefined,
path = ''
) {
if (!propName) {
propName = 'value' as any;
}
let prop: BoundProp = props.formElement.props[propName];
if (prop == null) {
return;
}
if (typeof prop !== 'object') {
owner.setValue(propName as string, value);
}
if (prop.parse) {
handle(props.handlers, prop.parse, props.owner, props, props.formElement, context, {
current: value,
previous: getValue(props, context, propName)
});
return;
}
// if (prop.validate) {
// let error = handle(props.handlers, prop.validate, props.owner, props, context, value);
// if (error) {
// return error;
// }
// }
if (prop.source) {
owner.setValue(
prop.source,
path ? { [path.substring(1)]: value } : value,
prop.validate ? (props.handlers[prop.validate] as any) : undefined
);
} else if (prop.handler) {
handle(props.handlers, prop.handler, props.owner, props, context, value);
}
}
export function prop<C>(
formElement: FormElement<C>,
propName: keyof C = 'value' as any,
type: 'source' | 'value' | 'handler' = 'source'
) {
return formElement && formElement.props && formElement.props[propName]
? (formElement.props as any)[propName][type]
: null;
}
export function isNullOrEmpty(val: any) {
return val == null || val == '';
}
export function valueSource<C extends { value: BoundProp }>(formElement: FormElement<C>) {
return prop(formElement, 'value', 'source');
}
export function valueHandler<C extends { value: BoundProp }>(formElement: FormElement<C>) {
return prop(formElement, 'value', 'handler');
}
export function value<C extends { value: BoundProp }>(formElement: FormElement<C>) {
return prop(formElement, 'value', 'value');
}
function stripUid(obj: any) {
if (obj.uid) {
delete obj.uid;
}
for (let key of Object.keys(obj)) {
let property = obj[key];
if (property.uid) {
delete property.uid;
}
if (Array.isArray(property)) {
for (let e of property) {
stripUid(e);
}
} else if (typeof property === 'object') {
stripUid(property);
}
}
return obj;
}
export function clone(dataset: DataSet) {
return stripUid(formDatasetToJS(dataset));
}
export function parseProps(props: FormComponentProps, context: ContextType) {
const value = getValue(props, context, undefined, undefined) || '';
const controlSource = valueSource(props.formElement);
let error = '';
let disabled = false;
const label = getValue(props, context, 'label');
// we will only process the values if the schma exists
if (controlSource !== '' && props.owner.getSchema(controlSource, false) != null) {
error = controlSource ? props.owner.getError(controlSource) : null;
disabled = props.readOnly || (controlSource && props.owner.getSchema(controlSource).readOnly);
}
return { value, label, error, disabled };
}
export function processControl(props: FormComponentProps, createCallback: boolean = true) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const context = React.useContext(Context);
const { formElement, owner } = props;
let handleChange: any;
if (createCallback) {
// eslint-disable-next-line react-hooks/rules-of-hooks
handleChange = React.useCallback(
(e: React.SyntheticEvent<any>, uiProps: any) => {
const source = valueSource(formElement);
if (!source) {
return;
}
setValue(
props,
context,
uiProps.checked != null
? uiProps.checked
: uiProps.value != null
? uiProps.value
: e.currentTarget.checked !== undefined
? e.currentTarget.checked
: e.currentTarget.value
);
const changeHandler = props.formElement.props.onChange;
if (changeHandler) {
simpleHandle(props, changeHandler, context);
}
},
[context, formElement, props]
);
}
const { error, value, disabled } = parseProps(props, context);
const source = valueSource(formElement);
return {
context,
owner,
formElement,
error,
value,
disabled,
source,
handleChange,
controlProps: formElement.props
};
}