react-jsonform-shape
Version:
A mini React library that abstracts the generation of forms from a JSON.
226 lines (196 loc) • 5.47 kB
JavaScript
import React from "react";
const { forwardRef, Fragment } = React;
const omitFields = (target, choices) => {
const newObj = {};
Object.keys(target).forEach(key => {
if (choices[key] === true) return;
newObj[key] = target[key];
});
return newObj;
};
const sortContents = arr => {
return arr.sort((a, b) => {
if (a.container.length === b.container.length) {
return a.container > b.container;
}
return a.container.length < b.container.length;
});
};
const sortStringKey = (a, b) => {
if (a.length === b.length) {
return a > b;
}
return a.length < b.length;
};
const isControlComponentDefined = (Settings = {}, type) => {
if (!Settings[type]) {
throw Error(
`Missing "${type}" component. "<AntdDynamicForm ControlComponents={{ ${type}..."`
);
}
};
const getControlByType = (Settings, item) => {
switch (item.type) {
case "slider":
isControlComponentDefined(Settings, "Slider");
return forwardRef((props, ref) => {
return <Settings.Slider {...item.propsItem} />;
});
case "button":
isControlComponentDefined(Settings, "Button");
return forwardRef((props, ref) => <Settings.Button {...{...props, ref}} />);
default:
isControlComponentDefined(Settings, "Input");
return forwardRef((props, ref) => {
return <Settings.Input {...{...props, ref}} />
});
}
};
const Render = Settings => {
if (!Settings.Form) {
throw Error(
`Missing "Form" component. "<AntdDynamicForm ControlComponents={{ Form..."`
);
}
if (!Settings.RenderItem) {
throw Error(
`Missing "renderItem". "<AntdDynamicForm renderItem={(item) => {...`
);
}
return (
<Settings.Form {...Settings.formProps}>
{Settings.items.map(field => {
const arg = {
...field,
ControlType: Settings.getControlByType(Settings, field)
};
// TODO: finalise requiring "key" instead
const key = field.propsItem.name ? field.propsItem.name : field.propsItem.id;
return <Settings.RenderItem key={key} {...arg} />;
})}
</Settings.Form>
);
};
const RenderWithTemplate = Settings => {
let templateContents = [...Settings.templateContents];
const toRender = sortContents(templateContents).reduce((acc, content) => {
const containerKey = content.container;
const contentsArr = Settings.items.filter(
item => item.container === containerKey
);
if (contentsArr.length) {
if (!content.renderItem || !content.render) {
let msg = "";
try {
msg = ` ${JSON.stringify(content)}`;
} catch (e) {}
throw Error(`Missing render or renderItem.${msg}`);
}
acc[containerKey] = () => {
return content.render({
getContents() {
return contentsArr.map(field => {
const arg = {
...field,
ControlType: Settings.getControlByType(Settings, field)
};
return content.renderItem(arg);
});
}
});
};
return acc;
}
acc[containerKey] = () => {
return content.render({
getContents() {
const r = Object.keys(acc)
.sort(sortStringKey)
.filter(
childKey =>
childKey.indexOf(containerKey) === 0 &&
childKey.length - 1 === containerKey.length
)
.reduce((nodes, currentNode) => {
if (
currentNode.indexOf(containerKey) === 0 &&
currentNode.length - 1 === containerKey.length
) {
nodes = nodes.concat([
<Fragment key={currentNode}>{acc[currentNode]()}</Fragment>
]);
}
return nodes;
}, []);
if (!r.length) {
return <></>;
}
return r;
}
});
};
return acc;
}, {});
if (toRender) {
return Object.keys(toRender)
.filter(k => k.length === 1)
.sort(sortStringKey)
.map(container => {
return <Fragment key={container}>{toRender[container]()}</Fragment>;
});
}
return <></>;
};
const AntdDynamicForm = props => {
const { ControlComponents = {}, items = [] } = props;
if (ControlComponents.Form) {
ControlComponents.formProps = omitFields(props, {
ControlComponents: true,
items: true,
renderItem: true,
render: true,
template: true
});
}
const Settings = {
...ControlComponents,
items,
RenderItem: props.renderItem,
render: props.render
? props.render
: params => {
return <Render {...Settings} />;
},
getControlByType,
templateContents:
props.template &&
props.template.contents &&
props.template.contents.length
? props.template.contents
: []
};
if (Settings.templateContents.length) {
return (
<>
{Settings.render({
...Settings,
getContents(ppp) {
return <RenderWithTemplate {...Settings} />;
}
})}
</>
);
}
return (
<>
{Settings.render({
...Settings,
getContents: ppp =>
Settings.items.map(field => {
return <Settings.RenderItem {...field} />;
})
})}
</>
);
};
export default AntdDynamicForm;