@reldens/cms 0.21.0 → 0.24.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 +413 -2
- package/admin/reldens-admin-client.css +10 -128
- package/admin/templates/fields/view/audio.html +7 -0
- package/admin/templates/fields/view/audios.html +8 -0
- package/install/index.html +4 -0
- package/lib/admin-manager/router-contents.js +18 -7
- package/lib/cache/add-cache-button-subscriber.js +1 -1
- package/lib/cache/cache-routes-handler.js +1 -1
- package/lib/dynamic-form-renderer.js +228 -0
- package/lib/dynamic-form-request-handler.js +135 -0
- package/lib/dynamic-form.js +310 -0
- package/lib/frontend.js +35 -1
- package/lib/installer.js +2 -1
- package/lib/template-engine/forms-transformer.js +187 -0
- package/lib/template-engine.js +17 -0
- package/lib/templates-list.js +2 -0
- package/migrations/default-forms.sql +22 -0
- package/package.json +5 -5
- package/templates/cms_forms/field_email.html +14 -0
- package/templates/cms_forms/field_number.html +17 -0
- package/templates/cms_forms/field_select.html +15 -0
- package/templates/cms_forms/field_text.html +16 -0
- package/templates/cms_forms/field_textarea.html +13 -0
- package/templates/cms_forms/form.html +22 -0
- package/templates/css/styles.css +4 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Reldens CMS
|
|
4
4
|
|
|
5
|
-
A powerful, flexible Content Management System built with Node.js, featuring an admin panel, multi-domain frontend support, enhanced templating with reusable content blocks, system variables, internationalization, template reloading, and automated installation.
|
|
5
|
+
A powerful, flexible Content Management System built with Node.js, featuring an admin panel, multi-domain frontend support, enhanced templating with reusable content blocks, system variables, internationalization, template reloading, dynamic forms, and automated installation.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -30,6 +30,7 @@ A powerful, flexible Content Management System built with Node.js, featuring an
|
|
|
30
30
|
- **Event-driven rendering** with hooks for customization
|
|
31
31
|
- **Custom 404 handling**
|
|
32
32
|
- **Advanced search functionality** with template data support
|
|
33
|
+
- **Dynamic forms system** with template transformers and security features
|
|
33
34
|
- **Template reloading** for development with configurable reload strategies
|
|
34
35
|
|
|
35
36
|
### - Admin Panel
|
|
@@ -49,6 +50,7 @@ A powerful, flexible Content Management System built with Node.js, featuring an
|
|
|
49
50
|
- **Translation support** for entity labels and properties
|
|
50
51
|
- **Content blocks management** via cms_blocks table
|
|
51
52
|
- **Entity access control** via entities_access table
|
|
53
|
+
- **Dynamic forms storage** via cms_forms and cms_forms_submitted tables
|
|
52
54
|
|
|
53
55
|
### - Configuration & Architecture
|
|
54
56
|
- **Environment-based configuration** (.env file)
|
|
@@ -74,6 +76,7 @@ The CMS uses a modular architecture with specialized classes:
|
|
|
74
76
|
**Request Processing:**
|
|
75
77
|
- `RequestProcessor` - HTTP request routing and path handling
|
|
76
78
|
- `SearchRequestHandler` - Dedicated search request processing
|
|
79
|
+
- `DynamicFormRequestHandler` - Form submission processing
|
|
77
80
|
|
|
78
81
|
**Content Management:**
|
|
79
82
|
- `ContentRenderer` - Content generation and template processing
|
|
@@ -85,6 +88,11 @@ The CMS uses a modular architecture with specialized classes:
|
|
|
85
88
|
**Template Processing:**
|
|
86
89
|
- `TemplateEngine` - Core template rendering with enhanced context
|
|
87
90
|
- `SystemVariablesProvider` - System variables for templates
|
|
91
|
+
- `FormsTransformer` - Dynamic forms template transformer
|
|
92
|
+
|
|
93
|
+
**Forms System:**
|
|
94
|
+
- `DynamicForm` - Form validation and data processing
|
|
95
|
+
- `DynamicFormRenderer` - Template-based form rendering
|
|
88
96
|
|
|
89
97
|
This architecture follows SOLID principles, providing better:
|
|
90
98
|
- **Testability** - Individual components can be tested in isolation
|
|
@@ -195,6 +203,349 @@ const cms = new Manager({
|
|
|
195
203
|
});
|
|
196
204
|
```
|
|
197
205
|
|
|
206
|
+
## Dynamic Forms System
|
|
207
|
+
|
|
208
|
+
### Basic Form Usage
|
|
209
|
+
Create forms in your templates using the `<cmsForm>` tag:
|
|
210
|
+
|
|
211
|
+
```html
|
|
212
|
+
<!-- Render all form fields -->
|
|
213
|
+
<cmsForm key="contactForm"/>
|
|
214
|
+
|
|
215
|
+
<!-- Render specific fields only -->
|
|
216
|
+
<cmsForm key="contactForm" fields="name,email,subject,message"/>
|
|
217
|
+
|
|
218
|
+
<!-- Custom form attributes -->
|
|
219
|
+
<cmsForm key="newsletterSignup"
|
|
220
|
+
fields="email,name"
|
|
221
|
+
submitButtonText="Subscribe Now"
|
|
222
|
+
cssClass="newsletter-form"
|
|
223
|
+
successRedirect="/thank-you"
|
|
224
|
+
errorRedirect="/contact-error"/>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Form Configuration in Database
|
|
228
|
+
Forms are configured in the `cms_forms` table via the admin panel:
|
|
229
|
+
|
|
230
|
+
```sql
|
|
231
|
+
-- Example form configuration
|
|
232
|
+
INSERT INTO cms_forms (form_key, fields_schema, enabled) VALUES
|
|
233
|
+
('contactForm', '[
|
|
234
|
+
{
|
|
235
|
+
"name": "name",
|
|
236
|
+
"type": "text",
|
|
237
|
+
"label": "Full Name",
|
|
238
|
+
"required": true,
|
|
239
|
+
"maxLength": 100,
|
|
240
|
+
"placeholder": "Enter your full name"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"name": "email",
|
|
244
|
+
"type": "email",
|
|
245
|
+
"label": "Email Address",
|
|
246
|
+
"required": true,
|
|
247
|
+
"placeholder": "your@email.com"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"name": "subject",
|
|
251
|
+
"type": "select",
|
|
252
|
+
"label": "Subject",
|
|
253
|
+
"required": true,
|
|
254
|
+
"options": [
|
|
255
|
+
{"value": "general", "label": "General Inquiry"},
|
|
256
|
+
{"value": "support", "label": "Technical Support"},
|
|
257
|
+
{"value": "sales", "label": "Sales Question"}
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"name": "message",
|
|
262
|
+
"type": "textarea",
|
|
263
|
+
"label": "Message",
|
|
264
|
+
"required": true,
|
|
265
|
+
"maxLength": 1000,
|
|
266
|
+
"placeholder": "Enter your message here..."
|
|
267
|
+
}
|
|
268
|
+
]', 1);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Supported Field Types
|
|
272
|
+
The forms system supports various field types with validation:
|
|
273
|
+
|
|
274
|
+
- **text** - Basic text input with maxLength, pattern validation
|
|
275
|
+
- **email** - Email input with built-in email validation
|
|
276
|
+
- **number** - Numeric input with min/max validation
|
|
277
|
+
- **textarea** - Multi-line text with maxLength
|
|
278
|
+
- **select** - Dropdown with options array
|
|
279
|
+
- **password** - Password input (masked)
|
|
280
|
+
- **tel** - Phone number input
|
|
281
|
+
- **url** - URL input with validation
|
|
282
|
+
- **date** - Date picker input
|
|
283
|
+
|
|
284
|
+
### Field Schema Properties
|
|
285
|
+
Each field in the `fields_schema` JSON supports:
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"name": "fieldName", // Required: Field identifier
|
|
290
|
+
"type": "text", // Required: Field type
|
|
291
|
+
"label": "Field Label", // Display label
|
|
292
|
+
"required": true, // Validation: required field
|
|
293
|
+
"placeholder": "Enter text...", // Input placeholder
|
|
294
|
+
"helpText": "Additional help", // Help text below field
|
|
295
|
+
"maxLength": 100, // String length limit
|
|
296
|
+
"minLength": 3, // Minimum string length
|
|
297
|
+
"pattern": "^[A-Za-z]+$", // Regex validation pattern
|
|
298
|
+
"min": 0, // Number minimum value
|
|
299
|
+
"max": 100, // Number maximum value
|
|
300
|
+
"step": 1, // Number step increment
|
|
301
|
+
"defaultValue": "default", // Default field value
|
|
302
|
+
"options": [ // Select/radio options
|
|
303
|
+
{"value": "val1", "label": "Option 1"},
|
|
304
|
+
{"value": "val2", "label": "Option 2"}
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Security Features
|
|
310
|
+
The form system includes comprehensive security measures:
|
|
311
|
+
|
|
312
|
+
#### 1. Honeypot Protection
|
|
313
|
+
Automatic bot detection using invisible fields:
|
|
314
|
+
```html
|
|
315
|
+
<!-- Automatically added to all forms -->
|
|
316
|
+
<div class="hidden">
|
|
317
|
+
<input type="text" name="website_url" value="" />
|
|
318
|
+
</div>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### 2. Server-Side Validation
|
|
322
|
+
- **SchemaValidator integration** - Uses `@reldens/utils` SchemaValidator
|
|
323
|
+
- **Required field validation** - Ensures all required fields are provided
|
|
324
|
+
- **Type validation** - Email, number, string validation with patterns
|
|
325
|
+
- **Length limits** - Configurable per field via schema
|
|
326
|
+
- **Custom validation** - Extensible validation rules
|
|
327
|
+
|
|
328
|
+
#### 3. Data Sanitization
|
|
329
|
+
- **XSS protection** - Handled by `@reldens/server-utils` SecurityConfigurer
|
|
330
|
+
- **Input normalization** - Type-specific data processing
|
|
331
|
+
- **Length truncation** - Based on field schema maxLength
|
|
332
|
+
|
|
333
|
+
#### 4. Rate Limiting
|
|
334
|
+
- **AppServerFactory integration** - Uses existing rate limiting from server-utils
|
|
335
|
+
- **No duplicate implementation** - Leverages proven security measures
|
|
336
|
+
|
|
337
|
+
### Template Customization
|
|
338
|
+
Forms use a domain-aware template fallback system:
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
templates/
|
|
342
|
+
├── domains/
|
|
343
|
+
│ └── example.com/
|
|
344
|
+
│ └── cms_forms/
|
|
345
|
+
│ ├── form.html # Domain-specific form wrapper
|
|
346
|
+
│ ├── field_text.html # Domain-specific text field
|
|
347
|
+
│ └── field_email.html # Domain-specific email field
|
|
348
|
+
└── cms_forms/ # Default templates
|
|
349
|
+
├── form.html # Main form wrapper
|
|
350
|
+
├── field_text.html # Text input template
|
|
351
|
+
├── field_email.html # Email input template
|
|
352
|
+
├── field_textarea.html # Textarea template
|
|
353
|
+
├── field_select.html # Select dropdown template
|
|
354
|
+
└── field_number.html # Number input template
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Custom Field Templates
|
|
358
|
+
Create custom field templates for specific types:
|
|
359
|
+
|
|
360
|
+
**templates/cms_forms/field_text.html:**
|
|
361
|
+
```html
|
|
362
|
+
<div class="form-field {{errorClass}} {{requiredClass}}">
|
|
363
|
+
<label for="{{fieldName}}" class="form-label">
|
|
364
|
+
{{fieldLabel}}{{#isRequired}} <span class="required-indicator">*</span>{{/isRequired}}
|
|
365
|
+
</label>
|
|
366
|
+
<input type="{{fieldType}}"
|
|
367
|
+
name="submittedValues[{{fieldName}}]"
|
|
368
|
+
id="{{fieldName}}"
|
|
369
|
+
value="{{fieldValue}}"
|
|
370
|
+
class="form-control {{#hasError}}is-invalid{{/hasError}}"
|
|
371
|
+
{{#isRequired}}required{{/isRequired}}
|
|
372
|
+
{{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}
|
|
373
|
+
{{#maxLength}}maxlength="{{maxLength}}"{{/maxLength}}
|
|
374
|
+
{{#pattern}}pattern="{{pattern}}"{{/pattern}} />
|
|
375
|
+
{{#helpText}}<div class="form-text">{{helpText}}</div>{{/helpText}}
|
|
376
|
+
{{#hasError}}<div class="invalid-feedback">{{fieldError}}</div>{{/hasError}}
|
|
377
|
+
</div>
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**templates/cms_forms/form.html:**
|
|
381
|
+
```html
|
|
382
|
+
<form method="POST" action="{{submitUrl}}" class="{{cssClass}}">
|
|
383
|
+
<input type="hidden" name="formKey" value="{{formKey}}" />
|
|
384
|
+
<input type="hidden" name="successRedirect" value="{{successRedirect}}" />
|
|
385
|
+
<input type="hidden" name="errorRedirect" value="{{errorRedirect}}" />
|
|
386
|
+
<div class="hidden">
|
|
387
|
+
<input type="text" name="{{honeypotFieldName}}" value="" />
|
|
388
|
+
</div>
|
|
389
|
+
{{&formFields}}
|
|
390
|
+
<div class="form-submit">
|
|
391
|
+
<button type="submit" class="btn btn-primary">{{submitButtonText}}</button>
|
|
392
|
+
</div>
|
|
393
|
+
</form>
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Forms with System Variables
|
|
397
|
+
Forms can access system variables and enhanced data in templates:
|
|
398
|
+
|
|
399
|
+
```html
|
|
400
|
+
<!-- Form with the current user context -->
|
|
401
|
+
<cmsForm key="userProfile" fields="name,email,bio"/>
|
|
402
|
+
|
|
403
|
+
<!-- In the form template, access system variables: -->
|
|
404
|
+
<form method="POST" action="{{submitUrl}}" class="{{cssClass}}">
|
|
405
|
+
<h2>Update Profile for {{currentRequest.host}}</h2>
|
|
406
|
+
<p>Current time: {{systemInfo.timestamp}}</p>
|
|
407
|
+
{{&formFields}}
|
|
408
|
+
<button type="submit">Update Profile</button>
|
|
409
|
+
</form>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Event System Integration
|
|
413
|
+
The forms system provides comprehensive event hooks:
|
|
414
|
+
|
|
415
|
+
```javascript
|
|
416
|
+
// Listen for form events
|
|
417
|
+
cms.events.on('reldens.formsTransformer.beforeRender', (eventData) => {
|
|
418
|
+
console.log('Rendering form:', eventData.formKey);
|
|
419
|
+
// Modify form attributes or fields before rendering
|
|
420
|
+
eventData.formAttributes.cssClass += ' custom-form';
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
cms.events.on('reldens.dynamicForm.beforeValidation', (eventData) => {
|
|
424
|
+
console.log('Validating form:', eventData.formKey);
|
|
425
|
+
// Add custom validation logic
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
cms.events.on('reldens.dynamicForm.afterSave', (eventData) => {
|
|
429
|
+
console.log('Form saved:', eventData.result.id);
|
|
430
|
+
// Send notifications, trigger workflows, etc.
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
cms.events.on('reldens.dynamicFormRequestHandler.beforeSave', (eventData) => {
|
|
434
|
+
// Modify prepared values before saving
|
|
435
|
+
eventData.preparedValues.submissionDate = new Date().toISOString();
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Available Form Events
|
|
440
|
+
- `reldens.formsTransformer.beforeRender` - Before form rendering
|
|
441
|
+
- `reldens.formsTransformer.afterRender` - After form rendering
|
|
442
|
+
- `reldens.dynamicForm.beforeValidation` - Before form validation
|
|
443
|
+
- `reldens.dynamicForm.afterValidation` - After form validation
|
|
444
|
+
- `reldens.dynamicForm.beforeSave` - Before saving to the database
|
|
445
|
+
- `reldens.dynamicForm.afterSave` - After successful save
|
|
446
|
+
- `reldens.dynamicFormRenderer.beforeFieldsRender` - Before rendering fields
|
|
447
|
+
- `reldens.dynamicFormRenderer.afterFieldsRender` - After rendering fields
|
|
448
|
+
- `reldens.dynamicFormRequestHandler.beforeValidation` - Before request validation
|
|
449
|
+
- `reldens.dynamicFormRequestHandler.beforeSave` - Before save process
|
|
450
|
+
- `reldens.dynamicFormRequestHandler.afterSave` - After successful save
|
|
451
|
+
|
|
452
|
+
### Database Tables
|
|
453
|
+
The forms system uses two main tables:
|
|
454
|
+
|
|
455
|
+
#### cms_forms Table
|
|
456
|
+
Store form configurations:
|
|
457
|
+
```sql
|
|
458
|
+
CREATE TABLE `cms_forms` (
|
|
459
|
+
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
460
|
+
`form_key` VARCHAR(255) NOT NULL UNIQUE,
|
|
461
|
+
`fields_schema` JSON NOT NULL,
|
|
462
|
+
`enabled` TINYINT UNSIGNED NOT NULL DEFAULT '0',
|
|
463
|
+
`created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
|
|
464
|
+
`updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
|
|
465
|
+
PRIMARY KEY (`id`)
|
|
466
|
+
);
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
#### cms_forms_submitted Table
|
|
470
|
+
Store form submissions:
|
|
471
|
+
```sql
|
|
472
|
+
CREATE TABLE `cms_forms_submitted` (
|
|
473
|
+
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
474
|
+
`form_id` INT UNSIGNED NOT NULL,
|
|
475
|
+
`submitted_values` JSON NOT NULL,
|
|
476
|
+
`created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
|
|
477
|
+
PRIMARY KEY (`id`),
|
|
478
|
+
FOREIGN KEY (`form_id`) REFERENCES `cms_forms`(`id`)
|
|
479
|
+
);
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Form Processing Flow
|
|
483
|
+
1. **Template Processing** - `FormsTransformer` finds `<cmsForm>` tags
|
|
484
|
+
2. **Form Loading** - Loads form configuration from database
|
|
485
|
+
3. **Field Filtering** - Applies field filter if specified
|
|
486
|
+
4. **Template Rendering** - Renders form using domain-aware templates
|
|
487
|
+
5. **Form Submission** - POST request to `/dynamic-form` endpoint
|
|
488
|
+
6. **Validation** - Honeypot, required fields, and schema validation
|
|
489
|
+
7. **Data Processing** - Input sanitization and normalization
|
|
490
|
+
8. **Database Storage** - Save to `cms_forms_submitted` table
|
|
491
|
+
9. **Response** - Redirect with success/error parameters
|
|
492
|
+
|
|
493
|
+
### Advanced Form Usage
|
|
494
|
+
|
|
495
|
+
#### Multi-Step Forms
|
|
496
|
+
```html
|
|
497
|
+
<!-- Step 1: Basic info -->
|
|
498
|
+
<cmsForm key="applicationForm" fields="name,email,phone"/>
|
|
499
|
+
|
|
500
|
+
<!-- Step 2: Details (separate form) -->
|
|
501
|
+
<cmsForm key="applicationDetails" fields="experience,portfolio"/>
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
#### Conditional Field Display
|
|
505
|
+
Use JavaScript to show/hide fields based on selections:
|
|
506
|
+
```html
|
|
507
|
+
<cmsForm key="surveyForm" fields="age,experience,expertise"/>
|
|
508
|
+
|
|
509
|
+
<script>
|
|
510
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
511
|
+
const ageField = document.getElementById('age');
|
|
512
|
+
const experienceField = document.getElementById('experience');
|
|
513
|
+
|
|
514
|
+
ageField.addEventListener('change', function() {
|
|
515
|
+
if(parseInt(this.value) >= 18) {
|
|
516
|
+
experienceField.parentElement.style.display = 'block';
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
experienceField.parentElement.style.display = 'none';
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
</script>
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
#### AJAX Form Submissions
|
|
526
|
+
Enable JSON responses for AJAX handling:
|
|
527
|
+
```javascript
|
|
528
|
+
const cms = new Manager({
|
|
529
|
+
enableJsonResponse: true // Enable JSON responses for forms
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
```javascript
|
|
534
|
+
// Frontend AJAX handling
|
|
535
|
+
document.querySelector('.dynamic-form').addEventListener('submit', async function(e) {
|
|
536
|
+
e.preventDefault();
|
|
537
|
+
|
|
538
|
+
const response = await fetch('/dynamic-form', {method: 'POST', body: new FormData(this)});
|
|
539
|
+
|
|
540
|
+
const result = await response.json();
|
|
541
|
+
if(result.success) {
|
|
542
|
+
alert('Form submitted successfully!');
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
alert('Error: ' + result.error);
|
|
546
|
+
});
|
|
547
|
+
```
|
|
548
|
+
|
|
198
549
|
## Search Functionality
|
|
199
550
|
|
|
200
551
|
### Basic Search
|
|
@@ -656,7 +1007,7 @@ The CMS uses a two-tier layout system:
|
|
|
656
1007
|
|
|
657
1008
|
Pages can use different layouts by setting the `layout` field in `cms_pages`:
|
|
658
1009
|
- `default` - Header, sidebar, main content, footer
|
|
659
|
-
- `full-width` - Full width without sidebars
|
|
1010
|
+
- `full-width` - Full width without sidebars
|
|
660
1011
|
- `minimal` - Basic layout with minimal styling
|
|
661
1012
|
|
|
662
1013
|
### Content Blocks
|
|
@@ -695,6 +1046,9 @@ templates/
|
|
|
695
1046
|
│ │ ├── partials/
|
|
696
1047
|
│ │ │ ├── header.html
|
|
697
1048
|
│ │ │ └── footer.html
|
|
1049
|
+
│ │ ├── cms_forms/ # Domain-specific form templates
|
|
1050
|
+
│ │ │ ├── form.html
|
|
1051
|
+
│ │ │ └── field_text.html
|
|
698
1052
|
│ │ ├── page.html # Domain-specific page wrapper
|
|
699
1053
|
│ │ └── index.html
|
|
700
1054
|
│ └── dev.example.com/
|
|
@@ -702,6 +1056,10 @@ templates/
|
|
|
702
1056
|
├── partials/
|
|
703
1057
|
│ ├── header.html (default)
|
|
704
1058
|
│ └── footer.html (default)
|
|
1059
|
+
├── cms_forms/ # Default form templates
|
|
1060
|
+
│ ├── form.html
|
|
1061
|
+
│ ├── field_text.html
|
|
1062
|
+
│ └── field_email.html
|
|
705
1063
|
├── translations/
|
|
706
1064
|
│ ├── en.json
|
|
707
1065
|
│ ├── es.json
|
|
@@ -766,6 +1124,12 @@ cms.events.on('reldens.afterContentProcess', (eventData) => {
|
|
|
766
1124
|
cms.events.on('reldens.templateReloader.templatesChanged', (eventData) => {
|
|
767
1125
|
console.log('Templates changed:', eventData.changedFiles);
|
|
768
1126
|
});
|
|
1127
|
+
|
|
1128
|
+
// Listen for form events
|
|
1129
|
+
cms.events.on('reldens.dynamicForm.afterSave', (eventData) => {
|
|
1130
|
+
// Send email notifications, trigger workflows, etc.
|
|
1131
|
+
console.log('Form submission received:', eventData.result.id);
|
|
1132
|
+
});
|
|
769
1133
|
```
|
|
770
1134
|
|
|
771
1135
|
### Custom Authentication
|
|
@@ -822,6 +1186,10 @@ cms.events.on('adminEntityExtraData', ({entitySerializedData, entity}) => {
|
|
|
822
1186
|
- `entities_meta` - Generic metadata storage
|
|
823
1187
|
- `cms_pages_meta` - Page-specific metadata
|
|
824
1188
|
|
|
1189
|
+
### Forms Tables
|
|
1190
|
+
- `cms_forms` - Form configurations with JSON schema
|
|
1191
|
+
- `cms_forms_submitted` - Form submissions with JSON data
|
|
1192
|
+
|
|
825
1193
|
### Installation Options
|
|
826
1194
|
The installer provides checkboxes for:
|
|
827
1195
|
- CMS core tables
|
|
@@ -830,6 +1198,7 @@ The installer provides checkboxes for:
|
|
|
830
1198
|
- Default homepage
|
|
831
1199
|
- Default content blocks
|
|
832
1200
|
- Entity access control rules
|
|
1201
|
+
- Dynamic forms system
|
|
833
1202
|
|
|
834
1203
|
## API Reference
|
|
835
1204
|
|
|
@@ -907,6 +1276,36 @@ The installer provides checkboxes for:
|
|
|
907
1276
|
- `Search.executeSearch(config)` - Execute search with configuration
|
|
908
1277
|
- `SearchRenderer.renderSearchResults(searchResults, config, domain, req)` - Render search results with template data
|
|
909
1278
|
|
|
1279
|
+
### Forms System Classes
|
|
1280
|
+
|
|
1281
|
+
#### DynamicForm Class
|
|
1282
|
+
- `validateFormSubmission(formKey, submittedValues, req)` - Validate form submission
|
|
1283
|
+
- `getFormConfig(formKey)` - Load form configuration from database
|
|
1284
|
+
- `validateHoneypot(submittedValues)` - Check honeypot field for bots
|
|
1285
|
+
- `validateFields(fieldsSchema, submittedValues)` - Schema-based field validation
|
|
1286
|
+
- `prepareSubmittedValues(submittedValues, fieldsSchema)` - Process and normalize values
|
|
1287
|
+
- `saveFormSubmission(formConfig, preparedValues)` - Save to database
|
|
1288
|
+
|
|
1289
|
+
#### DynamicFormRenderer Class
|
|
1290
|
+
- `renderForm(formConfig, fieldsToRender, domain, req, attributes)` - Render complete form
|
|
1291
|
+
- `renderFormFields(fieldsToRender, domain, req)` - Render field set
|
|
1292
|
+
- `renderFormField(field, domain, submittedValues, errors)` - Render individual field
|
|
1293
|
+
- `loadFormTemplate(templateName, domain)` - Load form template with domain fallback
|
|
1294
|
+
- `findFormTemplate(templateName, domain)` - Template discovery for forms
|
|
1295
|
+
|
|
1296
|
+
#### DynamicFormRequestHandler Class
|
|
1297
|
+
- `handleFormSubmission(req, res)` - Process POST form submissions
|
|
1298
|
+
- `handleBadRequest(res, message)` - Handle validation errors
|
|
1299
|
+
- `handleSuccessResponse(req, res, formKey, result)` - Handle successful submissions
|
|
1300
|
+
- `buildErrorRedirectPath(req, error, formKey)` - Build error redirect URLs
|
|
1301
|
+
- `buildSuccessRedirectPath(successRedirect, formKey)` - Build success redirect URLs
|
|
1302
|
+
|
|
1303
|
+
#### FormsTransformer Class
|
|
1304
|
+
- `transform(template, domain, req, systemVariables, enhancedData)` - Process cmsForm tags
|
|
1305
|
+
- `findAllFormTags(template)` - Find cmsForm tags in template
|
|
1306
|
+
- `parseFormAttributes(fullTag)` - Parse tag attributes
|
|
1307
|
+
- `parseFieldsFilter(attributes, formConfig)` - Filter fields based on attributes
|
|
1308
|
+
|
|
910
1309
|
### AdminManager Class
|
|
911
1310
|
- `setupAdmin()` - Initialize admin panel
|
|
912
1311
|
- `generateListRouteContent()` - Entity list pages
|
|
@@ -932,16 +1331,28 @@ project/
|
|
|
932
1331
|
│ │ ├── entity-access-manager.js
|
|
933
1332
|
│ │ ├── content-renderer.js
|
|
934
1333
|
│ │ └── response-manager.js
|
|
1334
|
+
│ ├── template-engine/ # Template processing classes
|
|
1335
|
+
│ │ └── forms-transformer.js
|
|
935
1336
|
│ ├── frontend.js # Main Frontend orchestrator
|
|
936
1337
|
│ ├── template-reloader.js # Template reloading functionality
|
|
937
1338
|
│ ├── search-request-handler.js
|
|
938
1339
|
│ ├── search.js # Search functionality
|
|
939
1340
|
│ ├── search-renderer.js # Search result rendering
|
|
1341
|
+
│ ├── dynamic-form.js # Forms validation and processing
|
|
1342
|
+
│ ├── dynamic-form-renderer.js # Forms template rendering
|
|
1343
|
+
│ ├── dynamic-form-request-handler.js # Forms request handling
|
|
940
1344
|
│ └── template-engine.js # Core template processing
|
|
941
1345
|
├── templates/
|
|
942
1346
|
│ ├── layouts/ # Body content layouts
|
|
943
1347
|
│ ├── domains/ # Domain-specific templates
|
|
1348
|
+
│ │ └── example.com/
|
|
1349
|
+
│ │ └── cms_forms/ # Domain-specific form templates
|
|
944
1350
|
│ ├── partials/ # Shared template partials
|
|
1351
|
+
│ ├── cms_forms/ # Default form templates
|
|
1352
|
+
│ │ ├── form.html # Main form wrapper
|
|
1353
|
+
│ │ ├── field_text.html # Text field template
|
|
1354
|
+
│ │ ├── field_email.html # Email field template
|
|
1355
|
+
│ │ └── field_select.html # Select field template
|
|
945
1356
|
│ ├── page.html # Base HTML wrapper
|
|
946
1357
|
│ └── 404.html # Error page
|
|
947
1358
|
├── translations/
|
|
@@ -607,6 +607,7 @@
|
|
|
607
607
|
width: 34px;
|
|
608
608
|
height: auto;
|
|
609
609
|
margin-right: 0.5rem;
|
|
610
|
+
opacity: 0.7;
|
|
610
611
|
}
|
|
611
612
|
|
|
612
613
|
.search-controls {
|
|
@@ -639,132 +640,6 @@
|
|
|
639
640
|
flex-shrink: 0;
|
|
640
641
|
}
|
|
641
642
|
|
|
642
|
-
.maps-wizard {
|
|
643
|
-
.main-action-container.maps-selection {
|
|
644
|
-
width: 100%;
|
|
645
|
-
|
|
646
|
-
.wizard-options-container {
|
|
647
|
-
display: flex;
|
|
648
|
-
justify-content: space-between;
|
|
649
|
-
flex-wrap: wrap;
|
|
650
|
-
width: 100%;
|
|
651
|
-
|
|
652
|
-
/* 3 or more items => 4 columns */
|
|
653
|
-
.wizard-map-option-container {
|
|
654
|
-
flex: 0 0 22%;
|
|
655
|
-
padding: 1rem 1% 0;
|
|
656
|
-
margin: 1.5rem 0 0;
|
|
657
|
-
border: 1px solid #ccc;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
/* 1 child => 1 columns */
|
|
661
|
-
.wizard-map-option-container:only-child {
|
|
662
|
-
flex: 0 0 100%;
|
|
663
|
-
padding: 1rem 0 0;
|
|
664
|
-
border: none;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
/* 2 children => 2 columns */
|
|
668
|
-
.wizard-map-option-container:first-child:nth-last-child(2),
|
|
669
|
-
.wizard-map-option-container:last-child:nth-child(2) {
|
|
670
|
-
flex: 0 0 47%;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
& input.map-wizard-option {
|
|
674
|
-
top: 2px;
|
|
675
|
-
margin-right: 0;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
.checkbox-container {
|
|
681
|
-
display: flex;
|
|
682
|
-
flex-direction: row;
|
|
683
|
-
font-weight: var(--bold);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
.wizard-options-container {
|
|
687
|
-
padding: 0;
|
|
688
|
-
margin-bottom: 1rem;
|
|
689
|
-
|
|
690
|
-
& input.map-wizard-option {
|
|
691
|
-
position: relative;
|
|
692
|
-
top: -2px;
|
|
693
|
-
margin-right: 6px;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
.wizard-map-option-container {
|
|
697
|
-
display: flex;
|
|
698
|
-
flex-direction: column;
|
|
699
|
-
list-style: none;
|
|
700
|
-
padding-top: 1rem;
|
|
701
|
-
margin-top: 1.5rem;
|
|
702
|
-
border-top: 1px solid #ccc;
|
|
703
|
-
|
|
704
|
-
& label {
|
|
705
|
-
cursor: pointer;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
& canvas {
|
|
709
|
-
width: 100%;
|
|
710
|
-
margin-top: 1rem;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
.wizard-option-container {
|
|
715
|
-
list-style: none;
|
|
716
|
-
margin-bottom: 1rem;
|
|
717
|
-
|
|
718
|
-
.main-option {
|
|
719
|
-
display: inline-block;
|
|
720
|
-
cursor: pointer;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
.maps-wizard-option-content {
|
|
724
|
-
display: none;
|
|
725
|
-
padding-left: 1.6rem;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
&.active {
|
|
729
|
-
.maps-wizard-option-content {
|
|
730
|
-
display: block;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
.maps-wizard-form {
|
|
737
|
-
align-items: flex-start;
|
|
738
|
-
flex-direction: column;
|
|
739
|
-
justify-content: flex-start;
|
|
740
|
-
|
|
741
|
-
.submit-container {
|
|
742
|
-
display: flex;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
.sub-map-option-description {
|
|
747
|
-
display: flex;
|
|
748
|
-
align-items: center;
|
|
749
|
-
|
|
750
|
-
& img.icon-sm {
|
|
751
|
-
margin-right: 1rem;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
.sub-maps-container {
|
|
756
|
-
display: block;
|
|
757
|
-
|
|
758
|
-
&.hidden {
|
|
759
|
-
display: none;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
& p {
|
|
763
|
-
margin: 1.5rem 0 0;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
|
|
768
643
|
.loading {
|
|
769
644
|
max-width: 50px;
|
|
770
645
|
|
|
@@ -820,8 +695,15 @@
|
|
|
820
695
|
}
|
|
821
696
|
}
|
|
822
697
|
|
|
823
|
-
.view-field
|
|
824
|
-
|
|
698
|
+
.view-field {
|
|
699
|
+
.field-value img {
|
|
700
|
+
max-width: 200px;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.admin-audio-player {
|
|
704
|
+
width: 100%;
|
|
705
|
+
max-width: 400px;
|
|
706
|
+
}
|
|
825
707
|
}
|
|
826
708
|
|
|
827
709
|
.edit-field {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{{#fieldValue}}
|
|
2
|
+
<audio controls class="admin-audio-player multiple-files">
|
|
3
|
+
<source src="{{&fieldValuePart}}">
|
|
4
|
+
Your browser does not support the audio element.
|
|
5
|
+
</audio>
|
|
6
|
+
<p><a href="{{&fieldValuePart}}"{{&target}}>{{&fieldOriginalValuePart}}</a></p>
|
|
7
|
+
<br/>
|
|
8
|
+
{{/fieldValue}}
|