@dmitryvim/form-builder 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -6
- package/dist/demo.js +488 -473
- package/dist/form-builder.js +1433 -1291
- package/dist/index.html +270 -142
- package/docs/13_form_builder.html +1217 -543
- package/docs/REQUIREMENTS.md +46 -14
- package/docs/integration.md +241 -206
- package/docs/schema.md +37 -31
- package/package.json +14 -2
package/docs/integration.md
CHANGED
|
@@ -7,18 +7,26 @@ Complete guide for integrating Form Builder into your application.
|
|
|
7
7
|
### Basic Iframe Embedding
|
|
8
8
|
|
|
9
9
|
```html
|
|
10
|
-
<iframe
|
|
11
|
-
|
|
10
|
+
<iframe
|
|
11
|
+
src="https://picazru.github.io/form-builder/dist/index.html"
|
|
12
|
+
width="100%"
|
|
13
|
+
height="600px"
|
|
14
|
+
frameborder="0"
|
|
15
|
+
></iframe>
|
|
12
16
|
```
|
|
13
17
|
|
|
14
18
|
### With Custom Schema
|
|
15
19
|
|
|
16
20
|
```html
|
|
17
|
-
<iframe
|
|
18
|
-
|
|
21
|
+
<iframe
|
|
22
|
+
src="https://picazru.github.io/form-builder/dist/index.html?schema=BASE64_SCHEMA"
|
|
23
|
+
width="100%"
|
|
24
|
+
height="600px"
|
|
25
|
+
></iframe>
|
|
19
26
|
```
|
|
20
27
|
|
|
21
28
|
**Encoding Schema:**
|
|
29
|
+
|
|
22
30
|
```javascript
|
|
23
31
|
const schema = { version: "0.3", title: "My Form", elements: [...] };
|
|
24
32
|
const encodedSchema = btoa(JSON.stringify(schema));
|
|
@@ -38,10 +46,10 @@ npm install @dmitryvim/form-builder
|
|
|
38
46
|
Copy the form builder HTML directly into your application:
|
|
39
47
|
|
|
40
48
|
```javascript
|
|
41
|
-
import formBuilderHTML from
|
|
49
|
+
import formBuilderHTML from "@dmitryvim/form-builder/dist/index.html";
|
|
42
50
|
|
|
43
51
|
// Insert into your page
|
|
44
|
-
document.getElementById(
|
|
52
|
+
document.getElementById("form-container").innerHTML = formBuilderHTML;
|
|
45
53
|
```
|
|
46
54
|
|
|
47
55
|
## Configuration
|
|
@@ -51,35 +59,38 @@ document.getElementById('form-container').innerHTML = formBuilderHTML;
|
|
|
51
59
|
The upload handler **must return a resource ID** that can be used for downloads and form submission:
|
|
52
60
|
|
|
53
61
|
```javascript
|
|
54
|
-
window.addEventListener(
|
|
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
|
-
|
|
62
|
+
window.addEventListener("message", (event) => {
|
|
63
|
+
if (event.origin !== "https://picazru.github.io") return;
|
|
64
|
+
|
|
65
|
+
if (event.data.type === "formBuilderReady") {
|
|
66
|
+
const iframe = document.getElementById("formBuilder");
|
|
67
|
+
iframe.contentWindow.postMessage(
|
|
68
|
+
{
|
|
69
|
+
type: "configure",
|
|
70
|
+
config: {
|
|
71
|
+
uploadHandler: async (file) => {
|
|
72
|
+
const formData = new FormData();
|
|
73
|
+
formData.append("file", file);
|
|
74
|
+
|
|
75
|
+
const response = await fetch("/api/upload", {
|
|
76
|
+
method: "POST",
|
|
77
|
+
body: formData,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const result = await response.json();
|
|
81
|
+
return result.resourceId; // Return ID, not URL
|
|
82
|
+
},
|
|
83
|
+
downloadHandler: async (resourceId) => {
|
|
84
|
+
window.open(`/api/download/${resourceId}`, "_blank");
|
|
85
|
+
},
|
|
86
|
+
thumbnailHandler: async (resourceId) => {
|
|
87
|
+
return `/api/thumbnail/${resourceId}`;
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
"https://picazru.github.io",
|
|
92
|
+
);
|
|
93
|
+
}
|
|
83
94
|
});
|
|
84
95
|
```
|
|
85
96
|
|
|
@@ -88,12 +99,15 @@ window.addEventListener('message', (event) => {
|
|
|
88
99
|
Display form data without editing capabilities:
|
|
89
100
|
|
|
90
101
|
```javascript
|
|
91
|
-
iframe.contentWindow.postMessage(
|
|
92
|
-
|
|
102
|
+
iframe.contentWindow.postMessage(
|
|
103
|
+
{
|
|
104
|
+
type: "configure",
|
|
93
105
|
options: {
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
},
|
|
106
|
+
readonly: true,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
"https://picazru.github.io",
|
|
110
|
+
);
|
|
97
111
|
```
|
|
98
112
|
|
|
99
113
|
### Tailwind CSS Integration
|
|
@@ -103,14 +117,14 @@ Always uses Tailwind CSS styling. Add to your CSS:
|
|
|
103
117
|
```css
|
|
104
118
|
/* Custom properties for consistent styling */
|
|
105
119
|
:root {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
--form-primary: 59 130 246; /* Blue */
|
|
121
|
+
--form-secondary: 100 116 139; /* Gray */
|
|
122
|
+
--form-accent: 16 185 129; /* Green */
|
|
109
123
|
}
|
|
110
124
|
|
|
111
125
|
/* Override form builder styles if needed */
|
|
112
126
|
.form-builder-container {
|
|
113
|
-
|
|
127
|
+
@apply max-w-none; /* Remove max-width constraints */
|
|
114
128
|
}
|
|
115
129
|
```
|
|
116
130
|
|
|
@@ -121,14 +135,14 @@ Always uses Tailwind CSS styling. Add to your CSS:
|
|
|
121
135
|
Listen for form submissions:
|
|
122
136
|
|
|
123
137
|
```javascript
|
|
124
|
-
window.addEventListener(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
window.addEventListener("message", (event) => {
|
|
139
|
+
if (event.data.type === "formSubmit") {
|
|
140
|
+
const { data, schema } = event.data;
|
|
141
|
+
console.log("Form submitted:", data);
|
|
142
|
+
|
|
143
|
+
// Handle the submission
|
|
144
|
+
handleFormSubmission(data);
|
|
145
|
+
}
|
|
132
146
|
});
|
|
133
147
|
```
|
|
134
148
|
|
|
@@ -137,14 +151,14 @@ window.addEventListener('message', (event) => {
|
|
|
137
151
|
Listen for draft save events:
|
|
138
152
|
|
|
139
153
|
```javascript
|
|
140
|
-
window.addEventListener(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
window.addEventListener("message", (event) => {
|
|
155
|
+
if (event.data.type === "draftSave") {
|
|
156
|
+
const { data, schema } = event.data;
|
|
157
|
+
console.log("Draft saved:", data);
|
|
158
|
+
|
|
159
|
+
// Save draft without validation
|
|
160
|
+
saveDraft(data);
|
|
161
|
+
}
|
|
148
162
|
});
|
|
149
163
|
```
|
|
150
164
|
|
|
@@ -166,28 +180,34 @@ iframe.contentWindow.postMessage({
|
|
|
166
180
|
### Prefill Form Data
|
|
167
181
|
|
|
168
182
|
```javascript
|
|
169
|
-
iframe.contentWindow.postMessage(
|
|
170
|
-
|
|
183
|
+
iframe.contentWindow.postMessage(
|
|
184
|
+
{
|
|
185
|
+
type: "setData",
|
|
171
186
|
data: {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
},
|
|
187
|
+
name: "John Doe",
|
|
188
|
+
email: "john@example.com",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
"https://picazru.github.io",
|
|
192
|
+
);
|
|
176
193
|
```
|
|
177
194
|
|
|
178
195
|
### Get Current Data
|
|
179
196
|
|
|
180
197
|
```javascript
|
|
181
198
|
// Request data
|
|
182
|
-
iframe.contentWindow.postMessage(
|
|
183
|
-
|
|
184
|
-
|
|
199
|
+
iframe.contentWindow.postMessage(
|
|
200
|
+
{
|
|
201
|
+
type: "getData",
|
|
202
|
+
},
|
|
203
|
+
"https://picazru.github.io",
|
|
204
|
+
);
|
|
185
205
|
|
|
186
206
|
// Listen for response
|
|
187
|
-
window.addEventListener(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
207
|
+
window.addEventListener("message", (event) => {
|
|
208
|
+
if (event.data.type === "currentData") {
|
|
209
|
+
console.log("Current form data:", event.data.data);
|
|
210
|
+
}
|
|
191
211
|
});
|
|
192
212
|
```
|
|
193
213
|
|
|
@@ -195,15 +215,18 @@ window.addEventListener('message', (event) => {
|
|
|
195
215
|
|
|
196
216
|
```javascript
|
|
197
217
|
// Request validation
|
|
198
|
-
iframe.contentWindow.postMessage(
|
|
199
|
-
|
|
200
|
-
|
|
218
|
+
iframe.contentWindow.postMessage(
|
|
219
|
+
{
|
|
220
|
+
type: "validate",
|
|
221
|
+
},
|
|
222
|
+
"https://picazru.github.io",
|
|
223
|
+
);
|
|
201
224
|
|
|
202
225
|
// Listen for result
|
|
203
|
-
window.addEventListener(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
226
|
+
window.addEventListener("message", (event) => {
|
|
227
|
+
if (event.data.type === "validationResult") {
|
|
228
|
+
console.log("Form is valid:", event.data.isValid);
|
|
229
|
+
}
|
|
207
230
|
});
|
|
208
231
|
```
|
|
209
232
|
|
|
@@ -215,58 +238,61 @@ Your backend must handle file uploads and return resource IDs:
|
|
|
215
238
|
|
|
216
239
|
```javascript
|
|
217
240
|
// Express.js example
|
|
218
|
-
app.post(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
241
|
+
app.post("/api/upload", upload.single("file"), (req, res) => {
|
|
242
|
+
const file = req.file;
|
|
243
|
+
|
|
244
|
+
// Store file (S3, local storage, etc.)
|
|
245
|
+
const resourceId = generateResourceId();
|
|
246
|
+
storeFile(resourceId, file);
|
|
247
|
+
|
|
248
|
+
res.json({
|
|
249
|
+
resourceId: resourceId,
|
|
250
|
+
filename: file.originalname,
|
|
251
|
+
size: file.size,
|
|
252
|
+
mimetype: file.mimetype,
|
|
253
|
+
});
|
|
231
254
|
});
|
|
232
255
|
```
|
|
233
256
|
|
|
234
257
|
### File Download Endpoint
|
|
235
258
|
|
|
236
259
|
```javascript
|
|
237
|
-
app.get(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
260
|
+
app.get("/api/download/:resourceId", (req, res) => {
|
|
261
|
+
const { resourceId } = req.params;
|
|
262
|
+
|
|
263
|
+
// Retrieve file info
|
|
264
|
+
const fileInfo = getFileInfo(resourceId);
|
|
265
|
+
if (!fileInfo) {
|
|
266
|
+
return res.status(404).json({ error: "File not found" });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Stream file to client
|
|
270
|
+
const fileStream = getFileStream(resourceId);
|
|
271
|
+
res.setHeader(
|
|
272
|
+
"Content-Disposition",
|
|
273
|
+
`attachment; filename="${fileInfo.filename}"`,
|
|
274
|
+
);
|
|
275
|
+
res.setHeader("Content-Type", fileInfo.mimetype);
|
|
276
|
+
fileStream.pipe(res);
|
|
251
277
|
});
|
|
252
278
|
```
|
|
253
279
|
|
|
254
280
|
### Form Submission Handler
|
|
255
281
|
|
|
256
282
|
```javascript
|
|
257
|
-
app.post(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
283
|
+
app.post("/api/forms/submit", (req, res) => {
|
|
284
|
+
const { schema, data } = req.body;
|
|
285
|
+
|
|
286
|
+
// Process form data
|
|
287
|
+
// File fields will contain resource IDs
|
|
288
|
+
|
|
289
|
+
console.log("Received form:", data);
|
|
290
|
+
// Example: { name: "John", avatar: "res_abc123", documents: ["res_def456", "res_ghi789"] }
|
|
291
|
+
|
|
292
|
+
// Validate and save
|
|
293
|
+
const result = processFormSubmission(schema, data);
|
|
294
|
+
|
|
295
|
+
res.json({ success: true, id: result.id });
|
|
270
296
|
});
|
|
271
297
|
```
|
|
272
298
|
|
|
@@ -275,39 +301,42 @@ app.post('/api/forms/submit', (req, res) => {
|
|
|
275
301
|
### React Integration
|
|
276
302
|
|
|
277
303
|
```jsx
|
|
278
|
-
import { useEffect, useRef } from
|
|
304
|
+
import { useEffect, useRef } from "react";
|
|
279
305
|
|
|
280
306
|
function FormBuilder({ schema, onSubmit }) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return (
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
307
|
+
const iframeRef = useRef(null);
|
|
308
|
+
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
const handleMessage = (event) => {
|
|
311
|
+
if (event.data.type === "formBuilderReady") {
|
|
312
|
+
// Configure form builder
|
|
313
|
+
iframeRef.current.contentWindow.postMessage(
|
|
314
|
+
{
|
|
315
|
+
type: "setSchema",
|
|
316
|
+
schema: schema,
|
|
317
|
+
},
|
|
318
|
+
"https://picazru.github.io",
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (event.data.type === "formSubmit") {
|
|
323
|
+
onSubmit(event.data.data);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
window.addEventListener("message", handleMessage);
|
|
328
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
329
|
+
}, [schema, onSubmit]);
|
|
330
|
+
|
|
331
|
+
return (
|
|
332
|
+
<iframe
|
|
333
|
+
ref={iframeRef}
|
|
334
|
+
src="https://picazru.github.io/form-builder/dist/index.html"
|
|
335
|
+
width="100%"
|
|
336
|
+
height="600px"
|
|
337
|
+
frameBorder="0"
|
|
338
|
+
/>
|
|
339
|
+
);
|
|
311
340
|
}
|
|
312
341
|
```
|
|
313
342
|
|
|
@@ -315,42 +344,45 @@ function FormBuilder({ schema, onSubmit }) {
|
|
|
315
344
|
|
|
316
345
|
```vue
|
|
317
346
|
<template>
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
347
|
+
<iframe
|
|
348
|
+
ref="formBuilder"
|
|
349
|
+
src="https://picazru.github.io/form-builder/dist/index.html"
|
|
350
|
+
width="100%"
|
|
351
|
+
height="600px"
|
|
352
|
+
frameborder="0"
|
|
353
|
+
@load="onIframeLoad"
|
|
354
|
+
/>
|
|
326
355
|
</template>
|
|
327
356
|
|
|
328
357
|
<script>
|
|
329
358
|
export default {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
359
|
+
props: ["schema"],
|
|
360
|
+
|
|
361
|
+
mounted() {
|
|
362
|
+
window.addEventListener("message", this.handleMessage);
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
beforeUnmount() {
|
|
366
|
+
window.removeEventListener("message", this.handleMessage);
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
methods: {
|
|
370
|
+
handleMessage(event) {
|
|
371
|
+
if (event.data.type === "formBuilderReady") {
|
|
372
|
+
this.$refs.formBuilder.contentWindow.postMessage(
|
|
373
|
+
{
|
|
374
|
+
type: "setSchema",
|
|
375
|
+
schema: this.schema,
|
|
376
|
+
},
|
|
377
|
+
"https://picazru.github.io",
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (event.data.type === "formSubmit") {
|
|
382
|
+
this.$emit("submit", event.data.data);
|
|
383
|
+
}
|
|
334
384
|
},
|
|
335
|
-
|
|
336
|
-
beforeUnmount() {
|
|
337
|
-
window.removeEventListener('message', this.handleMessage);
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
methods: {
|
|
341
|
-
handleMessage(event) {
|
|
342
|
-
if (event.data.type === 'formBuilderReady') {
|
|
343
|
-
this.$refs.formBuilder.contentWindow.postMessage({
|
|
344
|
-
type: 'setSchema',
|
|
345
|
-
schema: this.schema
|
|
346
|
-
}, 'https://picazru.github.io');
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (event.data.type === 'formSubmit') {
|
|
350
|
-
this.$emit('submit', event.data.data);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
385
|
+
},
|
|
354
386
|
};
|
|
355
387
|
</script>
|
|
356
388
|
```
|
|
@@ -362,20 +394,20 @@ export default {
|
|
|
362
394
|
Always validate message origins:
|
|
363
395
|
|
|
364
396
|
```javascript
|
|
365
|
-
window.addEventListener(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
397
|
+
window.addEventListener("message", (event) => {
|
|
398
|
+
// Only accept messages from form builder origin
|
|
399
|
+
if (event.origin !== "https://picazru.github.io") {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Process message...
|
|
372
404
|
});
|
|
373
405
|
```
|
|
374
406
|
|
|
375
407
|
### File Upload Security
|
|
376
408
|
|
|
377
409
|
- **Validate file types** on server
|
|
378
|
-
- **Limit file sizes** appropriately
|
|
410
|
+
- **Limit file sizes** appropriately
|
|
379
411
|
- **Scan for viruses** if possible
|
|
380
412
|
- **Use secure file storage** (S3 with proper permissions)
|
|
381
413
|
- **Generate secure resource IDs** (UUID, crypto.randomUUID())
|
|
@@ -385,9 +417,9 @@ window.addEventListener('message', (event) => {
|
|
|
385
417
|
Add to your CSP headers:
|
|
386
418
|
|
|
387
419
|
```
|
|
388
|
-
Content-Security-Policy:
|
|
389
|
-
default-src 'self';
|
|
390
|
-
frame-src https://picazru.github.io;
|
|
420
|
+
Content-Security-Policy:
|
|
421
|
+
default-src 'self';
|
|
422
|
+
frame-src https://picazru.github.io;
|
|
391
423
|
connect-src 'self' https://your-api.com;
|
|
392
424
|
```
|
|
393
425
|
|
|
@@ -408,12 +440,15 @@ Content-Security-Policy:
|
|
|
408
440
|
Enable console logging in iframe:
|
|
409
441
|
|
|
410
442
|
```javascript
|
|
411
|
-
iframe.contentWindow.postMessage(
|
|
412
|
-
|
|
443
|
+
iframe.contentWindow.postMessage(
|
|
444
|
+
{
|
|
445
|
+
type: "configure",
|
|
413
446
|
config: {
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
},
|
|
447
|
+
debug: true,
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
"https://picazru.github.io",
|
|
451
|
+
);
|
|
417
452
|
```
|
|
418
453
|
|
|
419
454
|
This will log all form builder events to the browser console.
|
|
@@ -431,15 +466,15 @@ This will log all form builder events to the browser console.
|
|
|
431
466
|
```javascript
|
|
432
467
|
// Old (v0.1.x)
|
|
433
468
|
uploadHandler: async (file) => {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
469
|
+
// ... upload logic
|
|
470
|
+
return result.fileUrl; // URL
|
|
471
|
+
};
|
|
437
472
|
|
|
438
|
-
// New (v0.2.x)
|
|
473
|
+
// New (v0.2.x)
|
|
439
474
|
uploadHandler: async (file) => {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
475
|
+
// ... upload logic
|
|
476
|
+
return result.resourceId; // ID
|
|
477
|
+
};
|
|
443
478
|
```
|
|
444
479
|
|
|
445
|
-
This integration guide covers all aspects of using Form Builder in production applications.
|
|
480
|
+
This integration guide covers all aspects of using Form Builder in production applications.
|