react-easel
Version:
Widget to dynamically build forms using simple-schema and uniforms
239 lines (179 loc) • 8.5 kB
Markdown
# React Easel
A react component to dynamically build reusable forms. [See the demo here](https://noahprince22.github.io/react-easel/public/)
## Acknowledgements
React Uniform Builder is built on top of [uniforms](https://github.com/vazco/uniforms) and [simple-schema](https://github.com/aldeed/node-simple-schema) with styling inspiration from
[formBuilder](git@github.com:kevinchappell/formBuilder.git)
This project is maintained by [Amdirent, Inc.](https://amdirent.com) If you'd like to build forms that hook up to arbitrary processes, and a dynamically created database table, check out [Amdirent Opslab](opslab.amdirent.com)
# Usage
## Installing
````
npm install --save react-easel
````
or
````
yarn add react-easel
````
## Quick Start
First, ensure you have loaded bootstrap4 css. This library can be used with custom components and custom styling, but defaults exist for bootstrap.
```javascript
import Easel, { defaults } from 'react-easel';
<Easel onSchemaChange={console.log} {...defaults.bootstrap4} />
```
Using `onSchemaChange`, you can extract a json defintion of a SimpleSchema, which can then be used to render the form like so:
````javascript
import AutoFields from 'uniforms-bootstrap4';
import { defaults } from 'react-easel';
import SimpleSchema from 'node-simpl-schema';
const AutoForm = defaults.bootstrap4.AutoForm;
const AutoField = defaults.bootstrap4.AutoField;
const jsonSchemaDef = <your json definition of a SimpleSchema>
<AutoForm model={{}} schema={new SimpleSchema(jsonSchemaDef)} onSubmit={console.log}>
<AutoFields autoField={AutoField} />
</AutoForm>
````
*Note:* `onSchemaChange` exists to get data out. It should not be used to drive `Easel` like a normal controlled component. `Easel` will populate initial values with `props.schema`, but it does not update on `componentWillReceiveProps`. This is because the process of creating `Easel`'s interanal state from a schema is slow, but `onSchemaChange` is called every time a component is rearanged. Using this like a normal controlled component leads to a laggy drag and drop experience.
## Defining Your Own Components/Inputs
Defaults exist for bootstrap4. You can see those in `src/bootstrap4/defaults`
The easel component expects five props:
| Argument | Description |
| -------- | ---------- |
| `AutoForm` | A uniforms AutoForm. Can import directly from your uniforms package of choice, or use a custom form |
| `components` | A list of component definitions that will appear in the sidebar to be used on the form builder |
| `AutoField` | A uniforms [AutoField](https://github.com/vazco/uniforms/blob/master/INTRODUCTION.md#example-customautofield) component capable of rendering all the specified `components`. This will receive a prop `componentType`, which you can use to decide which component to render. |
| `onSchemaChange` | A callback function that will receive a json representation of the SimpleSchema when the component changes. This SimpleSchema can be used to render future forms.
| `schema` | (optional) A json representation of an existing SimpleSchema to render.
### Component Definiton
Components are defined as follows:
| Argument | Type | Description|
| -------- | ---- | ---------- |
| `type` | `string` | A unique identifier for the component. Ex: 'Text Field' |
| `schema` | `object` | Corresponds to the [node-simple-schema](https://github.com/aldeed/node-simple-schema) definition. Should, at the very least, contain type |
| `sidebar` | `object` | The definition for what will show in the sidebar|
| `sidebar.text` | `string` | The text that shows up on the sidebar. Ex: 'Text Field'
| `sidebar.icon` | `React Component` | The icon that shows up in the sidebar. Ex: `<i className="fa fa-pencil" />` |
| `admin` | `object` | Definition of any additional configuration the field has. Defaults include `name`, `placeholder`, `label`, `optional`, and `value`|
| `admin.schema` | `SimpleSchema` | A Simple Schema definition to render configuration fields for this Component. Whatever is filled into these fields will be passed as a prop to the component |
#### Example: Using react-select instead of a regular select
First, make a uniforms driven react-select component:
```javascript
import React from 'react';
import connectField from 'uniforms/connectField';
import UnconnectedReactSelect from 'react-select';
import wrapField from 'uniforms-bootstrap4/wrapField';
const ReactSelect = connectField((props) => wrapField(props,
<UnconnectedReactSelect {...props} onChange={e => props.onChange(e ? e.value : null)} />
));
export default ReactSelect;
```
Now, override the Select field in the bootstrap defaults with your custom select field:
```javascript
import Easel, { defaults } from 'react-easel';
import ReactSelect from './ReactSelectUniforms';
const myDefaults = { ...defaults.bootstrap4 };
myDefaults.components = defaults.bootstrap4.map(component => {
if (component.type === 'Select') {
return {
...component,
formElement: ReactSelect
}
}
return component;
});
<Easel onSchemaChange={console.log} {...myDefaults} />
```
#### Example: Adding a WYSIWYG editor
For this example, we're going to use `Trumbowyg`.
First, make the Uniforms driven component:
```javascript
import React from 'react';
import $ from 'jquery';
import wrapField from 'uniforms-bootstrap4/wrapField';
import connectField from 'uniforms/connectField';
import 'react-trumbowyg/dist/trumbowyg.min.css'
global.$ = global.jQuery = window.$ = window.jQuery = $;
const Trumbowyg = require('react-trumbowyg').default;
let id = 0;
// Made to only load the first value passed, that way trumbo can do its thing.
class MyTrumboWyg extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
data: props.value
}
}
render() {
const props = this.props;
return <Trumbowyg
id={"react-trumbowyg" + (++id)}
placeholder={props.placeholder}
data={this.state.value}
onChange={(e) => {
props.onChange($(e.target).trumbowyg('html'));
}}
/>
}
}
export default connectField(props => wrapField(props,
<MyTrumboWyg {...props} />
));
```
Now, create your easel:
```javascript
import BaseField from 'uniforms-bootstrap4/AutoField';
import Easel, { defaults } from 'react-easel';
import MyTrumboWyg from './MyTrumboWyg';
// A custom auto field capable of using `componentType` to decide to render the WYSIWYG
class CustomAuto extends BaseField {
getChildContextName() {
return this.context.uniforms.name;
}
render() {
const props = this.getFieldProps(undefined, {ensureValue: false});
if (props.componentType === 'WYSIWYG') {
return <MyTrumboWyg {...props} />
}
return <defaults.bootstrap4.AutoField {...this.props} />;
}
}
const WYSIWYGComponent = {
type: 'WYSIWYG Editor',
schema: {
type: String
},
sidebar: {
icon: <i className="fa fa-file-text-o"/>,
text: 'WYSIWYG Editor'
},
}
<Easel
AutoForm={defaults.bootstrap4.AutoForm}
AutoField={CustomAuto}
components={defaults.bootstrap4.components.concat([WYSIWYGComponent])}
/>
```
#### Example: Text Area with admin configuration.
Uniforms-bootstrap4 gives us a LongTextField, and that LongTextField can take `rows` as a prop. We'd like to show that in the admin section of the field in Easel.
This example will just show the component configuration, since this already exists in `defaults.bootstrap4`
```javascript
import LongTextField from 'uniforms-bootstrap4/LongTextField';
const LongTextFieldComponent = {
type: 'Textarea',
schema: {
String,
uniforms: {
rows: 10
}
},
sidebar: {
icon: <i className="fa fa-pencil-square-o" />,
text: 'Textarea'
},
admin: {
schema: new SimpleSchema({
rows: {
type: Number
},
})
}
}
```