-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathcustom_components.html
More file actions
402 lines (380 loc) · 19.7 KB
/
custom_components.html
File metadata and controls
402 lines (380 loc) · 19.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
<article class="docs-article">
<section class="docs-section" id="custom_components">
<div>
<h1>Custom Components</h1><br>
<hr>
<p>One of the more powerful features of the forms-flow.ai platform is the ability to create your own custom
form components. The process of creating a custom component involves extending a base class of the
component that is "closest" to the implementation you desire, and then override methods or introduce new
methods that will implement your custom logic. All of the existing components within the platform also
use this same method, so you can see the multitude of examples by inspecting how the base components for
the Forms-flow renderer are implemented.
</p>
<p>In order to create the best components, it is important to understand the critical methods used to define
a new component. They are as follows.
</p>
<h2>Extending Components</h2>
<p>Every custom component will derive from a base class, whose behaviour is closest to the behaviour of the
component you wish to create. It is possible to extend any other component within the Form.io renderer
and a list of all of these components and their classes can be found <a
href="https://github.com/formio/formio.js/tree/master/src/components">@https://github.com/formio/formio.js/tree/master/src/components</a> .
</p>
<p>Because of this, the first task in building a custom component is to determine which component most
closely resembles the behaviour and data model of the component you are looking to achieve. For example,
if you wish to build a multi-button select component, it may be best to start with a radio component
since this is the component that most closely resembles the behaviour of the component you wish to
create.
</p>
<p>If you are unsure, then it is also fine to derive from the "core" components which serve as the base for
all other components within the renderer. These core components are as follows.</p>
<table>
<tr>
<thead>
<th>Class</th>
<th>Extends</th>
<th>Description</th>
</thead>
</tr>
<tr>
<td>Component</td>
<td>Element</td>
<td>Base component class</td>
</tr>
<tr>
<td>Field</td>
<td>Component</td>
<td>Component that derives from Component class that implements a "field" render template</td>
</tr>
<tr>
<td>Multivalue</td>
<td>Field</td>
<td>A component that is able to implement the "multiple" configuration allowing for multiple inputs
for this field type.</td>
</tr>
<tr>
<td>Input</td>
<td>Multivalue</td>
<td>A component type that implements an HTML value input.</td>
</tr>
</table>
<br>
<p>Each of the components can be extended by first referencing them from the Components.components object,
and then extending them as follows.</p>
<pre>
<code class="hljs">
const Input = Formio.Components.components.input;
class MyInput extends Input {
...
...
}
</code>
</pre>
<p>For the most generic components, it is fine to derive from "Component", but in most value components, you
may wish to derive from the Input component.</p>
<br>
<h3>Component Methods</h3>
<p>Once you derive from a base component, the next step is to define methods that either override base
behaviour or introduce new behaviour into the component class. It is recommended to look at the source
code of your "base" component and the classes that it extends to understand what methods you have
available to you, but the majority of all behaviour can be achieved by implementing some of the following
methods.</p>
<pre>
<code class="hljs">
const Input = Formio.Components.components.input;
class MyComponent extends Input {
/**
* This is the default schema of your custom component. It will "derive"
* from the base class "schema" and extend it with its default JSON schema
* properties. The most important are "type" which will be your component
* type when defining new components.
*
* @param extend - This allows classes deriving from this component to
* override the schema of the overridden class.
*/
static schema(...extend) {
return Input.schema({
type: 'mycomp',
label: 'My Component',
key: 'mycomp',
});
}
/**
* This is the Form Builder information on how this component should show
* up within the form builder. The "title" is the label that will be given
* to the button to drag-and-drop on the buidler. The "icon" is the font awesome
* icon that will show next to it, the "group" is the component group where
* this component will show up, and the weight is the position within that
* group where it will be shown. The "schema" field is used as the default
* JSON schema of the component when it is dragged onto the form.
*/
static get builderInfo() {
return {
title: 'My Component',
icon: 'terminal',
group: 'basic',
documentation: '/userguide/#textfield',
weight: 0,
schema: MyComponent.schema()
};
}
/**
* Called when the component has been instantiated. This is useful to define
* default instance variable values.
*
* @param component - The JSON representation of the component created.
* @param options - The global options for the renderer
* @param data - The contextual data object (model) used for this component.
*/
constructor(component, options, data) {
super(component, options, data);
}
/**
* Called immediately after the component has been instantiated to initialize
* the component.
*/
init() {
super.init();
}
/**
* For Input based components, this returns the <input> attributes that should
* be added to the input elements of the component. This is useful if you wish
* to alter the "name" and "class" attributes on the <input> elements created
* within this component.
*
* @return - A JSON object that is the attribute information to be added to the
* input element of a component.
*/
get inputInfo() {
const info = super.inputInfo;
return info;
}
/**
* This method is used to render a component as an HTML string. This method uses
* the template system (see Form Templates documentation) to take a template
* and then render this as an HTML string.
*
* @param content - Important for nested components that receive the "contents"
* of their children as an HTML string that should be injected
* in the {{ content }} token of the template.
*
* @return - An HTML string of this component.
*/
render(content) {
return super.render('<div ref="customRef">This is a custom component!</div>');
}
/**
* The attach method is called after "render" which takes the rendered contents
* from the render method (which are by this point already added to the DOM), and
* then "attach" this component logic to that html. This is where you would load
* any references within your templates (which use the "ref" attribute) to assign
* them to the "this.refs" component variable (see comment below).
*
* @param - The parent DOM HtmlElement that contains the component template.
*
* @return - A Promise that will resolve when the component has completed the
* attach phase.
*/
attach(element) {
/**
* This method will look for an element that has the 'ref="customRef"' as an
* attribute (like <div ref="customRef"></div>) and then assign that DOM
* element to the variable "this.refs". After this method is executed, the
* following will point to the DOM element of that reference.
*
* this.refs.customRef
*
* For DOM elements that have multiple in the component, you would make this
* say 'customRef: "multiple"' which would then turn "this.refs.customRef" into
* an array of DOM elements.
*/
this.loadRefs(element, {
customRef: 'single',
});
/**
* It is common to attach events to your "references" within your template.
* This can be done with the "addEventListener" method and send the template
* reference to that object.
*/
this.addEventListener(this.refs.customRef, 'click', () => {
console.log('Custom Ref has been clicked!!!');
});
return super.attach(element);
}
/**
* Called when the component has been detached. This is where you would destroy
* any other instance variables to free up memory. Any event registered with
* "addEventListener" will automatically be detached so no need to remove them
* here.
*
* @return - A Promise that resolves when this component is done detaching.
*/
detach() {
return super.detach();
}
/**
* Called when the component has been completely "destroyed" or removed form the
* renderer.
*
* @return - A Promise that resolves when this component is done being destroyed.
*/
destroy() {
return super.destroy();
}
/**
* A very useful method that will take the values being passed into this component
* and convert them into the "standard" or normalized value. For exmample, this
* could be used to convert a string into a boolean, or even a Date type.
*
* @param value - The value that is being passed into the "setValueAt" method to normalize.
* @param flags - Change propogation flags that are being used to control behavior of the
* change proogation logic.
*
* @return - The "normalized" value of this component.
*/
normalizeValue(value, flags = {}) {
return super.normalizeValue(value, flags);
}
/**
* Returns the value of the "view" data for this component.
*
* @return - The value for this whole component.
*/
getValue() {
return super.getValue();
}
/**
* Much like "getValue", but this handles retrieving the value of a single index
* when the "multiple" flag is used within the component (which allows them to add
* multiple values). This turns a single value into an array of values, and this
* method provides access to a certain index value.
*
* @param index - The index within the array of values (from the multiple flag)
* that is getting fetched.
*
* @return - The view data of this index.
*/
getValueAt(index) {
return super.getValueAt(index);
}
/**
* Sets the value of both the data and view of the component (such as setting the
* <input> value to the correct value of the data. This is most commonly used
* externally to set the value and also see that value show up in the view of the
* component. If you wish to only set the data of the component, like when you are
* responding to an HMTL input event, then updateValue should be used instead since
* it only sets the data value of the component and not the view.
*
* @param value - The value that is being set for this component's data and view.
* @param flags - Change propogation flags that are being used to control behavior of the
* change proogation logic.
*
* @return - Boolean indicating if the setValue changed the value or not.
*/
setValue(value, flags = {}) {
return super.setValue(value, flags);
}
/**
* Sets the value for only this index of the component. This is useful when you have
* the "multiple" flag set for this component and only wish to tell this component
* how the value should be set on a per-row basis.
*
* @param index - The index within the value array that is being set.
* @param value - The value at this index that is being set.
* @param flags - Change propogation flags that are being used to control behavior of the
* change proogation logic.
*
* @return - Boolean indiciating if the setValue at this index was changed.
*/
setValueAt(index, value, flags = {}) {
return super.setValueAt(index, value, flags);
}
/**
* Similar to setValue, except this does NOT update the "view" but only updates
* the data model of the component.
*
* @param value - The value of the component being set.
* @param flags - Change propogation flags that are being used to control behavior of the
* change proogation logic.
*
* @return - Boolean indicating if the updateValue changed the value or not.
*/
updateValue(value, flags = {}) {
return super.updateValue(...args);
}
}
</code>
</pre>
<br>
<h3>Component Modules
</h3>
<p>Custom components can be registered with the renderer using a module that is also able to include a large
number of components.</p>
<p>These components can then be included in the module by creating an export of these components like so.
</p>
<pre>
<code class="hljs">
import components from './components';
import templates from './templates';
export default {
components,
templates
};
</code>
</pre><br>
<h3>Using Custom Components in Applications</h3>
<p>Once these components are created as a module, they can then easily be added to the renderer using the
Formio.use method as follows.</p>
<pre>
<code class="hljs">
import { Formio } from 'formiojs';
import YourModule from './yourmodule';
Formio.use(YourModule);
</code>
</pre><br>
<p>Once this is done, any new components that have been added to the renderer / builder will show up and can
be used.</p><br>
<h3>Examples</h3>
<ol>
<h4>
<li>Google Map integration.
</li>
</h4>
<p>We have created a custom component integrating Google Maps. The Google Maps API Key can be called
from .env file and thus you need to include the key in the .env file like</p>
<pre><code class="hljs">REACT_APP_GOOGLE_MAP_URL="YOUR GOOGLE MAP API KEY HERE" </code>.</pre>
<p>The last component in the image below is the one which we created as the new custom component for
“Google Map”.</p>
<img src="./assets/images/custom1.png" alt="" class="diagram-sm"><br><br>
<p>And the resultant component will look like this:</p>
<img src="./assets/images/custom2.png" alt="" class="screenshot">
<br><br>
<p>
Follow the link for further references and code base for the implementing the Google Maps service as a
custom component:
</p>
<a
href="https://github.com/AOT-Technologies/forms-flow-ai-examples/tree/main/custom-components/google-map-component">https://github.com/AOT-Technologies/forms-flow-ai-examples/tree/main/custom-components/google-map-component</a>
<br><br>
<h4>
<li>Custom file upload Component</li>
</h4>
<p>We have created a custom component for file upload for the customization of the file upload where we
can upload the files and save the data in a different database(maybe our own separate database).
Usually we save the file data in the space provided by the Form.io, By implementing the custom file
upload, we can save the data in our own space.</p>
<p>The File Upload component UI will look like </p>
<img src="./assets/images/custom3.png" class="diagram-md"><br><br>
<p>We can download the file from our directory using the file name saved in Form.io server.
In this case we have to add the server details in the .env file to which we have to send the file
data.
</p><br>
<img src="./assets/images/custom4.png" class="screenshot"><br><br>
<p>Refer to the code base for further reference. </p>
<a
href="https://github.com/AOT-Technologies/forms-flow-ai-examples/tree/main/custom-components/custom-file-upload">https://github.com/AOT-Technologies/forms-flow-ai-examples/tree/main/custom-components/custom-file-upload</a><br><br>
<a href="https://help.form.io/developers/custom-components">https://help.form.io/developers/custom-components</a>
</ol>
</div>
</section>
</article>
<script>hljs.initHighlighting();</script>