mirror of https://github.com/abpframework/abp.git
9 changed files with 729 additions and 31 deletions
@ -1,6 +1,98 @@ |
|||
<div class="container mt-4"> |
|||
<h2>Dynamic Form Page</h2> |
|||
@if (formFields.length) { |
|||
<abp-dynamic-form [fields]="formFields" (onSubmit)="submit($event)" /> |
|||
} |
|||
<div class="row mb-4"> |
|||
<div class="col-12"> |
|||
<h1 class="display-4">Dynamic Form Showcase</h1> |
|||
<p class="lead text-muted"> |
|||
Comprehensive example demonstrating all available field types with validation and conditional logic. |
|||
</p> |
|||
<hr> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="row"> |
|||
<div class="col-lg-10 mx-auto"> |
|||
<div class="card shadow-sm"> |
|||
<div class="card-header bg-primary text-white"> |
|||
<h4 class="mb-0"> |
|||
<i class="fas fa-wpforms me-2"></i> |
|||
User Registration Form |
|||
</h4> |
|||
<small>All 16 field types with accessibility support</small> |
|||
</div> |
|||
<div class="card-body p-4"> |
|||
@if (formFields.length) { |
|||
<abp-dynamic-form |
|||
[fields]="formFields" |
|||
[submitButtonText]="'Register'" |
|||
[showCancelButton]="true" |
|||
(onSubmit)="submit($event)" |
|||
(formCancel)="cancel()" /> |
|||
} @else { |
|||
<div class="text-center py-5"> |
|||
<div class="spinner-border text-primary" role="status"> |
|||
<span class="visually-hidden">Loading...</span> |
|||
</div> |
|||
<p class="mt-3 text-muted">Loading form configuration...</p> |
|||
</div> |
|||
} |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Field Types Reference Card --> |
|||
<div class="card shadow-sm mt-4"> |
|||
<div class="card-header bg-info text-white"> |
|||
<h5 class="mb-0"> |
|||
<i class="fas fa-info-circle me-2"></i> |
|||
Available Field Types |
|||
</h5> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div class="row"> |
|||
<div class="col-md-6"> |
|||
<h6 class="text-primary">Text Inputs</h6> |
|||
<ul class="list-unstyled"> |
|||
<li><i class="fas fa-check text-success"></i> Text</li> |
|||
<li><i class="fas fa-check text-success"></i> Email</li> |
|||
<li><i class="fas fa-check text-success"></i> Password</li> |
|||
<li><i class="fas fa-check text-success"></i> Tel</li> |
|||
<li><i class="fas fa-check text-success"></i> URL</li> |
|||
<li><i class="fas fa-check text-success"></i> Textarea</li> |
|||
</ul> |
|||
</div> |
|||
<div class="col-md-6"> |
|||
<h6 class="text-primary">Special Inputs</h6> |
|||
<ul class="list-unstyled"> |
|||
<li><i class="fas fa-check text-success"></i> Number</li> |
|||
<li><i class="fas fa-check text-success"></i> Date</li> |
|||
<li><i class="fas fa-check text-success"></i> DateTime-Local</li> |
|||
<li><i class="fas fa-check text-success"></i> Time</li> |
|||
<li><i class="fas fa-check text-success"></i> Range</li> |
|||
<li><i class="fas fa-check text-success"></i> Color</li> |
|||
</ul> |
|||
</div> |
|||
<div class="col-md-6"> |
|||
<h6 class="text-primary">Selection</h6> |
|||
<ul class="list-unstyled"> |
|||
<li><i class="fas fa-check text-success"></i> Select (Dropdown)</li> |
|||
<li><i class="fas fa-check text-success"></i> Radio</li> |
|||
<li><i class="fas fa-check text-success"></i> Checkbox</li> |
|||
</ul> |
|||
</div> |
|||
<div class="col-md-6"> |
|||
<h6 class="text-primary">Files</h6> |
|||
<ul class="list-unstyled"> |
|||
<li><i class="fas fa-check text-success"></i> File (Single/Multiple)</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
<hr> |
|||
<div class="alert alert-info mb-0"> |
|||
<i class="fas fa-lightbulb me-2"></i> |
|||
<strong>Features:</strong> Full validation support, conditional logic, |
|||
grid-based layout, ARIA accessibility, keyboard navigation, and screen reader support. |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -1,15 +1,244 @@ |
|||
import { Injectable, inject } from '@angular/core'; |
|||
import { HttpClient } from '@angular/common/http'; |
|||
import { Observable } from 'rxjs'; |
|||
import { Injectable } from '@angular/core'; |
|||
import { FormFieldConfig } from '@abp/ng.components/dynamic-form'; |
|||
import { Observable, of } from 'rxjs'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root', |
|||
}) |
|||
export class FormConfigService { |
|||
protected readonly http = inject(HttpClient); |
|||
|
|||
getFormConfig(): Observable<FormFieldConfig[]> { |
|||
return this.http.get<FormFieldConfig[]>('/assets/form-config.json'); |
|||
const formConfig: FormFieldConfig[] = [ |
|||
// Section 1: Basic Text Inputs
|
|||
{ |
|||
key: 'firstName', |
|||
label: 'First Name', |
|||
type: 'text', |
|||
placeholder: 'Enter your first name', |
|||
required: true, |
|||
gridSize: 6, |
|||
order: 1, |
|||
validators: [{ type: 'required', message: 'First name is required' }], |
|||
}, |
|||
{ |
|||
key: 'lastName', |
|||
label: 'Last Name', |
|||
type: 'text', |
|||
placeholder: 'Enter your last name', |
|||
required: true, |
|||
gridSize: 6, |
|||
order: 2, |
|||
validators: [{ type: 'required', message: 'Last name is required' }], |
|||
}, |
|||
|
|||
// Section 2: Email & Password
|
|||
{ |
|||
key: 'email', |
|||
label: 'Email Address', |
|||
type: 'email', |
|||
placeholder: 'example@domain.com', |
|||
required: true, |
|||
gridSize: 6, |
|||
order: 3, |
|||
validators: [ |
|||
{ type: 'required', message: 'Email is required' }, |
|||
{ type: 'email', message: 'Invalid email address' }, |
|||
], |
|||
}, |
|||
{ |
|||
key: 'password', |
|||
label: 'Password', |
|||
type: 'password', |
|||
placeholder: 'Enter a strong password', |
|||
required: true, |
|||
gridSize: 6, |
|||
order: 4, |
|||
minLength: 8, |
|||
maxLength: 50, |
|||
validators: [ |
|||
{ type: 'required', message: 'Password is required' }, |
|||
{ type: 'minLength', value: 8, message: 'Password must be at least 8 characters' }, |
|||
], |
|||
}, |
|||
|
|||
// Section 3: Contact Information
|
|||
{ |
|||
key: 'phone', |
|||
label: 'Phone Number', |
|||
type: 'tel', |
|||
placeholder: '555-123-4567', |
|||
gridSize: 6, |
|||
order: 5, |
|||
pattern: '[0-9]{3}-[0-9]{3}-[0-9]{4}', |
|||
}, |
|||
{ |
|||
key: 'website', |
|||
label: 'Website', |
|||
type: 'url', |
|||
placeholder: 'https://example.com', |
|||
gridSize: 6, |
|||
order: 6, |
|||
}, |
|||
|
|||
// Section 4: Numbers & Dates
|
|||
{ |
|||
key: 'age', |
|||
label: 'Age', |
|||
type: 'number', |
|||
placeholder: 'Enter your age', |
|||
required: true, |
|||
gridSize: 4, |
|||
order: 7, |
|||
min: 18, |
|||
max: 100, |
|||
validators: [ |
|||
{ type: 'required', message: 'Age is required' }, |
|||
{ type: 'min', value: 18, message: 'You must be at least 18 years old' }, |
|||
], |
|||
}, |
|||
{ |
|||
key: 'birthdate', |
|||
label: 'Birth Date', |
|||
type: 'date', |
|||
required: true, |
|||
gridSize: 4, |
|||
order: 8, |
|||
max: new Date().toISOString().split('T')[0], |
|||
validators: [{ type: 'required', message: 'Birth date is required' }], |
|||
}, |
|||
{ |
|||
key: 'appointmentTime', |
|||
label: 'Appointment Date & Time', |
|||
type: 'datetime-local', |
|||
gridSize: 4, |
|||
order: 9, |
|||
min: new Date().toISOString().slice(0, 16), |
|||
}, |
|||
|
|||
// Section 5: Select & Radio
|
|||
{ |
|||
key: 'country', |
|||
label: 'Country', |
|||
type: 'select', |
|||
required: true, |
|||
gridSize: 6, |
|||
order: 10, |
|||
options: { |
|||
defaultValues: [ |
|||
{ key: 'usa', value: 'United States' }, |
|||
{ key: 'uk', value: 'United Kingdom' }, |
|||
{ key: 'canada', value: 'Canada' }, |
|||
{ key: 'germany', value: 'Germany' }, |
|||
{ key: 'france', value: 'France' }, |
|||
], |
|||
}, |
|||
validators: [{ type: 'required', message: 'Country is required' }], |
|||
}, |
|||
{ |
|||
key: 'gender', |
|||
label: 'Gender', |
|||
type: 'radio', |
|||
required: true, |
|||
gridSize: 6, |
|||
order: 11, |
|||
options: { |
|||
defaultValues: [ |
|||
{ key: 'male', value: 'Male' }, |
|||
{ key: 'female', value: 'Female' }, |
|||
{ key: 'other', value: 'Other' }, |
|||
{ key: 'prefer-not-to-say', value: 'Prefer not to say' }, |
|||
], |
|||
}, |
|||
validators: [{ type: 'required', message: 'Gender is required' }], |
|||
}, |
|||
|
|||
// Section 6: Conditional Field (shown when country is USA)
|
|||
{ |
|||
key: 'state', |
|||
label: 'State (USA Only)', |
|||
type: 'select', |
|||
gridSize: 6, |
|||
order: 12, |
|||
options: { |
|||
defaultValues: [ |
|||
{ key: 'ca', value: 'California' }, |
|||
{ key: 'ny', value: 'New York' }, |
|||
{ key: 'tx', value: 'Texas' }, |
|||
{ key: 'fl', value: 'Florida' }, |
|||
], |
|||
}, |
|||
conditionalLogic: [ |
|||
{ dependsOn: 'country', condition: 'equals', value: 'usa', action: 'show' }, |
|||
], |
|||
}, |
|||
|
|||
// Section 7: Time & Range
|
|||
{ |
|||
key: 'preferredTime', |
|||
label: 'Preferred Contact Time', |
|||
type: 'time', |
|||
gridSize: 6, |
|||
order: 13, |
|||
step: '900', // 15 minutes
|
|||
}, |
|||
{ |
|||
key: 'experienceLevel', |
|||
label: 'Experience Level (0-10)', |
|||
type: 'range', |
|||
gridSize: 6, |
|||
order: 14, |
|||
min: 0, |
|||
max: 10, |
|||
step: 1, |
|||
value: 5, |
|||
}, |
|||
|
|||
// Section 8: Color & File
|
|||
{ |
|||
key: 'favoriteColor', |
|||
label: 'Favorite Color', |
|||
type: 'color', |
|||
gridSize: 6, |
|||
order: 15, |
|||
value: '#007bff', |
|||
}, |
|||
{ |
|||
key: 'profilePicture', |
|||
label: 'Profile Picture', |
|||
type: 'file', |
|||
gridSize: 6, |
|||
order: 16, |
|||
accept: 'image/*', |
|||
multiple: false, |
|||
}, |
|||
|
|||
// Section 9: Textarea & Checkbox
|
|||
{ |
|||
key: 'bio', |
|||
label: 'Biography', |
|||
type: 'textarea', |
|||
placeholder: 'Tell us about yourself...', |
|||
gridSize: 12, |
|||
order: 17, |
|||
maxLength: 500, |
|||
}, |
|||
{ |
|||
key: 'newsletter', |
|||
label: 'Subscribe to newsletter', |
|||
type: 'checkbox', |
|||
gridSize: 6, |
|||
order: 18, |
|||
}, |
|||
{ |
|||
key: 'terms', |
|||
label: 'I agree to the terms and conditions', |
|||
type: 'checkbox', |
|||
required: true, |
|||
gridSize: 6, |
|||
order: 19, |
|||
validators: [{ type: 'requiredTrue', message: 'You must agree to the terms' }], |
|||
}, |
|||
]; |
|||
|
|||
return of(formConfig); |
|||
} |
|||
} |
|||
|
|||
@ -1,4 +1,12 @@ |
|||
// Minimal styling - rely on Bootstrap/Lepton-X theme styles |
|||
.form-group { |
|||
display: flex; |
|||
flex-direction: column; |
|||
|
|||
// Radio group spacing (layout only) |
|||
.radio-group { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 0.5rem; |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue