React form builder
react-form-data-structure
was created to manage complex data structures. A single configuration is used to describe both the data structure and the form. The configuration is designed have a minimal feel/code footprint, with the ability to tune or change almost anything. The idea is to be able to create an manage an infinitly scaling form/data structure that is only limited by browser resources.
Install
yarn add react-form-data-structure
Basic use case
import { Form } from 'react-form-data-structure';
import 'react-form-data-structure/react-form.css'; // to load the css
render( <Form label="I am a form!"
display={{
type: 'hash',
display: [
{type: 'text', label: 'Email Address', name: "email", required: true},
{type: 'password', label: "password", name: "password", required: true} ] }}/>,
document.getElementById( 'app' )
);
Mindset
Objects are typically displayed in containers; Containers represent data structures. Example: if you want the form to manage a list ( Array Object ) the first container would be a type: list
, and the display contents would then represent list elements.
Terms
- path Path(s) are a list that represent where in the object structure this thing exists.
-
dataPath An Array Object
path
that refers to an object that exists in Form this.state.data -
displayPath An Array Object
path
that refers to an object that exists in Form this.state.display - scalar primitive data value
Form Options
- Arguments
option | description |
---|---|
data | data structure to be displayed |
display | form/data structure configuration |
showSubmit | default: true, show or hide the submit button |
showReset | default: true, show or hide the reset button |
submitText | default "Submit", submit button text |
resetText | default "Rest", submit button reset |
formError | default: false, State for the form ( error: true|false ) |
name | default "form", used for internal key geneation ( recommended this be set to something unique ) |
label | default "", header text of the form |
errorText | default "Some fields are not filled out properly" |
Getting Data out
Form
provides a collection of callback handlers that return a semi deep copy of the the internal data.
- Event Handlers
name | When its called | Arguments |
---|---|---|
onChange | when a field changes | (data,dataPath,displayPath) |
onSubmit | when the submit button is pressed | (data,display) |
onReset | when the reset button is pressed | () |
preSubmit | when onSubmit would be called this function is called, if it returns onSubmit will be called | (data,display) |
- ** Getting the default state
The Form object allows for generation of the default state based on the following:
const state=Form.buildStateFromProps(props);
Given: props state will contain state.data and state.display
- Css Options
Form uses the following CSS Classes, and each class can be passed in as an argument.
option | default value |
---|---|
classNameFormContainer | rf-form-container |
classNameFormHeader | rf-form-header |
classNameHeaderLine | "" |
classNameSubmitRow | rf-form-submit-row |
classNameFormDiv | "" |
classNameButton | rfFormSubmit |
classNameFormError | rf-form-error |
Default Plugins
Each plugin has its on css, and each css class can be passed in as an argument.
Plugin | Type |
---|---|
text | input |
quill | Ebmbeded html editor, ( wrapper for quill ) |
datepicker | Date Time picker |
color | Color picker |
text-autocomplete | input, with autocomplete |
password | password |
textarea | textarea |
select | select |
multiselect | select multiple |
radio | radio |
checkbox | checkbox |
add | subform add |
hash | container for hashes |
list | container for lists |
watch | chooses a configuration based on an item being watched |
Input Examples
input text box ( text plugin )
- Example
This example shows how to manage a scalar value with a text input.
<Form display={
{
type: 'text',
input: "",
required: true,
label: "input test",
}
}/>
- Options
option | description |
---|---|
input | default value |
required | true|false determines if this field is required |
validate | function(currentValue), must return true if the value is valid false if it is not |
disabled | true|false determines if the field is disabled |
- CSS Options
option | default value |
---|---|
classNameInputText | rfFormDefaultsInputText |
classNameInputTextCheckFailed | rfFormDefaultsInputTextfailed |
classNameInputLabel | rfFormLabel |
classNameInputContainer | rf-form-input-container |
classNameInputTextDefault | rfFormDefaultsInputText |
Html Editor ( quill plugin )
- Example
This plugin is really a wrapper for: https://www.npmjs.com/package/react-quill .This example shows how to manage editable html input.
import 'react-quill/dist/quill.snow.css';
<Form display={
{
type: 'quill',
input: "",
required: true,
label: "input test",
}
}/>
- Options
option | description |
---|---|
input | default value |
required | true|false determines if this field is required |
validate | function(currentValue), must return true if the value is valid false if it is not |
disabled | true|false determines if the field is disabled |
toolbarOptions | default: [ [{'background': []}, 'bold', { font: []}, {color: []}, 'code', 'italic', 'link', 'strike', 'underline'], ['blockquote', { 'header': [1, 2, 3, 4, 5, 6, false] }, { 'indent': '-1' }, { 'indent': '+1' }, { 'list': 'ordered' }, { 'list': 'bullet' }, { 'align': [] }, { 'direction': 'rtl' }, 'code-block'], ['formula', 'image', 'video'] ] |
- CSS Options
option | default value |
---|---|
classNameInputText | rfFormDefaultsInputText |
classNameInputTextCheckFailed | rfFormDefaultsInputTextfailed |
classNameInputLabel | rfFormLabel |
classNameInputContainer | rf-form-input-container |
classNameInputTextDefault | rfFormDefaultsInputText |
Date Picker ( datepicker plugin )
- Example
This plugin is wrapper for: https://chmln.github.io/flatpickr/ This example shows how to manage a scalar value with a text input.
import 'flatpickr/dist/themes/material_blue.css';
<Form display={
{
type: 'datepicker',
input: "",
required: true,
label: "input test",
options: { enableTime: true, enableSeconds: true, dateFormat: "Y-m-d H:i:S" }
}
}/>
- Options
option | description |
---|---|
input | default value |
required | true|false determines if this field is required |
validate | function(currentValue), must return true if the value is valid false if it is not |
disabled | true|false determines if the field is disabled |
options | default: { enableTime: true, enableSeconds: true, dateFormat: "Y-m-d H:i:s" } see: https://chmln.github.io/flatpickr/options/ |
- CSS Options
option | default value |
---|---|
classNameInputText | rfFormDefaultsInputText |
classNameInputTextCheckFailed | rfFormDefaultsInputTextfailed |
classNameInputLabel | rfFormLabel |
classNameInputContainer | rf-form-input-container |
classNameInputTextDefault | rfFormDefaultsInputText |
input text box ( text-autocomplete plugin )
- Example
This example shows how to manage a scalar value with a text input.
<Form display={
{
type: 'text-autocomplete ',
input: "",
required: true,
label: "input test",
}
}/>
- Options
option | description |
---|---|
input | default value |
required | true|false determines if this field is required |
validate | function(currentValue), must return true if the value is valid false if it is not |
disabled | true|false determines if the field is disabled |
data | Array, each value represents an auto complete option |
- CSS Options
option | default value |
---|---|
classNameInputText | rfFormDefaultsInputText |
classNameInputTextCheckFailed | rfFormDefaultsInputTextfailed |
classNameInputLabel | rfFormLabel |
classNameInputContainer | rf-form-input-container |
classNameInputTextDefault | rfFormDefaultsInputText |
classNameInputAutoComplete | "rf-auto-complete" |
Password ( password plugin )
- Example
This example shows how to manage a scalar value with a text input.
<Form display={
{
type: 'password',
input: "",
required: true,
label: "password test",
}
}/>
- Options
option | description |
---|---|
input | default value |
required | true|false determines if this field is required |
validate | function(currentValue), must return true if the value is valid false if it is not |
disabled | true|false determines if the field is disabled |
- CSS Options
option | default value |
---|---|
classNameInputText | rfFormDefaultsInputText |
classNameInputTextCheckFailed | rfFormDefaultsInputTextfailed |
classNameInputLabel | rfFormLabel |
classNameInputContainer | rf-form-input-container |
classNameInputTextDefault | rfFormDefaultsInputText |
Textarea ( textarea plugin )
- Example
This example shows how to manage a scalar value with a textarea.
<Form display={
{
type: 'textarea',
input: "",
required: true,
label: "password test",
}
}/>
- Options
option | description |
---|---|
input | default value |
required | true|false determines if this field is required |
validate | function(currentValue), must return true if the value is valid false if it is not |
disabled | true|false determines if the field is disabled |
- CSS Options
option | default value |
---|---|
classNameInputText | rf-form-textarea |
classNameInputTextCheckFailed | rf-form-textarea-error |
classNameInputLabel | rf-form-label |
classNameInputContainer | rf-form-textarea-container |
classNameInputTextDefault | rf-form-textarea-container |
Color picker ( color plugin )
- Example
This example shows how to manage a scalar value with a color picker.
<Form display={
{
type: 'color',
input: "",
required: true,
label: "password test",
}
}/>
- Options
option | description |
---|---|
input | default value |
required | true|false determines if this field is required |
validate | function(currentValue), must return true if the value is valid false if it is not |
disabled | true|false determines if the field is disabled |
- CSS Options
option | default value |
---|---|
classNameInputText | rf-form-textarea |
classNameInputTextCheckFailed | rf-form-textarea-error |
classNameInputLabel | rf-form-label |
classNameInputContainer | rf-form-textarea-container |
classNameInputTextDefault | rf-form-textarea-container |
Select box ( select plugin )
- Example
This example shows how to manage a scalar value with a select box.
<Form display={
{
type: 'select',
input: "",
required: true,
label: "Select Test",
data: [
{ value: "one", label: "Option one" },
{ value: 'two', label: "Option two" }
]
}
}/>
- Options
option | description |
---|---|
data | Array of Objects: { value: "value", label: "display text } |
input | The default selected value "" if you don't want anything selected |
required | true|false, denotes if the field is required for the onSubmit action to be called |
label | display text |
disabled | true|false, denotes if this field is disabled |
- CSS Options
The select plugin has the following css options
option | default value |
---|---|
classNameSelectRow | rfFormSelectRow |
classNameSelectHeader | rfFormSelectHeader |
classNameSelectCell | rf-form-select-cell |
classNameSelectCellDefault | rf-form-select-cell |
classNameSelectCellError | rf-form-select-cell-error |
classNameSelect | rfFormSelect |
classNameSelectDefault | rfFormSelect |
classNameSelectError | rfFormSelectError |
classNameSelectOption | rf-form-select-option |
classNameSelectOptionDefault | rf-form-select-option |
classNameSelectOptionError | rf-form-select-option-error |
Radio Set ( radio plugin )
- Example
This example shows how to manage a scalar value with a radio set.
<Form display={
{
type: 'radio',
input: "",
required: true,
label: "Raidio Test",
data: [
{ value: "one", label: "Option one" },
{ value: 'two', label: "Option two" }
]
}
}/>
- Options
option | description |
---|---|
data | Array of Objects: { value: "value", label: "display text } |
input | The default selected value "" if you don't want anything selected |
required | true|false, denotes if the field is required for the onSubmit action to be called |
label | display text |
disabled | true|false, denotes if this field is disabled |
- CSS Options
option | default value |
---|---|
classNameInputRaidio | rfFormDefaultsInputRaidio |
classNameRadioCellIndent | rf-form-radio-indent |
classNameRadioHeader | rfFormRadioHeader |
classNameRaidoRow | rf-form-raidio-row |
classNameRaidioRowContainer | rf-form-raidio-row-container |
classNameRadioButtonCell | rf-form-radio-button-cell |
classNameRadioButtonCellDefault | rf-form-radio-button-cell |
classNameRadioButtonCellFailed | rf-form-radio-button-cell-failed |
Multiple Select ( multiselect plugin )
- Example
This example shows how to manage a scalar value with a multiple select.
<Form display={
{
type: 'multiselect',
input: [],
required: true,
label: "Milti Select Test",
data: [
{ value: "one", label: "Option one" },
{ value: 'two', label: "Option two" }
]
}
}/>
- Options
option | description |
---|---|
data | Array of Objects: { value: "value", label: "display text } |
input | Array of selected values, [] if you don't want anything selected |
required | true|false, denotes if the field is required for the onSubmit action to be called |
label | display text |
disabled | true|false, denotes if this field is disabled |
size | default: 5, sets how many records will be shown |
- CSS Options
option | default value |
---|---|
classNameSelectRow | rfFormSelectRow |
classNameSelectHeader | rfFormSelectHeader |
classNameSelectCell | rf-form-select-cell |
classNameSelectCellDefault | rf-form-select-cell |
classNameSelectCellError | rf-form-select-cell-error |
classNameSelect | rf-form-select-multi |
classNameSelectOption | rf-form-select-option |
classNameSelectOptionDefault | rf-form-select-option |
classNameSelectOptionError | rf-form-select-option-error |
CheckBox ( checbox plugin )
- Example
This example shows how to manage a scalar value with a multiple select.
<Form display={
{
type: 'checkbox',
label: "Label Example",
}
}/>
- Options
option | description |
---|---|
input | true|false, if not set defaults to false |
required | true|false, denotes if the field is required for the onSubmit action to be called |
label | display text |
disabled | true|false, denotes if this field is disabled |
- CSS Options
option | default value |
---|---|
classNameFormCheckboxLeft | rf-form-label-right |
classNameFormCheckboxRight | rf-form-element-left |
classNameFormCheckboxContainer | rf-form-input-container |
classNameFormCheckBox | rf-form-input-checkbox |
classNameFormCheckBoxDefault | rf-form-input-checkbox |
classNameFormCheckBoxError | rf-form-input-checkbox-error |
classNameFormCheckboxRightError | rf-form-label-right-error |
classNameFormCheckBoxRightDefault | rf-form-label-right |
Container Examples
Containers represent Data structures of type Array or Object. Containers make it possible to move beyond a scalar value.
list ( list plugin )
- Example
This example shows how to manage a list of text inputs.
<Form display={{
"type": "list",
"label": "Test Set 1",
"canMove": true,
"canDelete": true,
"display": [
{
"type": "text",
"label": "test One",
"required": true
},
{
"type": "text",
"label": "Test Two",
"required": true
}]
}}
/>
- Options
option | description |
---|---|
deleteText | default "Delete", text of the delete button |
moveUpText | default value "^", text of the move up button |
moveDownText | default value "v", text of the move down button |
canMove | false, enables or disables the move option |
canDelete | false, enables or disables the delete option |
headerText | default "I am a list", sets the header text for the list block |
display | Array that contains the plugins options to display |
- CSS Options
option | default value |
---|---|
classNameListContainer | rf-form-container |
classNameListRow | rf-form-container-list-row |
classNameListButton | rfFormSubmit |
classNameListCell | rf-form-container-list-cell |
classNameListButtonContainer | rf-form-container-list-cell-buttons |
classNameHidden | rfFormHidden |
classNameSmallerWidth | rf-form-container-smaller-row |
classNameListHeader | rf-form-container-list-header |
hash
- Example
This example shows how to manage a hash of text and password inputs.
Note Note Note All objects in a hash container require an additional argument name
, the name represents the key in the data structure.
<Form display={{
label: "Simple Hash example",
display: {
type: 'hash',
display: [
{ type: 'text', label: 'Email Address', name: "email", required: true },
{ type: 'password', label: "password", name: "password", required: true }
]
}
}}
/>
- Options
option | description |
---|---|
display | Array that contains the plugins options to display |
- CSS Options
option | default value |
---|---|
classNameListContainer | rf-form-container |
classNameHashRow | rf-form-container-hash-row |
classNameHashCell | rf-form-container-hash-cell |
classNameListHeader | rf-form-container-list-header |
Add Elements ( add plugin )
- Basic Example
<Form display={{
label: "Add to list",
display: {
type: 'add',
add: {
type: 'hash',
display: [
{ name: 'label', type: 'text', label: "label", required: true },
{ name: 'value', type: 'text', label: "value", required: true }
]
},
form: {
display: {
type: 'hash', display: [
{ name: 'label', type: 'text', label: "label", required: true },
{ name: 'value', type: 'text', label: "value", required: true }
]
}
},
list: {
canMove: true,
canDelete: true,
label: 'sets added',
display: [],
}
}}
/>
- Comlpex Example
The following is an example of how to use the add plugin.
<Form display={{
label: "Add to list",
display: {
type: 'add',
add: ['forms'],
form: {
display: {
type: 'hash', display: [
{ name: 'label', type: 'text', label: "label", required: true },
{ name: 'value', type: 'text', label: "value", required: true }
{
name: 'forms',
type: 'select',
label: "Choose the subform",
data: [
{ value: 'Informal', label: 'informal' },
{ value: 'Formal', label: 'formal' }
]
}
]
}
},
list: {
canMove: true,
canDelete: true,
label: 'sets added',
display: [],
},
chooser: {
Informal: {
label: 'Form One',
type: 'hash',
display: [
{ name: 'label', type: 'text', label: "label", required: true },
{ name: 'value', type: 'text', label: "value", required: true }
]
},
Formal: {
label: 'Form Two',
type: 'hash',
display: [
{ name: 'label', type: 'text', label: "Nomenclature", required: true },
{ name: 'value', type: 'text', label: "Assignment", required: true },
{ name: 'select', type: 'select', label: 'Choose One', required: true,
data: [{ label: 'one', value: 'one' }, { label: 'two', value: 'two' }] }
]
}
},
}
}}
/>
- Options
option | description |
---|---|
display | Array that contains the plugins options to display |
add | Array, path representing the current most display container element to watch |
add | Object, display object to add |
list | ignored if add.constructor==Object, all objects added exist in a "list" plugin object, this is where those options are set |
chooser | Object, of display Objects, when a user clicks the add button, the option matching the chooser will be used |
submitText | default "Add", the submit button text |
errorText | default "Some fields are not filled out properly", error show when validation fails |
- CSS Options
option | default value |
---|---|
classNameFormContainer | rf-form-container |
classNameListContainer | rf-form-container |
Watch example(s)
- Example
THe following example shows how to use watch in a hash container.
Note Note Note! Unlike other plugins, watch does not contain what it is watching.
<Form display={{
"type": "hash",
"display": [
{
"type": "select",
"label": "choose a subform",
"input": "one",
"name": "choose",
"data": [
{
"value": "one",
"label": "Form One"
},
{
"value": "two",
"label": "Form Two"
}
]
},
{
"type": "watch",
"name": "subform",
"watch": [
"choose"
],
"setDisplay": "one",
"displays": {
"one": {
"type": "text",
"label": "test"
},
"two": {
"type": "checkbox",
"label": "subform checkbox"
}
}
}
]
}}
/>
- Options
option | description |
---|---|
watch | path to the parent container value to watch |
setDisplay | which display to use from "displays" |
displays | Object of Objects, each key should map to the watch path value. |
Creating plugins
The plugin API is very flexible and powerful, it was designed to allow the encapsulation of any plugin within any plugin. This means a list can contain a list, hash, add and and any other assortment of plugins. Any plugin created must follow this mind set.
When creating a plugin the static buildData(props) method must be implemented. The buildData(props) provides the default data structure for a given dataPath.
- Example from the text plugin
static buildData( props ) {
const input = props.hasOwnProperty( 'input' ) ?
props.input
:
FormTextInput.defaultProps.input;
return input == null ? "" : input;
}
- Plugin intantiation arguments
Plugins are rendered with the following arguments
<Plugin
key={key} {...displayArguments}
root={root}
displayPath={displayPath}
dataPath={dataPath}
{...root.defaultHandlers() }
input={root.getPathValue( this.state, dataPath )}
/>
- Plugin Options
option | description |
---|---|
key | required by the react api |
root | the top level Form object instance |
displayArguments | a copy of the prop arguments for this plugin |
displayPath | a path array representing the display configuration in the root.state data structure |
dataPath | a path array representing the stored in root.state |
root.defaultHandlers() | object event handlers |
input | the default data structure for this object |
** root.defaultHandelers()
name | description | arguments |
---|---|---|
onChange | the function used to push state into the root
|
(dataPath,displayPath,newProps) |
onSubmit | allows the plugin to call the submit method of root
|
() |
onMove | called by a plugin to move itself within a list plugin | ( dataPath, displayPath, +1|-1 ) |
onDelete | called by a plugin to delete a plugin from a list plugin | ( list_id, dataPath, displayPath ) |
onValidate | used to call validation for a given plugin | ( dataPath, displayPath, props, state ) |
** Registering a plugin for events
The root
object offers the following event handler registration methods, and clean up methods.
name | description | where to initialize | arguments |
---|---|---|---|
registerSubmitCheck | registers a pre-submit check | componentDidMount,componentWillReceiveProps | (dataPath,function) |
registerWatch | registers the monitoring of a value | componentDidMount,componentWillReceiveProps | (dataPath,function) |
deleteWatch | deletes a callback for dataPath | componentWillReceiveProps,componentWillUnmount | (dataPath) |
deleteSubmitCheck | deletes a submit check | componentWillUnmount,componentWillReceiveProps | (dataPath) |
- Registering your plugin
Import the module registry.
import { FormElements } from 'react-form-data-structure/build/form-elements.jsx';
Register your module
FormElements['your-plugin']=YourPluginClass;
Built in demo
The project contains a very basic demo which can be accessed by by issuing the following command, in the project folder.
npm run demo
From there the demo/index.html can be viewed locally.