Skip to main content

Custom React components

In this section let's discuss how to create our own React component and connect it to Form Builder and DWKit 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. But it is most essential to understand how data binding works in this case.

  • 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 to DWKit, in which it transfers its new value and name (props.name). When connecting component to DWKit, the function which is responsible for sending events to DWKit 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 DWKit 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;
}

Form Builder passes lots of parameters into this function. It is necessary because some 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 admin and DWKit app. To do that, change admin.jsx and app.jsx files. They are usually found at this path: /OptimaJet.DWKit.StarterApplication/wwwroot/js/app/.

Step 5. Connecting component to admin panel (admin.jsx)

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

import renderControls from './controls/controlrenderer';
import customControls from './controls/controlslist';

//...
render(
<DWKitAdmin
//...
externalControlList={customControls}
externalControlRender={renderControls}
//externalControlsOnly
/>,
document.getElementById('content')
);

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

Full code of modified admin.jsx can be found on GitHub.

Step 6. Connecting component to DWKit application (app.jsx)

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

import renderControls from './controls/controlrenderer';
import customControls from './controls/controlslist';

...
window.DWKitFormSettings = {
externalControlList: customControls,
externalControlRender: renderControls
};
...

Full code of modified app.jsx can be found on GitHub.

Step 7. Repacking DWKit client side

After all changes have been applied, you'll need to repack DWKit client side. Use webpack to do that. Open the following path when executing commands below (this is where webpack.config.js file is located): /OptimaJet.DWKit.StarterApplication/.

  • If your webpack is installed globally, it must be webpack 4. We do not recommend this option, as you might have several applications packed by different versions of webpack, and webpack versions are not compatible. Let's activate the following commands:

    npm install
    webpack --env.prod
  • we recommend to use webpack installed via npm, as this is the most correct version.

    npm install
    node_modules\.bin\webpack --env.prod

Now you can use your external components in Form Builder and DWKit app. See the result of our labor.

Counter in form