@aatulwork/customform-renderer 1.1.0 → 1.2.0
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 +358 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -224,29 +224,375 @@ interface FormField {
|
|
|
224
224
|
|
|
225
225
|
### Reference Fields
|
|
226
226
|
|
|
227
|
+
Reference fields allow you to create dropdown selects that fetch options from other forms or external APIs. There are two types: **Form Reference** and **API Reference**.
|
|
228
|
+
|
|
229
|
+
#### Form Reference (`formReference`)
|
|
230
|
+
|
|
231
|
+
Form Reference fields fetch options from entries of another form in your system. This is useful for creating relationships between forms (e.g., selecting a user, product, or category).
|
|
232
|
+
|
|
233
|
+
**Field Configuration:**
|
|
227
234
|
```typescript
|
|
228
|
-
// Form Reference
|
|
229
235
|
{
|
|
230
236
|
type: 'formReference',
|
|
231
|
-
name: '
|
|
232
|
-
label: '
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
237
|
+
name: 'userId',
|
|
238
|
+
label: 'Select User',
|
|
239
|
+
required: true,
|
|
240
|
+
referenceFormName: 'users', // Name of the form to reference
|
|
241
|
+
referenceFieldName: 'fullName', // Field name to display as label
|
|
242
|
+
allowMultiple: false, // Allow selecting multiple items
|
|
243
|
+
placeholder: 'Select a user...'
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Required Service Setup:**
|
|
248
|
+
|
|
249
|
+
You must provide a `formReference` service that fetches options from your form entries:
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
import { FormRenderer, FormServices } from '@custom-form/renderer';
|
|
253
|
+
|
|
254
|
+
const services: FormServices = {
|
|
255
|
+
formReference: {
|
|
256
|
+
fetchOptions: async (formName: string, fieldName: string) => {
|
|
257
|
+
// Fetch entries from the referenced form
|
|
258
|
+
const response = await fetch(`/api/forms/${formName}/entries`);
|
|
259
|
+
const data = await response.json();
|
|
260
|
+
|
|
261
|
+
// Transform entries into OptionItem format
|
|
262
|
+
return data.map((entry: any) => ({
|
|
263
|
+
label: entry.payload[fieldName] || entry[fieldName] || entry._id,
|
|
264
|
+
value: entry._id,
|
|
265
|
+
}));
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
<FormRenderer
|
|
271
|
+
formSchema={formSchema}
|
|
272
|
+
services={services}
|
|
273
|
+
onSubmit={handleSubmit}
|
|
274
|
+
/>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Example: Complete Form Reference Setup**
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
import { FormRenderer, FormServices, FormSchema } from '@custom-form/renderer';
|
|
281
|
+
|
|
282
|
+
// Define your form schema with formReference field
|
|
283
|
+
const formSchema: FormSchema = {
|
|
284
|
+
title: 'Task Assignment',
|
|
285
|
+
name: 'task-assignment',
|
|
286
|
+
sections: [
|
|
287
|
+
{
|
|
288
|
+
id: 'assignment',
|
|
289
|
+
title: 'Assignment Details',
|
|
290
|
+
fields: [
|
|
291
|
+
{
|
|
292
|
+
type: 'formReference',
|
|
293
|
+
name: 'assignedTo',
|
|
294
|
+
label: 'Assign To',
|
|
295
|
+
required: true,
|
|
296
|
+
referenceFormName: 'users', // References the 'users' form
|
|
297
|
+
referenceFieldName: 'fullName', // Displays the 'fullName' field
|
|
298
|
+
placeholder: 'Select a user...',
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
type: 'formReference',
|
|
302
|
+
name: 'project',
|
|
303
|
+
label: 'Project',
|
|
304
|
+
required: true,
|
|
305
|
+
referenceFormName: 'projects',
|
|
306
|
+
referenceFieldName: 'title',
|
|
307
|
+
allowMultiple: false,
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// Provide the formReference service
|
|
315
|
+
const services: FormServices = {
|
|
316
|
+
formReference: {
|
|
317
|
+
fetchOptions: async (formName: string, fieldName: string) => {
|
|
318
|
+
try {
|
|
319
|
+
// Example: Fetch from your API
|
|
320
|
+
const response = await fetch(`/api/forms/${formName}/entries?status=active`);
|
|
321
|
+
|
|
322
|
+
if (!response.ok) {
|
|
323
|
+
throw new Error(`Failed to fetch ${formName} entries`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const entries = await response.json();
|
|
327
|
+
|
|
328
|
+
// Transform to OptionItem format
|
|
329
|
+
return entries.map((entry: any) => ({
|
|
330
|
+
label: entry.payload?.[fieldName] || entry[fieldName] || `Entry ${entry._id}`,
|
|
331
|
+
value: entry._id,
|
|
332
|
+
}));
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error(`Error fetching ${formName} options:`, error);
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
function TaskForm() {
|
|
342
|
+
const handleSubmit = async (data: Record<string, any>) => {
|
|
343
|
+
console.log('Assigned to:', data.assignedTo); // Will be the _id of selected user
|
|
344
|
+
console.log('Project:', data.project); // Will be the _id of selected project
|
|
345
|
+
// Submit to your API...
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
<FormRenderer
|
|
350
|
+
formSchema={formSchema}
|
|
351
|
+
services={services}
|
|
352
|
+
onSubmit={handleSubmit}
|
|
353
|
+
/>
|
|
354
|
+
);
|
|
236
355
|
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
#### API Reference (`apiReference`)
|
|
237
359
|
|
|
238
|
-
|
|
360
|
+
API Reference fields fetch options from any external API endpoint. This is useful for integrating with third-party APIs or your own REST endpoints.
|
|
361
|
+
|
|
362
|
+
**Field Configuration:**
|
|
363
|
+
```typescript
|
|
239
364
|
{
|
|
240
365
|
type: 'apiReference',
|
|
241
366
|
name: 'role',
|
|
242
|
-
label: 'Role',
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
367
|
+
label: 'Select Role',
|
|
368
|
+
required: true,
|
|
369
|
+
apiEndpoint: '/api/roles', // API endpoint to fetch from
|
|
370
|
+
apiLabelField: 'name', // Field name to display as label
|
|
371
|
+
apiValueField: '_id', // Field name to use as value (default: '_id')
|
|
372
|
+
allowMultiple: false, // Allow selecting multiple items
|
|
373
|
+
placeholder: 'Select a role...'
|
|
247
374
|
}
|
|
248
375
|
```
|
|
249
376
|
|
|
377
|
+
**Required Service Setup:**
|
|
378
|
+
|
|
379
|
+
You must provide an `apiReference` service that fetches options from your API:
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
import { FormRenderer, FormServices } from '@custom-form/renderer';
|
|
383
|
+
|
|
384
|
+
const services: FormServices = {
|
|
385
|
+
apiReference: {
|
|
386
|
+
fetchOptions: async (endpoint: string, labelField: string, valueField = '_id') => {
|
|
387
|
+
const response = await fetch(endpoint);
|
|
388
|
+
const data = await response.json();
|
|
389
|
+
|
|
390
|
+
// Handle both array responses and object responses
|
|
391
|
+
const items = Array.isArray(data) ? data : data.items || data.data || [];
|
|
392
|
+
|
|
393
|
+
return items.map((item: any) => ({
|
|
394
|
+
label: item[labelField],
|
|
395
|
+
value: item[valueField],
|
|
396
|
+
}));
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
<FormRenderer
|
|
402
|
+
formSchema={formSchema}
|
|
403
|
+
services={services}
|
|
404
|
+
onSubmit={handleSubmit}
|
|
405
|
+
/>
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Example: Complete API Reference Setup**
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
import { FormRenderer, FormServices, FormSchema } from '@custom-form/renderer';
|
|
412
|
+
|
|
413
|
+
// Define your form schema with apiReference field
|
|
414
|
+
const formSchema: FormSchema = {
|
|
415
|
+
title: 'User Registration',
|
|
416
|
+
name: 'user-registration',
|
|
417
|
+
sections: [
|
|
418
|
+
{
|
|
419
|
+
id: 'user-info',
|
|
420
|
+
title: 'User Information',
|
|
421
|
+
fields: [
|
|
422
|
+
{
|
|
423
|
+
type: 'text',
|
|
424
|
+
name: 'firstName',
|
|
425
|
+
label: 'First Name',
|
|
426
|
+
required: true,
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
type: 'apiReference',
|
|
430
|
+
name: 'country',
|
|
431
|
+
label: 'Country',
|
|
432
|
+
required: true,
|
|
433
|
+
apiEndpoint: '/api/countries',
|
|
434
|
+
apiLabelField: 'name',
|
|
435
|
+
apiValueField: 'code',
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
type: 'apiReference',
|
|
439
|
+
name: 'role',
|
|
440
|
+
label: 'Role',
|
|
441
|
+
required: true,
|
|
442
|
+
apiEndpoint: '/api/roles',
|
|
443
|
+
apiLabelField: 'name',
|
|
444
|
+
apiValueField: '_id', // Default, can be omitted
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
},
|
|
448
|
+
],
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// Provide the apiReference service
|
|
452
|
+
const services: FormServices = {
|
|
453
|
+
apiReference: {
|
|
454
|
+
fetchOptions: async (endpoint: string, labelField: string, valueField = '_id') => {
|
|
455
|
+
try {
|
|
456
|
+
const response = await fetch(endpoint, {
|
|
457
|
+
headers: {
|
|
458
|
+
'Authorization': `Bearer ${yourAuthToken}`, // Add auth if needed
|
|
459
|
+
'Content-Type': 'application/json',
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
if (!response.ok) {
|
|
464
|
+
throw new Error(`Failed to fetch from ${endpoint}`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const data = await response.json();
|
|
468
|
+
|
|
469
|
+
// Handle different response formats
|
|
470
|
+
const items = Array.isArray(data)
|
|
471
|
+
? data
|
|
472
|
+
: data.items || data.data || data.results || [];
|
|
473
|
+
|
|
474
|
+
// Transform to OptionItem format
|
|
475
|
+
return items.map((item: any) => ({
|
|
476
|
+
label: item[labelField] || String(item[valueField]),
|
|
477
|
+
value: item[valueField],
|
|
478
|
+
}));
|
|
479
|
+
} catch (error) {
|
|
480
|
+
console.error(`Error fetching options from ${endpoint}:`, error);
|
|
481
|
+
return [];
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
function RegistrationForm() {
|
|
488
|
+
const handleSubmit = async (data: Record<string, any>) => {
|
|
489
|
+
console.log('Country code:', data.country); // Will be the country code
|
|
490
|
+
console.log('Role ID:', data.role); // Will be the role _id
|
|
491
|
+
// Submit to your API...
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<FormRenderer
|
|
496
|
+
formSchema={formSchema}
|
|
497
|
+
services={services}
|
|
498
|
+
onSubmit={handleSubmit}
|
|
499
|
+
/>
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
#### Multiple Selection Support
|
|
505
|
+
|
|
506
|
+
Both reference field types support multiple selections:
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
{
|
|
510
|
+
type: 'formReference',
|
|
511
|
+
name: 'tags',
|
|
512
|
+
label: 'Tags',
|
|
513
|
+
referenceFormName: 'tags',
|
|
514
|
+
referenceFieldName: 'name',
|
|
515
|
+
allowMultiple: true, // Enable multiple selection
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
When `allowMultiple: true`, the field will return an array of selected values.
|
|
520
|
+
|
|
521
|
+
#### Advanced: Custom API with Query Parameters
|
|
522
|
+
|
|
523
|
+
You can create dynamic endpoints by modifying the service:
|
|
524
|
+
|
|
525
|
+
```tsx
|
|
526
|
+
const services: FormServices = {
|
|
527
|
+
apiReference: {
|
|
528
|
+
fetchOptions: async (endpoint: string, labelField: string, valueField = '_id') => {
|
|
529
|
+
// Add query parameters
|
|
530
|
+
const url = new URL(endpoint, window.location.origin);
|
|
531
|
+
url.searchParams.append('status', 'active');
|
|
532
|
+
url.searchParams.append('limit', '100');
|
|
533
|
+
|
|
534
|
+
const response = await fetch(url.toString());
|
|
535
|
+
const data = await response.json();
|
|
536
|
+
|
|
537
|
+
return data.map((item: any) => ({
|
|
538
|
+
label: item[labelField],
|
|
539
|
+
value: item[valueField],
|
|
540
|
+
}));
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
#### Error Handling
|
|
547
|
+
|
|
548
|
+
Both services should handle errors gracefully:
|
|
549
|
+
|
|
550
|
+
```tsx
|
|
551
|
+
const services: FormServices = {
|
|
552
|
+
formReference: {
|
|
553
|
+
fetchOptions: async (formName: string, fieldName: string) => {
|
|
554
|
+
try {
|
|
555
|
+
const response = await fetch(`/api/forms/${formName}/entries`);
|
|
556
|
+
if (!response.ok) throw new Error('Failed to fetch');
|
|
557
|
+
const data = await response.json();
|
|
558
|
+
return data.map((entry: any) => ({
|
|
559
|
+
label: entry.payload?.[fieldName] || entry._id,
|
|
560
|
+
value: entry._id,
|
|
561
|
+
}));
|
|
562
|
+
} catch (error) {
|
|
563
|
+
console.error('Form reference error:', error);
|
|
564
|
+
return []; // Return empty array on error
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
apiReference: {
|
|
569
|
+
fetchOptions: async (endpoint: string, labelField: string, valueField = '_id') => {
|
|
570
|
+
try {
|
|
571
|
+
const response = await fetch(endpoint);
|
|
572
|
+
if (!response.ok) throw new Error('Failed to fetch');
|
|
573
|
+
const data = await response.json();
|
|
574
|
+
const items = Array.isArray(data) ? data : data.items || [];
|
|
575
|
+
return items.map((item: any) => ({
|
|
576
|
+
label: item[labelField],
|
|
577
|
+
value: item[valueField],
|
|
578
|
+
}));
|
|
579
|
+
} catch (error) {
|
|
580
|
+
console.error('API reference error:', error);
|
|
581
|
+
return []; // Return empty array on error
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
};
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
#### Notes
|
|
589
|
+
|
|
590
|
+
- **Loading State**: Reference fields automatically show a loading indicator while fetching options
|
|
591
|
+
- **Disabled State**: Fields are disabled if required configuration is missing (e.g., `referenceFormName` or `apiEndpoint`)
|
|
592
|
+
- **Error Handling**: If the service throws an error or returns empty array, the field will be disabled
|
|
593
|
+
- **Caching**: Options are fetched once when the component mounts and when dependencies change
|
|
594
|
+
- **Custom Select Component**: You can provide a custom `SelectComponent` in services to use your own select component
|
|
595
|
+
|
|
250
596
|
## Service Injection
|
|
251
597
|
|
|
252
598
|
The package uses a service injection pattern to handle external dependencies. You can provide custom services for file uploads, form references, API references, and more.
|
package/package.json
CHANGED