@dmitryvim/form-builder 0.1.4 → 0.1.6

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 CHANGED
@@ -2,35 +2,43 @@
2
2
 
3
3
  JSON Schema → Dynamic Forms → Structured Output
4
4
 
5
+ A reusable, zero-dependency form generation library that converts JSON schemas into dynamic, interactive forms with real-time validation and file upload support.
6
+
5
7
  ## Live Demo
6
8
 
7
9
  Try it now: **[https://picazru.github.io/form-builder/dist/index.html](https://picazru.github.io/form-builder/dist/index.html)**
8
10
 
9
11
  ## Quick Start
10
12
 
11
- ### Embed in Your Website
13
+ ### CDN Integration
12
14
 
13
15
  ```html
14
- <!-- Full page embed -->
16
+ <!-- Embed via iframe -->
15
17
  <iframe src="https://picazru.github.io/form-builder/dist/index.html"
16
- width="100%" height="600px"
17
- frameborder="0"
18
- style="border-radius: 8px;"></iframe>
18
+ width="100%" height="600px" frameborder="0"></iframe>
19
19
 
20
- <!-- With custom schema via URL parameter -->
21
- <iframe src="https://picazru.github.io/form-builder/dist/index.html?schema=eyJ2ZXJzaW9uIjoiMC4zIi..."
20
+ <!-- With custom schema -->
21
+ <iframe src="https://picazru.github.io/form-builder/dist/index.html?schema=BASE64_SCHEMA"
22
22
  width="100%" height="600px"></iframe>
23
23
  ```
24
24
 
25
- ### Install via npm
25
+ ### NPM Installation
26
26
 
27
27
  ```bash
28
- npm install @picazru/form-builder
28
+ npm install @dmitryvim/form-builder
29
29
  ```
30
30
 
31
- ## Integration
31
+ ## Core Features
32
+
33
+ - **Schema-driven forms**: JSON Schema v0.3 → Interactive forms
34
+ - **File uploads**: Built-in support with configurable handlers
35
+ - **Real-time validation**: Client-side validation with error display
36
+ - **Framework agnostic**: Works with any web stack
37
+ - **Zero dependencies**: Self-contained HTML/CSS/JavaScript
38
+ - **Read-only mode**: Display form data without editing
39
+ - **Draft saving**: Save incomplete forms without validation
32
40
 
33
- ### Basic Schema
41
+ ## Basic Example
34
42
 
35
43
  ```json
36
44
  {
@@ -44,131 +52,32 @@ npm install @picazru/form-builder
44
52
  "required": true
45
53
  },
46
54
  {
47
- "type": "file",
48
- "key": "avatar",
49
- "label": "Profile Picture",
55
+ "type": "files",
56
+ "key": "attachments",
57
+ "label": "Attachments",
58
+ "maxCount": 3,
50
59
  "accept": {
51
- "extensions": ["jpg", "png"]
60
+ "extensions": ["pdf", "jpg", "png"]
52
61
  }
53
62
  }
54
63
  ]
55
64
  }
56
65
  ```
57
66
 
58
- ### Configure File Handlers
59
-
60
- When embedding the form builder, configure file upload handlers on your page:
61
-
62
- ```html
63
- <!DOCTYPE html>
64
- <html>
65
- <head>
66
- <title>My App with Form Builder</title>
67
- </head>
68
- <body>
69
- <!-- Embed the form builder -->
70
- <iframe id="formBuilder"
71
- src="https://picazru.github.io/form-builder/dist/index.html"
72
- width="100%" height="600px"></iframe>
73
-
74
- <script>
75
- // Configure file upload handlers
76
- window.addEventListener('message', (event) => {
77
- if (event.origin !== 'https://picazru.github.io') return;
78
-
79
- if (event.data.type === 'formBuilderReady') {
80
- // Send configuration to the iframe
81
- const iframe = document.getElementById('formBuilder');
82
- iframe.contentWindow.postMessage({
83
- type: 'configure',
84
- config: {
85
- uploadHandler: async (file) => {
86
- // Your S3 upload logic
87
- const formData = new FormData();
88
- formData.append('file', file);
89
-
90
- const response = await fetch('/api/upload', {
91
- method: 'POST',
92
- body: formData
93
- });
94
-
95
- const result = await response.json();
96
- return result.fileUrl;
97
- }
98
- }
99
- }, 'https://picazru.github.io');
100
- }
101
- });
102
- </script>
103
- </body>
104
- </html>
105
- ```
106
-
107
- ### Direct Page Integration
108
-
109
- If you want to integrate directly on your page (not iframe):
67
+ ## Integration Options
110
68
 
111
- ```html
112
- <script>
113
- window.addEventListener('formBuilderReady', (event) => {
114
- const config = event.detail;
115
-
116
- // Upload files to S3
117
- config.setUploadHandler(async (file) => {
118
- const formData = new FormData();
119
- formData.append('file', file);
120
-
121
- const response = await fetch('/api/upload', {
122
- method: 'POST',
123
- body: formData
124
- });
125
-
126
- const result = await response.json();
127
- return result.fileUrl; // Return S3 URL or resource ID
128
- });
129
-
130
- // Download files
131
- config.setDownloadHandler(async (resourceId) => {
132
- window.open(`/api/download/${resourceId}`, '_blank');
133
- });
134
-
135
- // Generate thumbnails
136
- config.setThumbnailHandler(async (resourceId) => {
137
- return `/api/thumbnail/${resourceId}`;
138
- });
139
- });
140
- </script>
141
- ```
69
+ - **CDN**: Direct iframe embedding
70
+ - **NPM**: Import as module
71
+ - **Direct**: Self-hosted deployment
142
72
 
143
- ### Field Types
73
+ See [Integration Guide](docs/integration.md) for complete setup instructions.
144
74
 
145
- - **text** - Single line text with validation
146
- - **textarea** - Multi-line text
147
- - **number** - Numeric input with min/max
148
- - **select** - Dropdown with options
149
- - **file** - Single file upload with preview
150
- - **files** - Multiple file uploads
151
- - **group** - Nested objects with repeat support
75
+ ## Documentation
152
76
 
153
- ### Form Output
154
-
155
- ```json
156
- {
157
- "name": "John Doe",
158
- "avatar": "https://bucket.s3.amazonaws.com/files/avatar.jpg"
159
- }
160
- ```
161
-
162
- ## Development
163
-
164
- ```bash
165
- git clone https://github.com/picazru/form-builder.git
166
- cd form-builder
167
- npm install
168
- npm run dev # Start development server
169
- npm test # Run tests
170
- npm run build # Build for production
171
- ```
77
+ - [Integration Guide](docs/integration.md) - How to use and integrate
78
+ - [Schema Reference](docs/schema.md) - Complete schema documentation
79
+ - [Requirements](docs/requirements.md) - Product and business requirements
80
+ - [Development Guide](development.md) - Development and testing
172
81
 
173
82
  ## License
174
83
 
package/dist/README.md ADDED
@@ -0,0 +1,284 @@
1
+ # Form Builder Library
2
+
3
+ A reusable JavaScript library for generating dynamic forms from JSON schemas.
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Include the Library
8
+
9
+ ```html
10
+ <script src="https://unpkg.com/@dmitryvim/form-builder@latest/dist/form-builder.js"></script>
11
+ ```
12
+
13
+ Or via npm:
14
+ ```bash
15
+ npm install @dmitryvim/form-builder
16
+ ```
17
+
18
+ ### 2. Basic Usage
19
+
20
+ ```javascript
21
+ // Define your schema
22
+ const schema = {
23
+ "version": "0.3",
24
+ "title": "User Registration",
25
+ "elements": [
26
+ {
27
+ "type": "text",
28
+ "key": "name",
29
+ "label": "Full Name",
30
+ "required": true
31
+ },
32
+ {
33
+ "type": "text",
34
+ "key": "email",
35
+ "label": "Email",
36
+ "required": true
37
+ }
38
+ ]
39
+ };
40
+
41
+ // Get container element
42
+ const container = document.getElementById('form-container');
43
+
44
+ // Render the form
45
+ const form = FormBuilder.renderForm(schema, container, {
46
+ onSubmit: (data) => {
47
+ console.log('Form submitted:', data);
48
+ },
49
+ onDraft: (data) => {
50
+ console.log('Draft saved:', data);
51
+ },
52
+ onError: (errors) => {
53
+ console.log('Validation errors:', errors);
54
+ }
55
+ });
56
+ ```
57
+
58
+ ## API Reference
59
+
60
+ ### FormBuilder.renderForm(schema, container, options)
61
+
62
+ Renders a form in the specified container.
63
+
64
+ **Parameters:**
65
+ - `schema` - JSON schema object (v0.3)
66
+ - `container` - DOM element to render form in
67
+ - `options` - Configuration object (optional)
68
+
69
+ **Options:**
70
+ - `prefill` - Object with prefilled values
71
+ - `readonly` - Boolean, renders form in read-only mode (hides buttons)
72
+ - `debug` - Boolean, enables debug logging to console
73
+ - `onSubmit` - Callback for form submission `(data) => {}`
74
+ - `onDraft` - Callback for draft saving `(data) => {}`
75
+ - `onError` - Callback for validation errors `(errors) => {}`
76
+ - `buttons` - Custom button text: `{ submit: "Start Workflow", draft: "Save Progress" }`
77
+
78
+ **Important:** Upload handlers must return a **string** resourceId, not an object.
79
+
80
+ ### FormBuilder.validateSchema(schema)
81
+
82
+ Validates a JSON schema and returns array of errors.
83
+
84
+ **Returns:** `string[]` - Array of error messages (empty if valid)
85
+
86
+ ### FormBuilder.collectAndValidate(schema, formElement, skipValidation)
87
+
88
+ ⚠️ **Direct API for advanced users only**
89
+
90
+ Collects form data and validates it. Most users should use `onSubmit`/`onDraft` callbacks instead.
91
+
92
+ **Parameters:**
93
+ - `schema` - The form schema
94
+ - `formElement` - The form DOM element
95
+ - `skipValidation` - Boolean, skip validation (for drafts)
96
+
97
+ **Returns:** `{ result: object, errors: string[] }`
98
+
99
+ **Important:** This is the correct destructuring format - there is no `.valid` or `.data` property.
100
+
101
+ ### FormBuilder.setUploadHandler(uploadFn)
102
+
103
+ Sets custom file upload handler:
104
+ ```javascript
105
+ FormBuilder.setUploadHandler(async (file) => {
106
+ // Upload file to your backend
107
+ const response = await fetch('/upload', {
108
+ method: 'POST',
109
+ body: file
110
+ });
111
+ const result = await response.json();
112
+ return result.resourceId; // Return resource ID
113
+ });
114
+ ```
115
+
116
+ ### FormBuilder.setDownloadHandler(downloadFn)
117
+
118
+ Sets custom file download handler:
119
+ ```javascript
120
+ FormBuilder.setDownloadHandler(async (resourceId, fileName) => {
121
+ window.open(`/download/${resourceId}`, '_blank');
122
+ });
123
+ ```
124
+
125
+ ## Supported Field Types
126
+
127
+ - `text` - Single line text input
128
+ - `textarea` - Multi-line text input
129
+ - `number` - Numeric input with constraints
130
+ - `select` - Dropdown selection
131
+ - `file` - Single file upload
132
+ - `files` - Multiple file upload
133
+ - `group` - Nested objects with optional repeat and custom element titles
134
+
135
+ ## Troubleshooting Common Issues
136
+
137
+ ### ❌ Upload Handler Returns Object
138
+ ```javascript
139
+ // Wrong - causes "resourceId.slice is not a function"
140
+ FormBuilder.setUploadHandler(async (file) => {
141
+ const response = await uploadFile(file);
142
+ return { reference: response.id, name: file.name }; // ❌ Object
143
+ });
144
+
145
+ // Correct - return string only
146
+ FormBuilder.setUploadHandler(async (file) => {
147
+ const response = await uploadFile(file);
148
+ return response.id; // ✅ String resourceId
149
+ });
150
+ ```
151
+
152
+ ### ❌ Wrong API Usage
153
+ ```javascript
154
+ // Wrong - no .valid or .data properties
155
+ const result = FormBuilder.collectAndValidate(schema, form);
156
+ if (result.valid) { // ❌ Property doesn't exist
157
+ console.log(result.data); // ❌ Property doesn't exist
158
+ }
159
+
160
+ // Correct - use destructuring
161
+ const { result, errors } = FormBuilder.collectAndValidate(schema, form);
162
+ if (errors.length === 0) { // ✅ Check errors array
163
+ console.log(result); // ✅ Data is in result
164
+ }
165
+ ```
166
+
167
+ ### ❌ Group Elements Missing Elements Array
168
+ ```javascript
169
+ // Wrong - causes "group.elements must be array"
170
+ {
171
+ type: 'group',
172
+ key: 'address',
173
+ // Missing elements array ❌
174
+ }
175
+
176
+ // Correct - always include elements
177
+ {
178
+ type: 'group',
179
+ key: 'address',
180
+ label: 'Address Information',
181
+ element_label: 'Address #$index', // ✅ Optional custom title
182
+ elements: [ // ✅ Required array
183
+ { type: 'text', key: 'street', label: 'Street' }
184
+ ]
185
+ }
186
+ ```
187
+
188
+ ### ❌ Readonly Mode Confusion
189
+ ```javascript
190
+ // This only hides the default buttons, fields remain editable
191
+ FormBuilder.renderForm(schema, container, { readonly: true });
192
+
193
+ // For true read-only, you need to disable inputs manually or use a different approach
194
+ ```
195
+
196
+ ## Complete Example
197
+
198
+ ```javascript
199
+ const schema = {
200
+ "version": "0.3",
201
+ "title": "Video Cover Generation",
202
+ "elements": [
203
+ {
204
+ "type": "text",
205
+ "key": "concept",
206
+ "label": "Animation Concept (Optional)",
207
+ "required": false,
208
+ "maxLength": 100,
209
+ "placeholder": "e.g., fun, elegant, dynamic, cozy..."
210
+ },
211
+ {
212
+ "type": "group",
213
+ "key": "slides_input",
214
+ "label": "Video Slides",
215
+ "element_label": "Slide $index", // 🆕 Custom title for each item
216
+ "repeat": {
217
+ "min": 1,
218
+ "max": 10
219
+ },
220
+ "elements": [
221
+ {
222
+ "type": "file",
223
+ "key": "main_image",
224
+ "label": "Main Image",
225
+ "required": true,
226
+ "accept": {
227
+ "extensions": ["png", "jpg", "jpeg", "webp"],
228
+ "mime": ["image/png", "image/jpeg", "image/webp"]
229
+ },
230
+ "maxSizeMB": 25
231
+ },
232
+ {
233
+ "type": "files",
234
+ "key": "elements",
235
+ "label": "Element Images (Optional)",
236
+ "required": false,
237
+ "accept": {
238
+ "extensions": ["png", "jpg", "jpeg", "webp"],
239
+ "mime": ["image/png", "image/jpeg", "image/webp"]
240
+ },
241
+ "minCount": 0,
242
+ "maxCount": 10,
243
+ "maxSizeMB": 10
244
+ }
245
+ ]
246
+ }
247
+ ]
248
+ };
249
+
250
+ // Configure file upload
251
+ FormBuilder.setUploadHandler(async (file) => {
252
+ const formData = new FormData();
253
+ formData.append('file', file);
254
+
255
+ const response = await fetch('/api/upload', {
256
+ method: 'POST',
257
+ body: formData
258
+ });
259
+
260
+ const result = await response.json();
261
+ return result.resourceId;
262
+ });
263
+
264
+ // Render form
265
+ FormBuilder.renderForm(schema, document.getElementById('form'), {
266
+ onSubmit: async (data) => {
267
+ const response = await fetch('/api/submit', {
268
+ method: 'POST',
269
+ headers: { 'Content-Type': 'application/json' },
270
+ body: JSON.stringify(data)
271
+ });
272
+
273
+ if (response.ok) {
274
+ alert('Form submitted successfully!');
275
+ }
276
+ }
277
+ });
278
+ ```
279
+
280
+ ## Files
281
+
282
+ - `form-builder.js` - Main library file
283
+ - `sample.html` - Interactive demo page
284
+ - Complete documentation at `/docs/`
@@ -0,0 +1,108 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Form Builder - Simple Example</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ </head>
9
+ <body class="bg-gray-50 p-8">
10
+ <div class="max-w-2xl mx-auto">
11
+ <h1 class="text-3xl font-bold mb-8">Form Builder Library Example</h1>
12
+
13
+ <div class="bg-white rounded-lg shadow p-6 mb-8">
14
+ <h2 class="text-xl font-semibold mb-4">Generated Form</h2>
15
+ <div id="form-container"></div>
16
+ </div>
17
+
18
+ <div class="bg-white rounded-lg shadow p-6">
19
+ <h2 class="text-xl font-semibold mb-4">Form Output</h2>
20
+ <pre id="output" class="bg-gray-100 p-4 rounded text-sm overflow-auto">Submit the form to see output...</pre>
21
+ </div>
22
+ </div>
23
+
24
+ <script src="form-builder.js"></script>
25
+ <script>
26
+ // Example schema demonstrating element_label feature
27
+ const schema = {
28
+ "version": "0.3",
29
+ "title": "Video Cover Generation",
30
+ "elements": [
31
+ {
32
+ "type": "text",
33
+ "key": "concept",
34
+ "label": "Animation Concept (Optional)",
35
+ "required": false,
36
+ "maxLength": 100,
37
+ "placeholder": "e.g., fun, elegant, dynamic, cozy..."
38
+ },
39
+ {
40
+ "type": "group",
41
+ "key": "slides_input",
42
+ "label": "Video Slides",
43
+ "element_label": "Slide $index",
44
+ "repeat": {
45
+ "min": 1,
46
+ "max": 5
47
+ },
48
+ "elements": [
49
+ {
50
+ "type": "file",
51
+ "key": "main_image",
52
+ "label": "Main Image",
53
+ "required": true,
54
+ "accept": {
55
+ "extensions": ["png", "jpg", "jpeg", "webp"],
56
+ "mime": ["image/png", "image/jpeg", "image/webp"]
57
+ },
58
+ "maxSizeMB": 25
59
+ },
60
+ {
61
+ "type": "files",
62
+ "key": "elements",
63
+ "label": "Element Images (Optional)",
64
+ "required": false,
65
+ "accept": {
66
+ "extensions": ["png", "jpg", "jpeg", "webp"],
67
+ "mime": ["image/png", "image/jpeg", "image/webp"]
68
+ },
69
+ "minCount": 0,
70
+ "maxCount": 5,
71
+ "maxSizeMB": 10
72
+ }
73
+ ]
74
+ }
75
+ ]
76
+ };
77
+
78
+ // Configure file upload simulation
79
+ FormBuilder.setUploadHandler(async (file) => {
80
+ // Simulate upload delay
81
+ await new Promise(resolve => setTimeout(resolve, 1000));
82
+
83
+ // Generate fake resource ID
84
+ const resourceId = 'res_' + Math.random().toString(36).substr(2, 12);
85
+ console.log(`File "${file.name}" uploaded with ID: ${resourceId}`);
86
+ return resourceId;
87
+ });
88
+
89
+ // Render the form
90
+ const container = document.getElementById('form-container');
91
+ const output = document.getElementById('output');
92
+
93
+ FormBuilder.renderForm(schema, container, {
94
+ onSubmit: (data) => {
95
+ output.textContent = JSON.stringify(data, null, 2);
96
+ alert('Form submitted successfully! Check the output below.');
97
+ },
98
+ onDraft: (data) => {
99
+ output.textContent = 'DRAFT:\n' + JSON.stringify(data, null, 2);
100
+ console.log('Draft saved:', data);
101
+ },
102
+ onError: (errors) => {
103
+ alert('Validation errors: ' + errors.join(', '));
104
+ }
105
+ });
106
+ </script>
107
+ </body>
108
+ </html>