Skip to main content

Introducing Formengine - The New Formbuilder, try for FREE formengine.io.

Custom React components

In this section let's discuss how to create our own React component and connect it to FormBuilder and an app. Component code example is uploaded to GitHub. Let's examine this example step by step.

Step 1. Creating React component

First let's write any React component we need. In our example we are working with Counter component which displays form data values and contains two buttons - to increase value by 1 and to decrease value by 1. This component can be linked to data and looks the following way:

Counter

Here's its code:

export class CounterControl extends React.Component {

constructor(props) {
super(props);
}

changeValue(e, newValue) {
const props = this.props;
if (props.handleEvent) {
props.handleEvent({ syntheticEvent: e, key: props.name, eventName: "onChange", name: props.name, value: newValue });
}
}

onInc = (e) => {
this.changeValue(e, this.getValue() + 1);
}

onDec = (e) => {
this.changeValue(e, this.getValue() - 1);
}

getValue() {
const data = this.props.data;
if (!data) return 0;
return data[this.props.name] ? data[this.props.name] : 0;
}

render() {
const label = this.props.label ? <label>{this.props.label}</label> : null;

return (
<div className="field">
{label}
<Button.Group>
<Button content={this.props.incButtonText} onClick={this.onInc} />
<Button.Or text={this.getValue()} />
<Button content={this.props.decButtonText} onClick={this.onDec} />
</Button.Group>
</div>
);
}
}

You can see that it's a very simple component.

  • Component receives all form data in its data property. If there is a property which name coincides with component props.name (i.e. component name) in data, this is the data component will display. The code is as follows:

     getValue() {
    const data = this.props.data;
    return data ? data[this.props.name] : 0;
    }
  • When changing data. component must send onChange event, in which it transfers its new value and name (props.name). When connecting component to FormBuilder, the function which is responsible for sending events to an application is available. It is called props.handleEvent. Here's the code:

    changeValue(e, newValue) {
    const props = this.props;
    if (props.handleEvent) {
    props.handleEvent({ syntheticEvent: e, key: props.name, eventName: "onChange", name: props.name, value: newValue });
    }
    }

Thus, this component will work with data binding.

Step 2. Creating form for editing component properties

As we have discussed before, form for editing component properties is drawn for each component in Form Builder. We need to create a component which will draw General tab in this form and return a list of events for the Events tab. Its code is also very simple, you can check it out below or on GitHub.

export class CounterEditControl extends BaseEditControl {

constructor(props) {
super(props);
}

getGeneralDescription() {
const data = this.props.data;
const handleChange = this.props.parent.handleChange.bind(this.props.parent);

return (<Form>
<Form.Group widths="equal">
<Form.Input name="key" label="Name" value={data.key} onChange={handleChange} />
<Form.Input name="label" label="Label" value={data.label} onChange={handleChange} />
</Form.Group>
<Form.Group widths="equal">
<Form.Input name="incButtonText" label="Inc button text" value={data.incButtonText} onChange={handleChange} />
<Form.Input name="decButtonText" label="Dec button text" value={data.decButtonText} onChange={handleChange} />
</Form.Group>
</Form>);
}

getEventsList() {
return ["onChange"];
}
}

Let us draw your attention to the following two aspects:

  • getGeneralDescription function returns a form which will be displayed in General tab. You can edit the following CounterControl component properties in this form: Name, Label, Inc button text and Dec button text. Note that name property values for inputs in this form are similar with CounterControl component props. I.e.:

    • set label property with the following code:

       <Form.Input name="label" label="Label" value={data.label} onChange={handleChange} />

      and then get its value in the CounterControl component:

      const label = this.props.label;
    • set Inc button text property with the following code:

      <Form.Input name="incButtonText" label="Inc button text" value={data.incButtonText} onChange={handleChange} />

      and then get its value in the CounterControl component:

      <Button content={this.props.incButtonText} onClick={this.onInc} />

    etc.

  • getEventsList function returns a list of events that can send the component.

General tab appearance is shown on the picture below.

Counter properties

Step 3. Creating component renderer function

Renderer function is necessary for our Form Builder component to render component in form. Here it is on GitHub. This function solves to important problems:

  • It adapts a lot of Form Builder parameters to props of our manually written component.
  • Renders a component.

it is presumed that there is only one custom component renderer function in the application, and you just change it when adding new custom components.

export default function renderControls(parentComponent, control,
{
model, data, errors,
parentItem,
buildermode, children,
handleEvent, getAdditionalDataForControl,
readOnlyControls, readOnly,
disableRefs,
uploadUrl, downloadUrl, extendedData, controlsToReplace, needCheckReplace,
eventOnEdit, eventOnDelete, eventOnCopy
}) {

const props = {
key: model.key,
name: model.key,
"data-buildertype": model["data-buildertype"]
};

if (control === CounterControl) {
props.incButtonText = model.incButtonText;
props.decButtonText = model.decButtonText;
props.label = model.label;
props.handleEvent = handleEvent;
props.data = data;

return (<CounterControl {...props} />);
}
return null;
}

FormBuilder passes lots of parameters into this function. It is necessary because some of the DWKit components are rather complicated. But we will talk only about several renderer function parameters:

  • model - component settings. This is what we set in component property edit form in Form Builder.
  • control - defines which component will be drawn.
  • handleEvent - DWKit function using which component sends events.
  • data - form data object.

Step 4. Creating a Custom React components list for Form builder

Now you need to create a list of your Custom components, so that Form Builder could draw it in the components panel. Here it is on GitHub. There can be only one such list and all further custom components should be added to it.

const customControls = [
{ key: "externalControls", title: 'User Controls', isseparate: true, defaultopen: true },
{
key: "counter",
title: 'Counter',
control: CounterControl,
editControl: CounterEditControl,
defaultValues: { label: "Counter", incButtonText: "Inc", decButtonText: "Dec" }
}
];

The first element in this list describes the section to which the components described below will be added. Second element binds Custom component - control: CounterControl with form for editing properties - editControl: CounterEditControl. Here you can also set default property values - defaultValues.

Next we need to transmit info about our custom components into the FormBuilder and Form components.

Step 5. Connecting component directly to each component

We must transmit renderer function and custom components list into <DWKitFormBuilder/> React component.

import renderControls from './controls/controlrenderer';
import customControls from './controls/controlslist';
...
render(
<DWKitFormBuilder
...
externalControlList={customControls}
externalControlRender={renderControls}
//externalControlsOnly
/>,
document.getElementById('content')
);

If you want Form Builder to display only external custom controls, set externalControlRender property for <DWKitFormBuilder/> component.

You can use the same properties for registering custom controls in DWKitForm and DWKitFormViewer.

Step 6. Connecting component to an application globally (optional)

We must transmit renderer function and Custom components list into each Form <DWKitForm/> React component. However, we can't do it directly all the time, but via window global object.

import renderControls from './controls/controlrenderer';
import customControls from './controls/controlslist';
...
window.DWKitFormSettings = {
externalControlList: customControls,
externalControlRender: renderControls
};
...

Now you can use your external components in FormBuilder and your application. See the result of our labor.

Counter in form