Form Generation
Blueprint-driven forms with validation, modals, wizards, and complete CRUD operations.
Basic Form Generation
Generate forms automatically from blueprint definitions with built-in validation and type mapping.
// Define a blueprint
const userBlueprint = {
name: {
type: 'string',
label: 'Full Name',
required: true,
minLength: 2
},
email: {
type: 'email',
label: 'Email Address',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
},
age: {
type: 'number',
label: 'Age',
min: 18,
max: 120
},
role: {
type: 'select',
label: 'Role',
options: [
{ value: 'user', label: 'User' },
{ value: 'admin', label: 'Administrator' }
]
},
newsletter: {
type: 'boolean',
label: 'Subscribe to Newsletter'
}
};
// Create form
const form = Domma.forms.create(userBlueprint, initialData, {
onSubmit: (data, formInstance) => {
console.log('Form submitted:', data);
}
});
// Render to DOM
form.renderTo('#form-container');
Generated Basic Form
<div class="forms-controls">
<button type="button" class="btn btn-primary btn-sm" id="generate-basic-form">
<span data-icon="plus" data-icon-size="16"></span>
Generate Form
</button>
<button type="button" class="btn btn-secondary btn-sm" id="clear-basic-form">
<span data-icon="trash" data-icon-size="16"></span>
Clear
</button>
</div>
<div id="basic-form-container">
<div class="card mt-4" data-if="shown.value">
<div class="card-header">
<h4 class="card-title mb-0">Generated Basic Form</h4>
</div>
<div class="card-body" id="basic-form-wrapper"></div>
</div>
</div>
<div class="forms-output" id="basic-form-output">Click "Generate Form" to see the blueprint-driven form...
</div>/**
* Basic Form - from public/showcase/forms.
*/
export function mount(root) {
// Common blueprints for examples
const userBlueprint = {
name: {
type: 'string',
label: 'Full Name',
required: true,
minLength: 2,
formConfig: {
placeholder: 'Enter your full name'
}
},
email: {
type: 'email',
label: 'Email Address',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
formConfig: {
placeholder: 'user@example.com'
}
},
age: {
type: 'number',
label: 'Age',
min: 18,
max: 120,
formConfig: {
helperText: 'Must be between 18 and 120'
}
},
role: {
type: 'select',
label: 'Role',
options: [
{value: 'user', label: 'User'},
{value: 'moderator', label: 'Moderator'},
{value: 'admin', label: 'Administrator'}
],
default: 'user'
},
newsletter: {
type: 'boolean',
label: 'Subscribe to Newsletter',
default: false
}
};
const sampleData = {
name: 'Alice Johnson',
email: 'alice@example.com',
age: 28,
role: 'user',
newsletter: true
};
// Log function for outputs
function logToOutput(selector, message) {
const output = $(root).find(selector);
if (output.length === 0) {
console.warn('Output element not found:', selector);
return;
}
const timestamp = D().format('HH:mm:ss');
output.text(output.text() + `[${timestamp}] ${message}\n`);
if (output[0]) {
output.scrollTop(output[0].scrollHeight);
}
}
// The card is in demo.html behind data-if; keep the wrapper node, since
// data-if detaches it (and puts the same node back) when the card is hidden.
const wrapper = $(root).find('#basic-form-wrapper')[0];
const shown = M.observable(false);
const handle = M.applyBindings({shown}, root);
// Basic Form Generation
$(root).find('#generate-basic-form').on('click', () => {
try {
const form = Domma.forms.create(userBlueprint, sampleData, {
layout: 'grid',
columns: 2,
onSubmit: (data, formInstance) => {
logToOutput('#basic-form-output', 'Form submitted: ' + JSON.stringify(data, null, 2));
},
onValidationError: (errors, formInstance) => {
logToOutput('#basic-form-output', 'Validation errors: ' + JSON.stringify(errors, null, 2));
}
});
form.renderTo(wrapper);
shown.value = true;
logToOutput('#basic-form-output', 'Form generated successfully!');
} catch (error) {
logToOutput('#basic-form-output', 'Error: ' + error.message);
}
});
$(root).find('#clear-basic-form').on('click', () => {
shown.value = false;
$(wrapper).empty();
$(root).find('#basic-form-output').text('Cleared. Click "Generate Form" to create a new form...');
});
return () => handle.dispose();
}Form Layouts
Support for different layout styles: stacked, grid, and inline layouts with customizable sections.
// Grid layout with sections
const profileForm = Domma.forms.create(blueprint, data, {
layout: 'grid',
columns: 2,
sections: [
{
title: 'Personal Information',
fields: ['name', 'email', 'phone']
},
{
title: 'Account Settings',
fields: ['role', 'newsletter', 'notifications']
}
]
});
Layout Demo
<div class="forms-controls">
<button type="button" class="btn btn-primary btn-sm" id="layout-stacked">
<span data-icon="layout" data-icon-size="16"></span>
Stacked
</button>
<button type="button" class="btn btn-success btn-sm" id="layout-grid">
<span data-icon="grid" data-icon-size="16"></span>
Grid (2-col)
</button>
<button type="button" class="btn btn-info btn-sm" id="layout-sections">
<span data-icon="folder-open" data-icon-size="16"></span>
With Sections
</button>
<button type="button" class="btn btn-secondary btn-sm" id="clear-layout-form">
<span data-icon="trash" data-icon-size="16"></span>
Clear
</button>
</div>
<div id="layout-form-container">
<div class="card mt-4" data-if="shown.value">
<div class="card-header">
<h4 class="card-title mb-0"><span data-bind-text="layoutType.value"></span> Layout Demo</h4>
</div>
<div class="card-body" id="layout-form-wrapper"></div>
</div>
</div>/**
* Layout Styles - from public/showcase/forms.
*/
export function mount(root) {
// Common blueprints for examples
const userBlueprint = {
name: {
type: 'string',
label: 'Full Name',
required: true,
minLength: 2,
formConfig: {
placeholder: 'Enter your full name'
}
},
email: {
type: 'email',
label: 'Email Address',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
formConfig: {
placeholder: 'user@example.com'
}
},
age: {
type: 'number',
label: 'Age',
min: 18,
max: 120,
formConfig: {
helperText: 'Must be between 18 and 120'
}
},
role: {
type: 'select',
label: 'Role',
options: [
{value: 'user', label: 'User'},
{value: 'moderator', label: 'Moderator'},
{value: 'admin', label: 'Administrator'}
],
default: 'user'
},
newsletter: {
type: 'boolean',
label: 'Subscribe to Newsletter',
default: false
}
};
const sampleData = {
name: 'Alice Johnson',
email: 'alice@example.com',
age: 28,
role: 'user',
newsletter: true
};
// The card is in demo.html behind data-if, its title bound to layoutType.
// Keep the wrapper node: data-if detaches it (and puts the same node back).
const wrapper = $(root).find('#layout-form-wrapper')[0];
const shown = M.observable(false);
const layoutType = M.observable('');
const handle = M.applyBindings({shown, layoutType}, root);
// Helper function to render layout forms into the card
function renderLayoutForm(form, type) {
form.renderTo(wrapper);
layoutType.value = type;
shown.value = true;
}
// Form Layouts
$(root).find('#layout-stacked').on('click', () => {
try {
const form = Domma.forms.create(userBlueprint, sampleData, {
layout: 'stacked'
});
renderLayoutForm(form, 'Stacked');
} catch (error) {
console.error('Layout error:', error);
}
});
$(root).find('#layout-grid').on('click', () => {
try {
const form = Domma.forms.create(userBlueprint, sampleData, {
layout: 'grid',
columns: 2
});
renderLayoutForm(form, 'Grid (2-col)');
} catch (error) {
console.error('Layout error:', error);
}
});
$(root).find('#layout-sections').on('click', () => {
try {
const form = Domma.forms.create(userBlueprint, sampleData, {
layout: 'grid',
columns: 2,
sections: [
{
title: 'Personal Information',
fields: ['name', 'email', 'age']
},
{
title: 'Account Settings',
fields: ['role', 'newsletter']
}
]
});
renderLayoutForm(form, 'With Sections');
} catch (error) {
console.error('Layout error:', error);
}
});
$(root).find('#clear-layout-form').on('click', () => {
shown.value = false;
$(wrapper).empty();
});
return () => handle.dispose();
}Chooser - Visual Option-Picker
A blueprint-driven option picker that replaces native radio/checkbox controls when the choice deserves visual weight. type: 'chooser' covers all four variant × multiple combinations from one config and integrates with the same validation, model binding, and submission pipeline as every other field type.
// Card-style single-select with rich per-option metadata
const planBlueprint = {
plan: {
type: 'chooser',
variant: 'card', // 'card' | 'chip'
multiple: false, // false = radio, true = checkbox semantics
density: 'comfortable', // 'comfortable' | 'compact'
columns: 3, // grid columns (cards only - chips wrap)
required: true,
label: 'Choose your plan',
accent: 'primary', // semantic name OR '#hex'
accentStyle: 'border', // 'border' | 'solid' | 'glow' | 'overlay' | 'underline'
options: [
{ value: 'starter', label: 'Starter', icon: 'rocket',
description: 'For solo builders.' },
{ value: 'pro', label: 'Pro', icon: 'zap',
description: 'Teams up to 10.', recommended: true,
badge: { text: 'POPULAR', type: 'success' } },
{ value: 'ent', label: 'Enterprise', icon: 'briefcase',
description: 'Custom limits + SSO.' }
]
}
};
// Chip-style multi-select tag picker
const tagsBlueprint = {
tags: {
type: 'chooser',
variant: 'chip',
multiple: true,
label: 'Pick your tags',
options: [
{ value: 'js', label: 'JavaScript', icon: 'code' },
{ value: 'css', label: 'CSS', icon: 'palette' },
{ value: 'a11y', label: 'A11y', icon: 'eye' },
{ value: 'perf', label: 'Performance', icon: 'zap' }
]
}
};
<div class="forms-controls">
<button type="button" class="btn btn-primary btn-sm" id="chooser-card-generate">
<span data-icon="check-square" data-icon-size="16"></span>
Generate card chooser
</button>
<button type="button" class="btn btn-info btn-sm" id="chooser-chip-generate">
<span data-icon="tag" data-icon-size="16"></span>
Generate chip multi-select
</button>
<button type="button" class="btn btn-success btn-sm" id="chooser-visual-generate">
<span data-icon="star" data-icon-size="16"></span>
Visual options (glow + accent)
</button>
<button type="button" class="btn btn-warning btn-sm" id="chooser-validate">
<span data-icon="alert-triangle" data-icon-size="16"></span>
Validate
</button>
<button type="button" class="btn btn-secondary btn-sm" id="chooser-submit">
<span data-icon="send" data-icon-size="16"></span>
Get values
</button>
<button type="button" class="btn btn-secondary btn-sm" id="chooser-clear">
<span data-icon="trash" data-icon-size="16"></span>
Clear
</button>
</div>
<div id="chooser-form-container"></div>
<div class="forms-output" id="chooser-output">Submit or validate to see captured values</div>/**
* Live demo - card single-select with required validation - from public/showcase/forms.
*/
export function mount(root) {
// ===== Chooser Field =====
let chooserFormInstance = null;
const chooserOutput = $(root).find('#chooser-output')[0];
const chooserContainer = $(root).find('#chooser-form-container')[0];
const cardChooserBlueprint = {
plan: {
type: 'chooser',
variant: 'card',
columns: 3,
required: true,
label: 'Choose your plan',
options: [
{ value: 'starter', label: 'Starter', icon: 'rocket',
description: 'For solo builders.' },
{ value: 'pro', label: 'Pro', icon: 'zap',
description: 'Teams up to 10.', recommended: true,
badge: { text: 'POPULAR', type: 'success' } },
{ value: 'ent', label: 'Enterprise', icon: 'briefcase',
description: 'Custom limits + SSO.' }
]
}
};
const chipChooserBlueprint = {
tags: {
type: 'chooser',
variant: 'chip',
multiple: true,
label: 'Pick your tags',
options: [
{ value: 'js', label: 'JavaScript', icon: 'code' },
{ value: 'css', label: 'CSS', icon: 'palette' },
{ value: 'a11y', label: 'A11y', icon: 'eye' },
{ value: 'perf', label: 'Performance', icon: 'zap' },
{ value: 'wasm', label: 'WASM', icon: 'cpu', disabled: true }
]
}
};
const visualChooserBlueprint = {
plan: {
type: 'chooser',
variant: 'card',
columns: 3,
required: true,
label: 'Glow + accent demo',
accent: '#ec4899',
accentStyle: 'glow',
glow: true,
glowColour: '#ec4899',
shadow: 'lg',
options: [
{ value: 'starter', label: 'Starter', icon: 'rocket', description: 'For solo builders.' },
{ value: 'pro', label: 'Pro', icon: 'zap', description: 'Teams up to 10.',
recommended: true, badge: { text: 'POPULAR', type: 'success' } },
{ value: 'ent', label: 'Enterprise', icon: 'briefcase', description: 'Custom limits + SSO.' }
]
}
};
const renderChooser = (blueprint) => {
chooserContainer.replaceChildren();
chooserFormInstance = Domma.forms.create(blueprint);
chooserFormInstance.renderTo('#chooser-form-container');
chooserOutput.textContent = 'Form rendered. Pick an option, then click "Validate" or "Get values".';
};
$(root).find('#chooser-card-generate').on('click', () => renderChooser(cardChooserBlueprint));
$(root).find('#chooser-chip-generate').on('click', () => renderChooser(chipChooserBlueprint));
$(root).find('#chooser-visual-generate').on('click', () => renderChooser(visualChooserBlueprint));
$(root).find('#chooser-validate').on('click', () => {
if (!chooserFormInstance) {
chooserOutput.textContent = 'Generate a chooser first.';
return;
}
const ok = chooserFormInstance.validate();
chooserOutput.textContent = ok
? 'Validation passed.'
: 'Validation failed: ' + JSON.stringify(chooserFormInstance.errors, null, 2);
});
$(root).find('#chooser-submit').on('click', () => {
if (!chooserFormInstance) {
chooserOutput.textContent = 'Generate a chooser first.';
return;
}
chooserOutput.textContent = JSON.stringify(chooserFormInstance.getData(), null, 2);
});
$(root).find('#chooser-clear').on('click', () => {
chooserContainer.replaceChildren();
chooserFormInstance = null;
chooserOutput.textContent = 'Submit or validate to see captured values';
});
}| Key | Type | Default | Notes |
|---|---|---|---|
variant | 'card' | 'chip' | 'card' | Visual style |
multiple | boolean | false | Single ↔ multi-select; value becomes string vs array |
density | 'comfortable' | 'compact' | 'comfortable' | Compact strips description |
columns | 1-6 | 3 | Card variant only; chips wrap |
accent | semantic or hex | 'primary' | Selected/recommended highlight |
accentStyle | 5 styles | 'border' | border | solid | glow | overlay | underline |
glow | boolean | false | Outer glow on selected |
shadow | none | sm | md | lg | xl | 'none' | Applied to every option |
options[].icon | string | - | Domma icon name |
options[].description | string | - | Card + comfortable only |
options[].tooltip | string | - | Hover hint |
options[].badge | { text, type } | - | Corner badge - primary/success/info/warning/danger |
options[].recommended | boolean | false | Success-coloured ring |
options[].disabled | boolean | false | Non-interactive, skipped by keyboard |
<table class="domma-table table-bordered table-sm">
<thead>
<tr><th>Key</th><th>Type</th><th>Default</th><th>Notes</th></tr>
</thead>
<tbody>
<tr><td><code>variant</code></td><td><code>'card' | 'chip'</code></td><td><code>'card'</code></td><td>Visual style</td></tr>
<tr><td><code>multiple</code></td><td>boolean</td><td>false</td><td>Single ↔ multi-select; value becomes string vs array</td></tr>
<tr><td><code>density</code></td><td><code>'comfortable' | 'compact'</code></td><td><code>'comfortable'</code></td><td>Compact strips description</td></tr>
<tr><td><code>columns</code></td><td>1-6</td><td>3</td><td>Card variant only; chips wrap</td></tr>
<tr><td><code>accent</code></td><td>semantic or hex</td><td><code>'primary'</code></td><td>Selected/recommended highlight</td></tr>
<tr><td><code>accentStyle</code></td><td>5 styles</td><td><code>'border'</code></td><td><code>border | solid | glow | overlay | underline</code></td></tr>
<tr><td><code>glow</code></td><td>boolean</td><td>false</td><td>Outer glow on selected</td></tr>
<tr><td><code>shadow</code></td><td><code>none | sm | md | lg | xl</code></td><td><code>'none'</code></td><td>Applied to every option</td></tr>
<tr><td><code>options[].icon</code></td><td>string</td><td>-</td><td>Domma icon name</td></tr>
<tr><td><code>options[].description</code></td><td>string</td><td>-</td><td>Card + comfortable only</td></tr>
<tr><td><code>options[].tooltip</code></td><td>string</td><td>-</td><td>Hover hint</td></tr>
<tr><td><code>options[].badge</code></td><td><code>{ text, type }</code></td><td>-</td><td>Corner badge - primary/success/info/warning/danger</td></tr>
<tr><td><code>options[].recommended</code></td><td>boolean</td><td>false</td><td>Success-coloured ring</td></tr>
<tr><td><code>options[].disabled</code></td><td>boolean</td><td>false</td><td>Non-interactive, skipped by keyboard</td></tr>
</tbody>
</table>
<p class="mt-2"><a href="/showcase/elements/chooser" class="btn btn-secondary btn-sm">
<span data-icon="external-link" data-icon-size="14"></span>
See full Chooser showcase
</a></p>Modal Forms
Create professional modal forms with automatic event handling and error management.
// Modal form
const modal = Domma.forms.modal(userBlueprint, userData, {
title: 'Edit User Profile',
size: 'medium',
saveText: 'Update Profile',
onSave: async (formData, formInstance) => {
// Handle form submission
await updateUserProfile(formData);
},
onError: (error, formData, formInstance) => {
console.error('Form error:', error);
}
});
modal.open();
<div class="forms-controls">
<button type="button" class="btn btn-primary btn-sm" id="open-edit-modal">
<span data-icon="edit" data-icon-size="16"></span>
Edit User Modal
</button>
<button type="button" class="btn btn-success btn-sm" id="open-create-modal">
<span data-icon="plus" data-icon-size="16"></span>
Create User Modal
</button>
</div>
<div class="forms-output" id="modal-form-output">Click a button to open a modal form...</div>/**
* Modal Forms - from public/showcase/forms.
*/
export function mount(root) {
// Common blueprints for examples
const userBlueprint = {
name: {
type: 'string',
label: 'Full Name',
required: true,
minLength: 2,
formConfig: {
placeholder: 'Enter your full name'
}
},
email: {
type: 'email',
label: 'Email Address',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
formConfig: {
placeholder: 'user@example.com'
}
},
age: {
type: 'number',
label: 'Age',
min: 18,
max: 120,
formConfig: {
helperText: 'Must be between 18 and 120'
}
},
role: {
type: 'select',
label: 'Role',
options: [
{value: 'user', label: 'User'},
{value: 'moderator', label: 'Moderator'},
{value: 'admin', label: 'Administrator'}
],
default: 'user'
},
newsletter: {
type: 'boolean',
label: 'Subscribe to Newsletter',
default: false
}
};
const sampleData = {
name: 'Alice Johnson',
email: 'alice@example.com',
age: 28,
role: 'user',
newsletter: true
};
// Log function for outputs
function logToOutput(selector, message) {
const output = $(selector);
if (output.length === 0) {
console.warn('Output element not found:', selector);
return;
}
const timestamp = D().format('HH:mm:ss');
output.text(output.text() + `[${timestamp}] ${message}\n`);
if (output[0]) {
output.scrollTop(output[0].scrollHeight);
}
}
// Modal Forms
$(root).find('#open-edit-modal').on('click', () => {
try {
const modal = Domma.forms.modal(userBlueprint, sampleData, {
title: 'Edit User Profile',
size: 'medium',
saveText: 'Update Profile',
onSave: async (formData, formInstance) => {
logToOutput('#modal-form-output', 'User updated: ' + JSON.stringify(formData, null, 2));
},
onError: (error, formData, formInstance) => {
logToOutput('#modal-form-output', 'Error: ' + error.message);
}
});
modal.open();
} catch (error) {
logToOutput('#modal-form-output', 'Modal error: ' + error.message);
}
});
$(root).find('#open-create-modal').on('click', () => {
try {
const modal = Domma.forms.modal(userBlueprint, {}, {
title: 'Create New User',
size: 'medium',
saveText: 'Create User',
onSave: async (formData, formInstance) => {
logToOutput('#modal-form-output', 'User created: ' + JSON.stringify(formData, null, 2));
},
onError: (error, formData, formInstance) => {
logToOutput('#modal-form-output', 'Error: ' + error.message);
}
});
modal.open();
} catch (error) {
logToOutput('#modal-form-output', 'Modal error: ' + error.message);
}
});
}Multi-Step Wizard
Create multi-step workflows with progress tracking and step validation.
// Multi-step wizard
const wizard = Domma.forms.wizard([
{
title: 'Account Details',
description: 'Basic account information',
blueprint: {
username: { type: 'string', required: true },
email: { type: 'email', required: true }
}
},
{
title: 'Profile Information',
description: 'Tell us about yourself',
blueprint: {
name: { type: 'string', required: true },
bio: { type: 'textarea' }
}
},
{
title: 'Preferences',
description: 'Customize your experience',
blueprint: {
theme: { type: 'select', options: ['light', 'dark'] },
notifications: { type: 'boolean' }
}
}
], {}, {
title: 'Account Setup Wizard',
onComplete: async (allData, formInstance) => {
console.log('Wizard completed:', allData);
}
});
wizard.open();
<div class="forms-controls">
<button type="button" class="btn btn-primary btn-sm" id="open-wizard">
<span data-icon="chevron-right" data-icon-size="16"></span>
Start Registration Wizard
</button>
</div>
<div id="wizard-container" style="margin-top: 1rem; display: none;"></div>
<div class="forms-output" id="wizard-output">Click "Start Registration Wizard" to begin...</div>/**
* Registration Wizard - from public/showcase/forms.
*/
export function mount(root) {
// Log function for outputs
function logToOutput(selector, message) {
const output = $(selector);
if (output.length === 0) {
console.warn('Output element not found:', selector);
return;
}
const timestamp = D().format('HH:mm:ss');
output.text(output.text() + `[${timestamp}] ${message}\n`);
if (output[0]) {
output.scrollTop(output[0].scrollHeight);
}
}
// Wizard Component
$(root).find('#open-wizard').on('click', () => {
try {
// Show the wizard container
$(root).find('#wizard-container').show();
const wizard = Domma.forms.wizard('#wizard-container', {
blueprint: {
steps: [
{
title: 'Account Details',
description: 'Basic account information',
fields: [
{
name: 'username',
type: 'text',
label: 'Username',
required: true,
validation: {
minLength: 3
}
},
{
name: 'email',
type: 'email',
label: 'Email Address',
required: true
}
]
},
{
title: 'Profile Information',
description: 'Tell us about yourself',
fields: [
{
name: 'name',
type: 'text',
label: 'Full Name',
required: true
},
{
name: 'bio',
type: 'textarea',
label: 'Biography',
formConfig: {
rows: 3
}
}
]
},
{
title: 'Preferences',
description: 'Customize your experience',
fields: [
{
name: 'theme',
type: 'select',
label: 'Theme',
options: [
{value: 'light', label: 'Light'},
{value: 'dark', label: 'Dark'}
]
},
{
name: 'notifications',
type: 'checkbox',
label: 'Enable Notifications'
}
]
}
]
},
layout: 'grid',
cols: 2,
finishText: 'Complete Registration',
onSubmit: (allData) => {
logToOutput('#wizard-output', 'Wizard completed! Data: ' + JSON.stringify(allData, null, 2));
$(root).find('#wizard-container').hide();
},
onStepChange: (step, data) => {
logToOutput('#wizard-output', `Moved to step ${step + 1}: ${JSON.stringify(data, null, 2)}`);
},
onValidation: (isValid, errors, step) => {
if (!isValid) {
logToOutput('#wizard-output', `Validation failed on step ${step + 1}: ${errors.join(', ')}`);
}
},
onError: (error) => {
logToOutput('#wizard-output', 'Wizard error: ' + error.message);
}
});
logToOutput('#wizard-output', 'Wizard created successfully! Use the navigation buttons to proceed.');
} catch (error) {
logToOutput('#wizard-output', 'Wizard error: ' + error.message);
console.error('Wizard creation error:', error);
}
});
}Complete CRUD Operations
Zero-configuration CRUD interfaces with automatic table generation and form modals.
// Complete CRUD setup
const userCrud = Domma.forms.crud({
blueprint: userBlueprint,
endpoint: '/api/users',
tableSelector: '#users-table',
title: 'Manage Users',
primaryKey: 'id',
displayField: 'name',
onSuccess: (message) => console.log(message),
onError: (error) => console.error(error)
});
// Initialize CRUD interface
await userCrud.init();
<div class="mock-data-info">
<strong>Note:</strong> This demo uses mock data and simulated API calls. In a real application, this would
connect to your backend API.
</div>
<div class="forms-controls">
<button type="button" class="btn btn-success btn-sm crud-add-btn">
<span data-icon="plus" data-icon-size="16"></span>
Add User
</button>
<button type="button" class="btn btn-secondary btn-sm crud-refresh-btn">
<span data-icon="refresh" data-icon-size="16"></span>
Refresh
</button>
</div>
<div id="crud-users-table" class="crud-demo-table"></div>
<div class="forms-output" id="crud-output">CRUD operations will be logged here...</div>/**
* CRUD Operations (Mock Data) - from public/showcase/forms.
*/
export function mount(root) {
// Common blueprints for examples
const userBlueprint = {
name: {
type: 'string',
label: 'Full Name',
required: true,
minLength: 2,
formConfig: {
placeholder: 'Enter your full name'
}
},
email: {
type: 'email',
label: 'Email Address',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
formConfig: {
placeholder: 'user@example.com'
}
},
age: {
type: 'number',
label: 'Age',
min: 18,
max: 120,
formConfig: {
helperText: 'Must be between 18 and 120'
}
},
role: {
type: 'select',
label: 'Role',
options: [
{value: 'user', label: 'User'},
{value: 'moderator', label: 'Moderator'},
{value: 'admin', label: 'Administrator'}
],
default: 'user'
},
newsletter: {
type: 'boolean',
label: 'Subscribe to Newsletter',
default: false
}
};
// Log function for outputs
function logToOutput(selector, message) {
const output = $(selector);
if (output.length === 0) {
console.warn('Output element not found:', selector);
return;
}
const timestamp = D().format('HH:mm:ss');
output.text(output.text() + `[${timestamp}] ${message}\n`);
if (output[0]) {
output.scrollTop(output[0].scrollHeight);
}
}
// Mock CRUD Data
let mockUsers = [
{id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'admin', age: 28},
{id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'user', age: 34},
{id: 3, name: 'Carol White', email: 'carol@example.com', role: 'moderator', age: 29}
];
let nextId = 4;
// Mock CRUD Helper (simplified version)
let crudTable = null;
function initMockCrud() {
// Create table
const columns = [
{key: 'id', title: 'ID', sortable: true, width: '60px'},
{key: 'name', title: 'Name', sortable: true},
{key: 'email', title: 'Email', sortable: true},
{key: 'role', title: 'Role', sortable: true},
{key: 'age', title: 'Age', sortable: true},
{
key: 'actions',
title: 'Actions',
sortable: false,
render: (value, row) => `
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary crud-edit-btn" data-id="${row.id}">
<span data-icon="edit" data-icon-size="16"></span>
Edit
</button>
<button class="btn btn-outline-danger crud-delete-btn" data-id="${row.id}">
<span data-icon="trash" data-icon-size="16"></span>
Delete
</button>
</div>
`
}
];
crudTable = Domma.tables.create('#crud-users-table', {
data: mockUsers,
columns,
pagination: true,
pageSize: 10,
striped: true,
exportPanel: true
});
logToOutput('#crud-output', 'CRUD table initialized with ' + mockUsers.length + ' users');
}
// CRUD Event Handlers
$(root).find('.crud-add-btn').on('click', () => {
try {
const modal = Domma.forms.modal(userBlueprint, {}, {
title: 'Add New User',
saveText: 'Add User',
onSave: async (formData) => {
const newUser = {...formData, id: nextId++};
mockUsers.push(newUser);
if (crudTable) {
crudTable.setData([...mockUsers]);
}
logToOutput('#crud-output', 'User added: ' + JSON.stringify(newUser));
}
});
modal.open();
} catch (error) {
logToOutput('#crud-output', 'Add user error: ' + error.message);
}
});
$(root).find('.crud-refresh-btn').on('click', () => {
if (crudTable) {
crudTable.setData([...mockUsers]);
logToOutput('#crud-output', 'Table refreshed');
}
});
// Event delegation for edit/delete buttons, on the demo (it goes when the demo resets)
$(root).on('click', '.crud-edit-btn', function () {
const userId = parseInt($(this).attr('data-id'));
const user = mockUsers.find(u => u.id === userId);
if (user) {
try {
const modal = Domma.forms.modal(userBlueprint, user, {
title: `Edit User: ${user.name}`,
saveText: 'Update User',
onSave: async (formData) => {
const index = mockUsers.findIndex(u => u.id === userId);
if (index !== -1) {
mockUsers[index] = {...mockUsers[index], ...formData};
if (crudTable) {
crudTable.setData([...mockUsers]);
}
logToOutput('#crud-output', 'User updated: ' + JSON.stringify(mockUsers[index]));
}
}
});
modal.open();
} catch (error) {
logToOutput('#crud-output', 'Edit user error: ' + error.message);
}
}
});
$(root).on('click', '.crud-delete-btn', async function () {
const userId = parseInt($(this).attr('data-id'));
const user = mockUsers.find(u => u.id === userId);
if (user) {
const confirmed = await E.confirm(
`Delete user "${user.name}"?\n\nThis action cannot be undone.`,
{title: 'Confirm Delete', confirmText: 'Delete'}
);
if (confirmed) {
const index = mockUsers.findIndex(u => u.id === userId);
if (index !== -1) {
mockUsers.splice(index, 1);
if (crudTable) {
crudTable.setData([...mockUsers]);
}
logToOutput('#crud-output', 'User deleted: ' + user.name);
}
}
}
});
// Initialize CRUD on page load
initMockCrud();
}Validation & Error Handling
Built-in and custom validators with real-time feedback and comprehensive error handling.
// Blueprint with various validation rules
const validationBlueprint = {
username: {
type: 'string',
required: true,
minLength: 3,
maxLength: 20,
pattern: /^[a-zA-Z0-9_]+$/,
formConfig: {
hint: 'Only letters, numbers, and underscores'
}
},
password: {
type: 'password',
required: true,
minLength: 8,
validate: (value) => {
if (!/[A-Z]/.test(value)) return 'Must contain uppercase letter';
if (!/[0-9]/.test(value)) return 'Must contain number';
return true;
}
},
confirmPassword: {
type: 'password',
required: true,
validate: (value, allData) => {
return value === allData.password || 'Passwords do not match';
}
},
age: {
type: 'number',
min: 13,
max: 120,
validate: (value) => {
if (value < 18) return 'Must be 18 or older for full access';
return true;
}
}
};
Interactive Validation Form
<div class="forms-controls">
<button type="button" class="btn btn-primary btn-sm" id="generate-validation-form">
<span data-icon="check" data-icon-size="16"></span>
Generate Validation Form
</button>
<button type="button" class="btn btn-secondary btn-sm" id="clear-validation-form">
<span data-icon="trash" data-icon-size="16"></span>
Clear
</button>
</div>
<div id="validation-form-container">
<div class="card mt-4" data-if="shown.value">
<div class="card-header">
<h4 class="card-title mb-0">Interactive Validation Form</h4>
</div>
<div class="card-body" id="validation-form-wrapper"></div>
</div>
</div>
<div class="forms-output" id="validation-output">Form validation results will appear here...</div>/**
* Form Validation - from public/showcase/forms.
*/
export function mount(root) {
// Log function for outputs
function logToOutput(selector, message) {
const output = $(root).find(selector);
if (output.length === 0) {
console.warn('Output element not found:', selector);
return;
}
const timestamp = D().format('HH:mm:ss');
output.text(output.text() + `[${timestamp}] ${message}\n`);
if (output[0]) {
output.scrollTop(output[0].scrollHeight);
}
}
// Validation Form
const validationBlueprint = {
username: {
type: 'string',
label: 'Username',
required: true,
minLength: 3,
maxLength: 20,
pattern: /^[a-zA-Z0-9_]+$/,
formConfig: {
hint: 'Only letters, numbers, and underscores',
placeholder: 'Enter username'
},
validate: (value) => {
if (value.toLowerCase().includes('admin')) {
return 'Username cannot contain "admin"';
}
if (value.toLowerCase().includes('test')) {
return 'Username cannot contain "test"';
}
return true;
}
},
email: {
type: 'email',
label: 'Email Address',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
formConfig: {
hint: 'Enter a valid email address',
placeholder: 'user@example.com'
},
validate: (value) => {
if (value.includes('spam')) {
return 'Email cannot contain "spam"';
}
return true;
}
},
age: {
type: 'number',
label: 'Age',
required: true,
min: 13,
max: 120,
formConfig: {
hint: 'Must be between 13 and 120',
placeholder: 'Enter your age'
},
validate: (value) => {
const num = parseInt(value);
if (num < 13) return 'Must be at least 13 years old';
if (num > 120) return 'Age cannot exceed 120';
if (num >= 13 && num < 18) return 'Warning: Under 18 requires parental consent';
return true;
}
},
password: {
type: 'password',
label: 'Password',
required: true,
minLength: 8,
formConfig: {
hint: 'At least 8 characters with uppercase, number, and special character',
placeholder: 'Enter secure password'
},
validate: (value) => {
if (!/[A-Z]/.test(value)) return 'Must contain uppercase letter';
if (!/[a-z]/.test(value)) return 'Must contain lowercase letter';
if (!/[0-9]/.test(value)) return 'Must contain number';
if (!/[!@#$%^&*(),.?":{}|<>]/.test(value)) return 'Must contain special character';
return true;
}
},
confirmPassword: {
type: 'password',
label: 'Confirm Password',
required: true,
formConfig: {
hint: 'Re-enter the password above',
placeholder: 'Confirm password'
},
validate: (value, allData) => {
if (!value) return 'Password confirmation is required';
return value === allData.password || 'Passwords do not match';
}
}
};
// The card is in demo.html behind data-if; keep the wrapper node, since
// data-if detaches it (and puts the same node back) when the card is hidden.
const wrapper = $(root).find('#validation-form-wrapper')[0];
const shown = M.observable(false);
const handle = M.applyBindings({shown}, root);
$(root).find('#generate-validation-form').on('click', () => {
try {
const form = Domma.forms.create(validationBlueprint, {}, {
validationMode: 'blur',
layout: 'grid',
columns: 2,
onSubmit: (data, formInstance) => {
logToOutput('#validation-output', 'Validation form submitted: ' + JSON.stringify(data, null, 2));
},
onValidationError: (errors, formInstance) => {
logToOutput('#validation-output', 'Validation errors: ' + JSON.stringify(errors, null, 2));
}
});
form.renderTo(wrapper);
shown.value = true;
logToOutput('#validation-output', 'Validation form generated. Try entering invalid data to see validation in action.');
} catch (error) {
logToOutput('#validation-output', 'Error: ' + error.message);
}
});
$(root).find('#clear-validation-form').on('click', () => {
shown.value = false;
$(wrapper).empty();
$(root).find('#validation-output').text('Cleared. Click "Generate Validation Form" to create a new form...');
});
return () => handle.dispose();
}Integration with Domma Models
Seamless integration with Domma's reactive model system for two-way data binding and persistence.
// Create model with persistence
const userModel = M.create(userBlueprint, initialData, {
persist: 'user-profile' // Auto-save to localStorage
});
// Form automatically binds to model
const form = Domma.forms.create(userBlueprint, userModel.get(), {
model: userModel, // Two-way binding
onSubmit: (data, formInstance) => {
userModel.set(data); // Updates model and saves to storage
}
});
// Listen for model changes
userModel.onChange((changes) => {
console.log('Model updated:', changes);
});
Model Integration Form
Changes automatically sync to localStorage<div class="forms-controls">
<button type="button" class="btn btn-primary btn-sm" id="create-model-form">
<span data-icon="database" data-icon-size="16"></span>
Create Model Form
</button>
<button type="button" class="btn btn-info btn-sm" id="load-saved-data">
<span data-icon="download" data-icon-size="16"></span>
Load Saved Data
</button>
<button type="button" class="btn btn-warning btn-sm" id="clear-saved-data">
<span data-icon="trash" data-icon-size="16"></span>
Clear Storage
</button>
</div>
<div id="model-form-container">
<div class="card mt-4" data-if="shown.value">
<div class="card-header">
<h4 class="card-title mb-0">Model Integration Form</h4>
<small class="text-muted">Changes automatically sync to localStorage</small>
</div>
<div class="card-body" id="model-form-wrapper"></div>
</div>
</div>
<div class="forms-output" id="model-output">Model changes and persistence will be logged here...</div>/**
* Model Integration - from public/showcase/forms.
*/
export function mount(root) {
// Common blueprints for examples
const userBlueprint = {
name: {
type: 'string',
label: 'Full Name',
required: true,
minLength: 2,
formConfig: {
placeholder: 'Enter your full name'
}
},
email: {
type: 'email',
label: 'Email Address',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
formConfig: {
placeholder: 'user@example.com'
}
},
age: {
type: 'number',
label: 'Age',
min: 18,
max: 120,
formConfig: {
helperText: 'Must be between 18 and 120'
}
},
role: {
type: 'select',
label: 'Role',
options: [
{value: 'user', label: 'User'},
{value: 'moderator', label: 'Moderator'},
{value: 'admin', label: 'Administrator'}
],
default: 'user'
},
newsletter: {
type: 'boolean',
label: 'Subscribe to Newsletter',
default: false
}
};
const sampleData = {
name: 'Alice Johnson',
email: 'alice@example.com',
age: 28,
role: 'user',
newsletter: true
};
// Log function for outputs
function logToOutput(selector, message) {
const output = $(root).find(selector);
if (output.length === 0) {
console.warn('Output element not found:', selector);
return;
}
const timestamp = D().format('HH:mm:ss');
output.text(output.text() + `[${timestamp}] ${message}\n`);
if (output[0]) {
output.scrollTop(output[0].scrollHeight);
}
}
// The card is in demo.html behind data-if; keep the wrapper node, since
// data-if detaches it (and puts the same node back) when the card is hidden.
const wrapper = $(root).find('#model-form-wrapper')[0];
const shown = M.observable(false);
const handle = M.applyBindings({shown}, root);
// Model Integration
let userModel = null;
$(root).find('#create-model-form').on('click', () => {
try {
// Create model with persistence
userModel = M.create(userBlueprint, sampleData, {
persist: 'forms-showcase-user'
});
// Listen for model changes
userModel.onChange((changes) => {
logToOutput('#model-output', 'Model changed: ' + JSON.stringify(changes, null, 2));
});
const form = Domma.forms.create(userBlueprint, userModel.get(), {
layout: 'grid',
columns: 2,
onSubmit: (data, formInstance) => {
userModel.set(data);
userModel.save(); // Explicitly save to localStorage
logToOutput('#model-output', 'Form submitted and model updated: ' + JSON.stringify(data, null, 2));
logToOutput('#model-output', 'Data automatically saved to localStorage with key: forms-showcase-user');
},
onChange: (data, formInstance) => {
// Auto-save on field changes for real-time persistence
userModel.set(data);
userModel.save();
}
});
form.renderTo(wrapper);
shown.value = true;
logToOutput('#model-output', 'Model form created with persistence. Data will be saved to localStorage.');
} catch (error) {
logToOutput('#model-output', 'Error: ' + error.message);
}
});
$(root).find('#load-saved-data').on('click', () => {
if (userModel) {
userModel.load();
logToOutput('#model-output', 'Data loaded from localStorage: ' + JSON.stringify(userModel.get(), null, 2));
} else {
logToOutput('#model-output', 'No model created yet. Create a form first.');
}
});
$(root).find('#clear-saved-data').on('click', () => {
if (userModel) {
userModel.clearStorage();
logToOutput('#model-output', 'Saved data cleared from localStorage.');
} else {
S.remove('forms-showcase-user');
logToOutput('#model-output', 'Storage cleared manually.');
}
});
return () => handle.dispose();
}Complete Forms Reference
Complete API reference for blueprint-driven form generation
Field Types
string
Text input
email
Email input
password
Password input
number
Number input
boolean
Checkbox
textarea
Multi-line text
select
Dropdown
radio
Radio group
checkbox-group
Multi-select
file
File upload
date
Date picker
datetime
Date + time
Validation Rules
required
Field is required
minLength
Min characters
maxLength
Max characters
min/max
Numeric range
pattern
Regex validation
validate
Custom function
Layout & Configuration
layout: 'stacked'
Vertical layout
layout: 'grid'
Grid layout
layout: 'inline'
Horizontal layout
columns: 2
Grid columns
submitText
Submit button text
showSubmitButton
Show/hide submit
FormConfig Options
placeholder
Input placeholder
helperText
Help description
hint
Validation hint
disabled
Disable field
readonly
Read-only field
span
Grid column span
API Methods
form.render()
Get HTML string
form.renderTo()
Render to element
form.validate()
Validate form
form.getFormData()
Get current data
form.setData()
Set form data
form.reset()
Reset form
Event Callbacks
onSubmit
Form submission
onValidationError
Validation failed
onChange
Field changed
onError
General error
onSave
Modal save event
onComplete
Wizard completion
<p class="text-muted text-sm">Complete API reference for blueprint-driven form generation</p>
<div class="reference-header">
<span data-icon="edit" class="icon"></span>
<h3>Field Types</h3>
</div>
<div class="reference-body">
<div class="reference-grid">
<div class="reference-item">
<code>string</code>
<span>Text input</span>
<button type="button" class="copy-btn" data-action="1">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>email</code>
<span>Email input</span>
<button type="button" class="copy-btn" data-action="2">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>password</code>
<span>Password input</span>
<button type="button" class="copy-btn" data-action="3">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>number</code>
<span>Number input</span>
<button type="button" class="copy-btn" data-action="4">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>boolean</code>
<span>Checkbox</span>
<button type="button" class="copy-btn" data-action="5">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>textarea</code>
<span>Multi-line text</span>
<button type="button" class="copy-btn" data-action="6">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>select</code>
<span>Dropdown</span>
<button type="button" class="copy-btn" data-action="7">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>radio</code>
<span>Radio group</span>
<button type="button" class="copy-btn" data-action="8">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>checkbox-group</code>
<span>Multi-select</span>
<button type="button" class="copy-btn" data-action="9">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>file</code>
<span>File upload</span>
<button type="button" class="copy-btn" data-action="10">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>date</code>
<span>Date picker</span>
<button type="button" class="copy-btn" data-action="11">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>datetime</code>
<span>Date + time</span>
<button type="button" class="copy-btn" data-action="12">
<span data-icon="copy"></span>
</button>
</div>
</div>
</div>
<div class="reference-header">
<span data-icon="zap" class="icon"></span>
<h3>Validation Rules</h3>
</div>
<div class="reference-body">
<div class="reference-grid">
<div class="reference-item">
<code>required</code>
<span>Field is required</span>
<button type="button" class="copy-btn" data-action="13">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>minLength</code>
<span>Min characters</span>
<button type="button" class="copy-btn" data-action="14">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>maxLength</code>
<span>Max characters</span>
<button type="button" class="copy-btn" data-action="15">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>min/max</code>
<span>Numeric range</span>
<button type="button" class="copy-btn" data-action="16">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>pattern</code>
<span>Regex validation</span>
<button type="button" class="copy-btn" data-action="17">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>validate</code>
<span>Custom function</span>
<button type="button" class="copy-btn" data-action="18">
<span data-icon="copy"></span>
</button>
</div>
</div>
</div>
<div class="reference-header">
<span data-icon="grid" class="icon"></span>
<h3>Layout & Configuration</h3>
</div>
<div class="reference-body">
<div class="reference-grid">
<div class="reference-item">
<code>layout: 'stacked'</code>
<span>Vertical layout</span>
<button type="button" class="copy-btn" data-action="19">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>layout: 'grid'</code>
<span>Grid layout</span>
<button type="button" class="copy-btn" data-action="20">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>layout: 'inline'</code>
<span>Horizontal layout</span>
<button type="button" class="copy-btn" data-action="21">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>columns: 2</code>
<span>Grid columns</span>
<button type="button" class="copy-btn" data-action="22">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>submitText</code>
<span>Submit button text</span>
<button type="button" class="copy-btn" data-action="23">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>showSubmitButton</code>
<span>Show/hide submit</span>
<button type="button" class="copy-btn" data-action="24">
<span data-icon="copy"></span>
</button>
</div>
</div>
</div>
<div class="reference-header">
<span data-icon="star" class="icon"></span>
<h3>FormConfig Options</h3>
</div>
<div class="reference-body">
<div class="reference-grid">
<div class="reference-item">
<code>placeholder</code>
<span>Input placeholder</span>
<button type="button" class="copy-btn" data-action="25">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>helperText</code>
<span>Help description</span>
<button type="button" class="copy-btn" data-action="26">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>hint</code>
<span>Validation hint</span>
<button type="button" class="copy-btn" data-action="27">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>disabled</code>
<span>Disable field</span>
<button type="button" class="copy-btn" data-action="28">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>readonly</code>
<span>Read-only field</span>
<button type="button" class="copy-btn" data-action="29">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>span</code>
<span>Grid column span</span>
<button type="button" class="copy-btn" data-action="30">
<span data-icon="copy"></span>
</button>
</div>
</div>
</div>
<div class="reference-header">
<span data-icon="box" class="icon"></span>
<h3>API Methods</h3>
</div>
<div class="reference-body">
<div class="reference-grid">
<div class="reference-item">
<code>form.render()</code>
<span>Get HTML string</span>
<button type="button" class="copy-btn" data-action="31">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>form.renderTo()</code>
<span>Render to element</span>
<button type="button" class="copy-btn" data-action="32">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>form.validate()</code>
<span>Validate form</span>
<button type="button" class="copy-btn" data-action="33">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>form.getFormData()</code>
<span>Get current data</span>
<button type="button" class="copy-btn" data-action="34">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>form.setData()</code>
<span>Set form data</span>
<button type="button" class="copy-btn" data-action="35">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>form.reset()</code>
<span>Reset form</span>
<button type="button" class="copy-btn" data-action="36">
<span data-icon="copy"></span>
</button>
</div>
</div>
</div>
<div class="reference-header">
<span data-icon="zap" class="icon"></span>
<h3>Event Callbacks</h3>
</div>
<div class="reference-body">
<div class="reference-grid">
<div class="reference-item">
<code>onSubmit</code>
<span>Form submission</span>
<button type="button" class="copy-btn" data-action="37">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>onValidationError</code>
<span>Validation failed</span>
<button type="button" class="copy-btn" data-action="38">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>onChange</code>
<span>Field changed</span>
<button type="button" class="copy-btn" data-action="39">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>onError</code>
<span>General error</span>
<button type="button" class="copy-btn" data-action="40">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>onSave</code>
<span>Modal save event</span>
<button type="button" class="copy-btn" data-action="41">
<span data-icon="copy"></span>
</button>
</div>
<div class="reference-item">
<code>onComplete</code>
<span>Wizard completion</span>
<button type="button" class="copy-btn" data-action="42">
<span data-icon="copy"></span>
</button>
</div>
</div>
</div>/**
* Domma Forms Reference - from public/showcase/forms.
*/
export function mount(root) {
// Copy functionality for reference guide
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
// Show brief success indicator
E.toast('Copied to clipboard!', {type: 'success', duration: 2000});
} catch (err) {
console.error('Failed to copy:', err);
E.toast('Failed to copy', {type: 'error', duration: 2000});
}
};
function copyCodeExample(button) {
const codeBlock = button.nextElementSibling.querySelector('code') ||
button.nextElementSibling;
const text = codeBlock.textContent;
copyToClipboard(text);
};
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
copyToClipboard('string');
});
$(root).find('[data-action="2"]').on('click', function (event) {
copyToClipboard('email');
});
$(root).find('[data-action="3"]').on('click', function (event) {
copyToClipboard('password');
});
$(root).find('[data-action="4"]').on('click', function (event) {
copyToClipboard('number');
});
$(root).find('[data-action="5"]').on('click', function (event) {
copyToClipboard('boolean');
});
$(root).find('[data-action="6"]').on('click', function (event) {
copyToClipboard('textarea');
});
$(root).find('[data-action="7"]').on('click', function (event) {
copyToClipboard('select');
});
$(root).find('[data-action="8"]').on('click', function (event) {
copyToClipboard('radio');
});
$(root).find('[data-action="9"]').on('click', function (event) {
copyToClipboard('checkbox-group');
});
$(root).find('[data-action="10"]').on('click', function (event) {
copyToClipboard('file');
});
$(root).find('[data-action="11"]').on('click', function (event) {
copyToClipboard('date');
});
$(root).find('[data-action="12"]').on('click', function (event) {
copyToClipboard('datetime');
});
$(root).find('[data-action="13"]').on('click', function (event) {
copyToClipboard('required: true');
});
$(root).find('[data-action="14"]').on('click', function (event) {
copyToClipboard('minLength: 3');
});
$(root).find('[data-action="15"]').on('click', function (event) {
copyToClipboard('maxLength: 100');
});
$(root).find('[data-action="16"]').on('click', function (event) {
copyToClipboard('min: 0, max: 100');
});
$(root).find('[data-action="17"]').on('click', function (event) {
copyToClipboard('pattern: /^[A-Za-z]+$/');
});
$(root).find('[data-action="18"]').on('click', function (event) {
copyToClipboard('validate: (value) => value !== \'test\'');
});
$(root).find('[data-action="19"]').on('click', function (event) {
copyToClipboard('layout: \'stacked\'');
});
$(root).find('[data-action="20"]').on('click', function (event) {
copyToClipboard('layout: \'grid\'');
});
$(root).find('[data-action="21"]').on('click', function (event) {
copyToClipboard('layout: \'inline\'');
});
$(root).find('[data-action="22"]').on('click', function (event) {
copyToClipboard('columns: 2');
});
$(root).find('[data-action="23"]').on('click', function (event) {
copyToClipboard('submitText: \'Save Changes\'');
});
$(root).find('[data-action="24"]').on('click', function (event) {
copyToClipboard('showSubmitButton: false');
});
$(root).find('[data-action="25"]').on('click', function (event) {
copyToClipboard('formConfig: { placeholder: \'Enter text...\' }');
});
$(root).find('[data-action="26"]').on('click', function (event) {
copyToClipboard('formConfig: { helperText: \'This helps you\' }');
});
$(root).find('[data-action="27"]').on('click', function (event) {
copyToClipboard('formConfig: { hint: \'Required field\' }');
});
$(root).find('[data-action="28"]').on('click', function (event) {
copyToClipboard('formConfig: { disabled: true }');
});
$(root).find('[data-action="29"]').on('click', function (event) {
copyToClipboard('formConfig: { readonly: true }');
});
$(root).find('[data-action="30"]').on('click', function (event) {
copyToClipboard('formConfig: { span: 2 }');
});
$(root).find('[data-action="31"]').on('click', function (event) {
copyToClipboard('const html = form.render();');
});
$(root).find('[data-action="32"]').on('click', function (event) {
copyToClipboard('form.renderTo(\'#container\');');
});
$(root).find('[data-action="33"]').on('click', function (event) {
copyToClipboard('const isValid = form.validate();');
});
$(root).find('[data-action="34"]').on('click', function (event) {
copyToClipboard('const data = form.getFormData();');
});
$(root).find('[data-action="35"]').on('click', function (event) {
copyToClipboard('form.setData(newData);');
});
$(root).find('[data-action="36"]').on('click', function (event) {
copyToClipboard('form.reset();');
});
$(root).find('[data-action="37"]').on('click', function (event) {
copyToClipboard('onSubmit: (data) => console.log(data)');
});
$(root).find('[data-action="38"]').on('click', function (event) {
copyToClipboard('onValidationError: (errors) => console.error(errors)');
});
$(root).find('[data-action="39"]').on('click', function (event) {
copyToClipboard('onChange: (data) => console.log(data)');
});
$(root).find('[data-action="40"]').on('click', function (event) {
copyToClipboard('onError: (error) => console.error(error)');
});
$(root).find('[data-action="41"]').on('click', function (event) {
copyToClipboard('onSave: async (data) => await api.save(data)');
});
$(root).find('[data-action="42"]').on('click', function (event) {
copyToClipboard('onComplete: (data) => console.log(\'Completed:\', data)');
});
}Quick Examples
<div class="reference-header"><span data-icon="document" class="icon"></span>
<h3>Quick Examples</h3>
</div>/**
* Domma Forms Reference - from public/showcase/forms.
*/
export function mount(root) {
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
copyToClipboard('string');
});
$(root).find('[data-action="2"]').on('click', function (event) {
copyToClipboard('email');
});
$(root).find('[data-action="3"]').on('click', function (event) {
copyToClipboard('password');
});
$(root).find('[data-action="4"]').on('click', function (event) {
copyToClipboard('number');
});
$(root).find('[data-action="5"]').on('click', function (event) {
copyToClipboard('boolean');
});
$(root).find('[data-action="6"]').on('click', function (event) {
copyToClipboard('textarea');
});
$(root).find('[data-action="7"]').on('click', function (event) {
copyToClipboard('select');
});
$(root).find('[data-action="8"]').on('click', function (event) {
copyToClipboard('radio');
});
$(root).find('[data-action="9"]').on('click', function (event) {
copyToClipboard('checkbox-group');
});
$(root).find('[data-action="10"]').on('click', function (event) {
copyToClipboard('file');
});
$(root).find('[data-action="11"]').on('click', function (event) {
copyToClipboard('date');
});
$(root).find('[data-action="12"]').on('click', function (event) {
copyToClipboard('datetime');
});
}Basic Form
<button class="copy-btn mb-2" data-action="1"><span data-icon="copy"></span> Copy
</button>/**
* Basic Form - from public/showcase/forms.
*/
export function mount(root) {
// Copy functionality for reference guide
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
// Show brief success indicator
E.toast('Copied to clipboard!', {type: 'success', duration: 2000});
} catch (err) {
console.error('Failed to copy:', err);
E.toast('Failed to copy', {type: 'error', duration: 2000});
}
};
function copyCodeExample(button) {
const codeBlock = button.nextElementSibling.querySelector('code') ||
button.nextElementSibling;
const text = codeBlock.textContent;
copyToClipboard(text);
};
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
copyCodeExample(this);
});
}const form = Domma.forms.create({
name: { type: 'string', required: true },
email: { type: 'email', required: true }
}, {}, {
onSubmit: (data) => console.log(data)
});
form.renderTo('#container');
Modal Form
<button class="copy-btn mb-2" data-action="1"><span data-icon="copy"></span> Copy
</button>/**
* Modal Form - from public/showcase/forms.
*/
export function mount(root) {
// Copy functionality for reference guide
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
// Show brief success indicator
E.toast('Copied to clipboard!', {type: 'success', duration: 2000});
} catch (err) {
console.error('Failed to copy:', err);
E.toast('Failed to copy', {type: 'error', duration: 2000});
}
};
function copyCodeExample(button) {
const codeBlock = button.nextElementSibling.querySelector('code') ||
button.nextElementSibling;
const text = codeBlock.textContent;
copyToClipboard(text);
};
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
copyCodeExample(this);
});
}const modal = Domma.forms.modal(blueprint, data, {
title: 'Edit User',
onSave: async (data) => await api.save(data)
});
modal.open();
Wizard
<button class="copy-btn mb-2" data-action="1"><span data-icon="copy"></span> Copy
</button>/**
* Wizard - from public/showcase/forms.
*/
export function mount(root) {
// Copy functionality for reference guide
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
// Show brief success indicator
E.toast('Copied to clipboard!', {type: 'success', duration: 2000});
} catch (err) {
console.error('Failed to copy:', err);
E.toast('Failed to copy', {type: 'error', duration: 2000});
}
};
function copyCodeExample(button) {
const codeBlock = button.nextElementSibling.querySelector('code') ||
button.nextElementSibling;
const text = codeBlock.textContent;
copyToClipboard(text);
};
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
copyCodeExample(this);
});
}const wizard = Domma.forms.wizard(steps, data, {
title: 'Setup Wizard',
onComplete: async (data) => await api.setup(data)
});
wizard.open();
CRUD Interface
<button class="copy-btn mb-2" data-action="1"><span data-icon="copy"></span> Copy
</button>/**
* CRUD Interface - from public/showcase/forms.
*/
export function mount(root) {
// Copy functionality for reference guide
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
// Show brief success indicator
E.toast('Copied to clipboard!', {type: 'success', duration: 2000});
} catch (err) {
console.error('Failed to copy:', err);
E.toast('Failed to copy', {type: 'error', duration: 2000});
}
};
function copyCodeExample(button) {
const codeBlock = button.nextElementSibling.querySelector('code') ||
button.nextElementSibling;
const text = codeBlock.textContent;
copyToClipboard(text);
};
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
copyCodeExample(this);
});
}const crud = Domma.forms.crud({
blueprint: userBlueprint,
endpoint: '/api/users',
tableSelector: '#users-table'
});
await crud.init();