@bcweed/dynamic-form-lib 0.0.4 → 0.0.5
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 +19 -15
- package/fesm2022/bcweed-dynamic-form-lib.mjs +948 -0
- package/fesm2022/bcweed-dynamic-form-lib.mjs.map +1 -0
- package/index.d.ts +257 -0
- package/package.json +37 -65
- package/.editorconfig +0 -17
- package/.postcssrc.json +0 -5
- package/.vscode/extensions.json +0 -4
- package/.vscode/launch.json +0 -20
- package/.vscode/tasks.json +0 -42
- package/AngularDynamicFormLibrary.esproj +0 -14
- package/CHANGELOG.md +0 -16
- package/angular.json +0 -169
- package/aspnetcore-https.js +0 -37
- package/karma.conf.js +0 -44
- package/obj/Debug/AngularDynamicFormLibrary.esproj.CoreCompileInputs.cache +0 -0
- package/obj/Debug/AngularDynamicFormLibrary.esproj.FileListAbsolute.txt +0 -1
- package/obj/Debug/package.g.props +0 -56
- package/postcss.config.js +0 -6
- package/projects/dynamic-form-lib/.postcssrc.json +0 -5
- package/projects/dynamic-form-lib/README.md +0 -63
- package/projects/dynamic-form-lib/ng-package.json +0 -10
- package/projects/dynamic-form-lib/package.json +0 -27
- package/projects/dynamic-form-lib/src/lib/README.md +0 -424
- package/projects/dynamic-form-lib/src/lib/SUMMARY.md +0 -159
- package/projects/dynamic-form-lib/src/lib/directives/dynamic-ref-directive.ts +0 -13
- package/projects/dynamic-form-lib/src/lib/dynamic-form-demo/dynamic-form-demo.component.html +0 -75
- package/projects/dynamic-form-lib/src/lib/dynamic-form-demo/dynamic-form-demo.component.scss +0 -127
- package/projects/dynamic-form-lib/src/lib/dynamic-form-demo/dynamic-form-demo.component.ts +0 -50
- package/projects/dynamic-form-lib/src/lib/dynamic-form-field/dynamic-form-field.component.html +0 -234
- package/projects/dynamic-form-lib/src/lib/dynamic-form-field/dynamic-form-field.component.scss +0 -315
- package/projects/dynamic-form-lib/src/lib/dynamic-form-field/dynamic-form-field.component.ts +0 -233
- package/projects/dynamic-form-lib/src/lib/dynamic-form.component.html +0 -137
- package/projects/dynamic-form-lib/src/lib/dynamic-form.component.scss +0 -141
- package/projects/dynamic-form-lib/src/lib/dynamic-form.component.ts +0 -366
- package/projects/dynamic-form-lib/src/lib/helpers/component-helper.ts +0 -54
- package/projects/dynamic-form-lib/src/lib/index.ts +0 -10
- package/projects/dynamic-form-lib/src/lib/models/dynamic-form-interfaces.ts +0 -117
- package/projects/dynamic-form-lib/src/lib/services/dynamic-form.service.spec.ts +0 -302
- package/projects/dynamic-form-lib/src/lib/services/dynamic-form.service.ts +0 -325
- package/projects/dynamic-form-lib/src/lib/style.scss +0 -3
- package/projects/dynamic-form-lib/src/lib/tailwind-output.css +0 -0
- package/projects/dynamic-form-lib/src/lib/validators/compare-validator.ts +0 -26
- package/projects/dynamic-form-lib/src/public-api.css +0 -1
- package/projects/dynamic-form-lib/src/public-api.ts +0 -10
- package/projects/dynamic-form-lib/tailwind.config.js +0 -10
- package/projects/dynamic-form-lib/tsconfig.lib.json +0 -15
- package/projects/dynamic-form-lib/tsconfig.lib.prod.json +0 -11
- package/projects/dynamic-form-lib/tsconfig.spec.json +0 -15
- package/public/favicon.ico +0 -0
- package/src/app/app-routing.module.ts +0 -10
- package/src/app/app.component.css +0 -0
- package/src/app/app.component.html +0 -18
- package/src/app/app.component.spec.ts +0 -35
- package/src/app/app.component.ts +0 -1038
- package/src/app/app.config.ts +0 -45
- package/src/app/app.module.ts +0 -46
- package/src/app/app.routes.ts +0 -8
- package/src/index.html +0 -13
- package/src/main.ts +0 -16
- package/src/styles.css +0 -2
- package/tailwind.config.js +0 -10
- package/tsconfig.app.json +0 -15
- package/tsconfig.json +0 -32
- package/tsconfig.spec.json +0 -15
- /package/{projects/dynamic-form-lib/src → src}/lib/style.css +0 -0
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
.dynamic-form-container {
|
|
2
|
-
width: 100%;
|
|
3
|
-
|
|
4
|
-
p-message .p-message.light-font .p-message-text {
|
|
5
|
-
font-weight: normal;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.form-description {
|
|
9
|
-
color: #666;
|
|
10
|
-
margin-bottom: 1rem;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.form-fields-container {
|
|
14
|
-
margin-bottom: 1rem;
|
|
15
|
-
|
|
16
|
-
.row {
|
|
17
|
-
margin-bottom: 0.5rem;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.form-actions {
|
|
22
|
-
margin-top: 2rem;
|
|
23
|
-
padding-top: 1rem;
|
|
24
|
-
border-top: 1px solid #dee2e6;
|
|
25
|
-
|
|
26
|
-
button {
|
|
27
|
-
margin-left: 0.5rem;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.btn-cancel {
|
|
31
|
-
background-color: #6c757d;
|
|
32
|
-
border-color: #6c757d;
|
|
33
|
-
|
|
34
|
-
&:hover {
|
|
35
|
-
background-color: #5a6268;
|
|
36
|
-
border-color: #545b62;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
// New styles for converted Tailwind classes
|
|
41
|
-
.section-wrapper {
|
|
42
|
-
width: 100%;
|
|
43
|
-
display: flex;
|
|
44
|
-
justify-content: center;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.section-content {
|
|
48
|
-
width: 100%;
|
|
49
|
-
display: flex;
|
|
50
|
-
flex-wrap: wrap;
|
|
51
|
-
gap: 1rem;
|
|
52
|
-
justify-content: center;
|
|
53
|
-
padding-top: 16px;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.section-title {
|
|
57
|
-
width: 100%;
|
|
58
|
-
text-align:left;
|
|
59
|
-
font-size:24px;
|
|
60
|
-
font-weight:600;
|
|
61
|
-
margin-top:8px;
|
|
62
|
-
margin-bottom:16px;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.message-container {
|
|
66
|
-
width: 100%;
|
|
67
|
-
display: block;
|
|
68
|
-
margin-top: 0.5rem;
|
|
69
|
-
margin-bottom: 0.5rem;
|
|
70
|
-
text-align: left;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.fields-wrapper {
|
|
74
|
-
width: 100%;
|
|
75
|
-
display: flex;
|
|
76
|
-
justify-content: center;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.fields-grid {
|
|
80
|
-
width: 100%;
|
|
81
|
-
display: flex;
|
|
82
|
-
flex-wrap: wrap;
|
|
83
|
-
gap: 1rem;
|
|
84
|
-
justify-content: left;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.field-container {
|
|
88
|
-
width: 100%;
|
|
89
|
-
text-align: left;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.field-container-2col {
|
|
93
|
-
width: 100%;
|
|
94
|
-
text-align: left;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.field-container-1col {
|
|
98
|
-
width: 100%;
|
|
99
|
-
text-align: left;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.button-container {
|
|
103
|
-
width: 100%;
|
|
104
|
-
display: flex;
|
|
105
|
-
justify-content: flex-end;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.button-group {
|
|
109
|
-
display: flex;
|
|
110
|
-
gap: 0.5rem;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Responsive adjustments
|
|
115
|
-
@media (min-width: 1024px) {
|
|
116
|
-
.dynamic-form-container {
|
|
117
|
-
|
|
118
|
-
.field-container {
|
|
119
|
-
width: calc(50% - 0.5rem);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.field-container-2col {
|
|
123
|
-
width: calc(50% - 0.5rem);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.field-container-1col {
|
|
127
|
-
width: 100%;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
@media (max-width: 768px) {
|
|
133
|
-
.dynamic-form-container {
|
|
134
|
-
.form-actions {
|
|
135
|
-
button {
|
|
136
|
-
width: 100%;
|
|
137
|
-
margin: 0.25rem 0;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ViewChildren, QueryList } from '@angular/core';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
4
|
-
import { Subject } from 'rxjs';
|
|
5
|
-
import { takeUntil } from 'rxjs/operators';
|
|
6
|
-
import { DynamicFormConfig, DynamicFormSection, DynamicFormState, DynamicFormValue, DynamicFormError, DynamicFormField, DynamicFormFrameGroup } from './models/dynamic-form-interfaces';
|
|
7
|
-
import { DynamicFormFieldComponent } from './dynamic-form-field/dynamic-form-field.component';
|
|
8
|
-
import { DynamicFormService } from './services/dynamic-form.service';
|
|
9
|
-
import { ButtonModule } from 'primeng/button';
|
|
10
|
-
import { inject } from '@angular/core';
|
|
11
|
-
import { MessageModule } from 'primeng/message';
|
|
12
|
-
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
|
13
|
-
|
|
14
|
-
@Component({
|
|
15
|
-
selector: 'app-dynamic-form',
|
|
16
|
-
standalone: true,
|
|
17
|
-
imports: [CommonModule, ReactiveFormsModule, DynamicFormFieldComponent, ButtonModule, MessageModule],
|
|
18
|
-
templateUrl: './dynamic-form.component.html',
|
|
19
|
-
styleUrls: ['./dynamic-form.component.scss']
|
|
20
|
-
})
|
|
21
|
-
export class DynamicFormComponent implements OnInit, OnDestroy {
|
|
22
|
-
// Inject dependencies using field initializers (this IS the injection context)
|
|
23
|
-
private fb = inject(FormBuilder);
|
|
24
|
-
private dynamicFormService = inject(DynamicFormService);
|
|
25
|
-
|
|
26
|
-
@Input() config!: DynamicFormConfig;
|
|
27
|
-
@Input() initialValues?: DynamicFormValue;
|
|
28
|
-
@Input() allowHelpOverlay = false;
|
|
29
|
-
@Input() allowHelpSideBar = false;
|
|
30
|
-
|
|
31
|
-
@Output() formSubmit = new EventEmitter<DynamicFormValue>();
|
|
32
|
-
@Output() formCancel = new EventEmitter<void>();
|
|
33
|
-
@Output() formStateChange = new EventEmitter<DynamicFormState>();
|
|
34
|
-
@ViewChildren(DynamicFormFieldComponent) fieldComponents!: QueryList<DynamicFormFieldComponent>;
|
|
35
|
-
|
|
36
|
-
componentMap!: Map<string, DynamicFormFieldComponent>;
|
|
37
|
-
fieldMap!: Map<string, DynamicFormField>;
|
|
38
|
-
form!: FormGroup;
|
|
39
|
-
formState!: DynamicFormState;
|
|
40
|
-
sortedFields: DynamicFormField[] = [];
|
|
41
|
-
sortedSections: DynamicFormSection[] = [];
|
|
42
|
-
sortedFrames: DynamicFormFrameGroup[] = [];
|
|
43
|
-
private destroy$ = new Subject<void>();
|
|
44
|
-
isExpanded = false;
|
|
45
|
-
isSmallMedia = false;
|
|
46
|
-
isMediumMedia = false;
|
|
47
|
-
|
|
48
|
-
// Empty constructor - no code needed here
|
|
49
|
-
constructor(private responsive: BreakpointObserver) {}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
ngOnInit(): void {
|
|
54
|
-
this.responsive.observe([Breakpoints.Medium, Breakpoints.Small, Breakpoints.XSmall])
|
|
55
|
-
.subscribe(result => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (result.matches) {
|
|
59
|
-
this.isSmallMedia = true;
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
this.isSmallMedia = false;
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
this.initializeForm();
|
|
69
|
-
this.subscribeToFormChanges();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
ngOnDestroy(): void {
|
|
73
|
-
this.destroy$.next();
|
|
74
|
-
this.destroy$.complete();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
ngAfterViewInit() {
|
|
78
|
-
this.componentMap = new Map(
|
|
79
|
-
this.fieldComponents.map(comp => [comp.field.id, comp])
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private initializeForm(): void {
|
|
84
|
-
|
|
85
|
-
//sort frames
|
|
86
|
-
// Sort sections and fields by order
|
|
87
|
-
this.sortedFrames = [...this.config.frames].sort((a, b) =>
|
|
88
|
-
(a.order ?? 0) - (b.order ?? 0)
|
|
89
|
-
);
|
|
90
|
-
this.sortedFrames.forEach((frame) => {
|
|
91
|
-
frame.sections = [...frame.sections].sort((a, b) =>
|
|
92
|
-
(a.order ?? 0) - (b.order ?? 0)
|
|
93
|
-
);
|
|
94
|
-
frame.sections.forEach((section) => {
|
|
95
|
-
section.fields = [...section.fields].sort((a, b) =>
|
|
96
|
-
(a.order ?? 0) - (b.order ?? 0)
|
|
97
|
-
);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// Build field map for O(1) lookup
|
|
103
|
-
this.fieldMap = new Map();
|
|
104
|
-
this.sortedFrames.forEach(frame => {
|
|
105
|
-
frame.sections.forEach(section => {
|
|
106
|
-
section.fields.forEach(field => {
|
|
107
|
-
this.fieldMap.set(field.id, field);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Build form controls
|
|
113
|
-
const group: { [key: string]: any } = {};
|
|
114
|
-
|
|
115
|
-
this.sortedFrames.forEach(frame => {
|
|
116
|
-
frame.sections.forEach(section => {
|
|
117
|
-
section.fields.forEach(field => {
|
|
118
|
-
const validators = field.disabled === true ? [] : this.dynamicFormService.getValidators(field);
|
|
119
|
-
const initialValue = this.getInitialValue(field);
|
|
120
|
-
|
|
121
|
-
group[field.id] = [
|
|
122
|
-
{ value: initialValue, disabled: field.disabled ?? false },
|
|
123
|
-
validators
|
|
124
|
-
];
|
|
125
|
-
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
this.form = this.fb.group(group);
|
|
131
|
-
|
|
132
|
-
// Trigger all click events on form controls
|
|
133
|
-
this.triggerAllClickEvents();
|
|
134
|
-
|
|
135
|
-
// Initialize form state
|
|
136
|
-
this.formState = {
|
|
137
|
-
values: this.form.value,
|
|
138
|
-
errors: [],
|
|
139
|
-
touched: {},
|
|
140
|
-
isSubmitting: false,
|
|
141
|
-
isValid: this.form.valid
|
|
142
|
-
};
|
|
143
|
-
if (this.form.valid) {
|
|
144
|
-
this.markAllAsTouched();
|
|
145
|
-
}
|
|
146
|
-
this.emitFormState();
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
private triggerAllClickEvents(): void {
|
|
151
|
-
this.sortedFrames.forEach(frame => {
|
|
152
|
-
frame.sections.forEach(section => {
|
|
153
|
-
section.fields.forEach(field => {
|
|
154
|
-
const control = this.form.get(field.id);
|
|
155
|
-
if (control && field.onClick) {
|
|
156
|
-
// Trigger the onClick event with current control value
|
|
157
|
-
field.onClick(control.value, this.form);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
private getInitialValue(field: DynamicFormField): any {
|
|
165
|
-
if (this.initialValues && this.initialValues[field.id] !== undefined) {
|
|
166
|
-
//return this.initialValues[field.id];
|
|
167
|
-
if (field.type == "date") {
|
|
168
|
-
if (this.initialValues[field.id] instanceof Date && !isNaN(this.initialValues[field.id].getTime())) {
|
|
169
|
-
console.log(field.id + " is already a valid Date object");
|
|
170
|
-
return this.initialValues[field.id];
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
console.log(field.id + " is not a valid Date object, attempting to parse");
|
|
174
|
-
return new Date(this.initialValues[field.id]);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
return this.initialValues[field.id];
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return field.value ?? this.getDefaultValue(field.type);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
private getDefaultValue(type: string): any {
|
|
185
|
-
switch (type) {
|
|
186
|
-
case 'checkbox':
|
|
187
|
-
return null;
|
|
188
|
-
case 'number':
|
|
189
|
-
return null;
|
|
190
|
-
case 'hidden':
|
|
191
|
-
return 0;
|
|
192
|
-
default:
|
|
193
|
-
return '';
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private subscribeToFormChanges(): void {
|
|
198
|
-
this.form.valueChanges
|
|
199
|
-
.pipe(takeUntil(this.destroy$))
|
|
200
|
-
.subscribe(values => {
|
|
201
|
-
this.updateFormState();
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
this.form.statusChanges
|
|
205
|
-
.pipe(takeUntil(this.destroy$))
|
|
206
|
-
.subscribe(() => {
|
|
207
|
-
this.updateFormState();
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
private updateFormState(): void {
|
|
212
|
-
const touched: { [key: string]: boolean } = {};
|
|
213
|
-
const errors: DynamicFormError[] = [];
|
|
214
|
-
|
|
215
|
-
this.fieldMap.forEach((field, fieldId) => {
|
|
216
|
-
const control = this.form.get(fieldId);
|
|
217
|
-
if (control) {
|
|
218
|
-
touched[fieldId] = control.touched;
|
|
219
|
-
|
|
220
|
-
if (control.invalid && control.touched) {
|
|
221
|
-
const fieldErrors = this.dynamicFormService.getErrorMessages(field, control.errors);
|
|
222
|
-
errors.push(...fieldErrors);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
this.formState = {
|
|
228
|
-
values: this.form.value,
|
|
229
|
-
errors,
|
|
230
|
-
touched,
|
|
231
|
-
isSubmitting: this.formState.isSubmitting,
|
|
232
|
-
isValid: this.form.valid
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
this.emitFormState();
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private emitFormState(): void {
|
|
239
|
-
this.formStateChange.emit({ ...this.formState });
|
|
240
|
-
}
|
|
241
|
-
showCancelButton(): boolean {
|
|
242
|
-
if (!this.config.hideCancelButton) {
|
|
243
|
-
return true;
|
|
244
|
-
}
|
|
245
|
-
return !this.config.hideCancelButton();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
showSubmitButton(): boolean {
|
|
249
|
-
if (!this.config.hideSubmitButton) {
|
|
250
|
-
return true;
|
|
251
|
-
}
|
|
252
|
-
return !this.config.hideSubmitButton();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
isFieldVisible(field: DynamicFormField): boolean {
|
|
256
|
-
if (!field.conditionalDisplay) {
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
return field.conditionalDisplay(this.form.value);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
isSectionVisible(section: DynamicFormSection): boolean {
|
|
263
|
-
if (!section.conditionalDisplay) {
|
|
264
|
-
return true;
|
|
265
|
-
}
|
|
266
|
-
return section.conditionalDisplay(this.form.value);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
isFrameVisible(frame: DynamicFormFrameGroup): boolean {
|
|
270
|
-
if (!frame.conditionalDisplay) {
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
return frame.conditionalDisplay(this.form.value);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
onSubmit(): void {
|
|
277
|
-
if (this.form.invalid) {
|
|
278
|
-
this.markAllAsTouched();
|
|
279
|
-
this.updateFormState();
|
|
280
|
-
for (const frame of this.sortedFrames) {
|
|
281
|
-
for (const section of frame.sections) {
|
|
282
|
-
for (const field of section.fields) {
|
|
283
|
-
const control = this.form.get(field.id);
|
|
284
|
-
if (control && !control.valid) {
|
|
285
|
-
const component = this.componentMap.get(field.id);
|
|
286
|
-
if (component) {
|
|
287
|
-
component.focusOn();
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
this.formState.isSubmitting = true;
|
|
298
|
-
this.emitFormState();
|
|
299
|
-
|
|
300
|
-
const formValue = this.form.value;
|
|
301
|
-
this.formSubmit.emit(formValue);
|
|
302
|
-
|
|
303
|
-
// Reset submitting state after emission
|
|
304
|
-
setTimeout(() => {
|
|
305
|
-
this.formState.isSubmitting = false;
|
|
306
|
-
this.emitFormState();
|
|
307
|
-
}, 0);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
onCancel(): void {
|
|
311
|
-
this.formCancel.emit();
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private markAllAsTouched(): void {
|
|
315
|
-
Object.keys(this.form.controls).forEach(key => {
|
|
316
|
-
const control = this.form.get(key);
|
|
317
|
-
control?.markAsTouched();
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
getFieldErrors(fieldId: string): string[] {
|
|
322
|
-
const control = this.form.get(fieldId);
|
|
323
|
-
if (!control || !control.errors || !control.touched) {
|
|
324
|
-
return [];
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const field = this.fieldMap.get(fieldId);
|
|
328
|
-
if (!field) {
|
|
329
|
-
return [];
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return this.dynamicFormService.getErrorMessages(field, control.errors).map(e => e.message);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
reset(): void {
|
|
336
|
-
this.form.reset();
|
|
337
|
-
this.formState = {
|
|
338
|
-
values: this.form.value,
|
|
339
|
-
errors: [],
|
|
340
|
-
touched: {},
|
|
341
|
-
isSubmitting: false,
|
|
342
|
-
isValid: this.form.valid
|
|
343
|
-
};
|
|
344
|
-
this.emitFormState();
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
patchValues(values: DynamicFormValue): void {
|
|
348
|
-
this.form.patchValue(values);
|
|
349
|
-
if (this.form.valid) {
|
|
350
|
-
this.markAllAsTouched();
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
getFormValue(): DynamicFormValue {
|
|
356
|
-
return this.form.value;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
get submitLabel(): string {
|
|
360
|
-
return this.config.submitLabel ?? 'Submit';
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
get cancelLabel(): string {
|
|
364
|
-
return this.config.cancelLabel ?? 'Cancel';
|
|
365
|
-
}
|
|
366
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { ElementRef, Injector, Type } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
export function getComponentFromElement<T>(
|
|
4
|
-
el: ElementRef,
|
|
5
|
-
injector: Injector,
|
|
6
|
-
componentType: Type<T>
|
|
7
|
-
): T | null {
|
|
8
|
-
try {
|
|
9
|
-
// Get the native element
|
|
10
|
-
const nativeElement = el.nativeElement;
|
|
11
|
-
|
|
12
|
-
// Access Angular's internal context from the element
|
|
13
|
-
const context = (nativeElement as any).__ngContext__;
|
|
14
|
-
|
|
15
|
-
if (!context) {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// The context is an array or number that references the LView
|
|
20
|
-
// We need to traverse up to find the component instance
|
|
21
|
-
let lView: any;
|
|
22
|
-
if (typeof context === 'number') {
|
|
23
|
-
// Context is an index into the LView array
|
|
24
|
-
lView = (nativeElement as any).__ngContext__;
|
|
25
|
-
} else if (Array.isArray(context)) {
|
|
26
|
-
lView = context;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Try to find the parent component by traversing the DOM tree
|
|
30
|
-
let currentElement = nativeElement.parentElement;
|
|
31
|
-
while (currentElement) {
|
|
32
|
-
const elementContext = (currentElement as any).__ngContext__;
|
|
33
|
-
if (elementContext) {
|
|
34
|
-
// Check if this element has a component instance
|
|
35
|
-
const lViewArray = Array.isArray(elementContext) ? elementContext : null;
|
|
36
|
-
if (lViewArray) {
|
|
37
|
-
// LView[8] typically contains the component instance
|
|
38
|
-
for (let i = 8; i < lViewArray.length; i++) {
|
|
39
|
-
const item = lViewArray[i];
|
|
40
|
-
if (item && item instanceof componentType) {
|
|
41
|
-
return item as T;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
currentElement = currentElement.parentElement;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return null;
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error('Error getting component from element:', error);
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// Components
|
|
2
|
-
export * from './dynamic-form.component';
|
|
3
|
-
export * from './dynamic-form-field/dynamic-form-field.component';
|
|
4
|
-
export * from './dynamic-form-demo/dynamic-form-demo.component';
|
|
5
|
-
|
|
6
|
-
// Services
|
|
7
|
-
export * from './services/dynamic-form.service';
|
|
8
|
-
|
|
9
|
-
// Re-export models for convenience
|
|
10
|
-
export * from './models/dynamic-form-interfaces';
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { FormGroup } from '@angular/forms';
|
|
2
|
-
export interface DynamicFormField {
|
|
3
|
-
id: string;
|
|
4
|
-
label: string;
|
|
5
|
-
superTitle?: string; //????
|
|
6
|
-
type: 'text' | 'number' | 'email' | 'select' | 'checkbox' | 'radio' | 'multi-checkbox' | 'multi-radio' | 'date' | 'textarea' | 'hidden' | 'phone';
|
|
7
|
-
value?: any;
|
|
8
|
-
decimals?: number;
|
|
9
|
-
placeholder?: string; //normal placeholder for input
|
|
10
|
-
popInfo?: string; //drawer or popover info for the field
|
|
11
|
-
leftPreInput?: string; // left side mid aligned pre phrase for input
|
|
12
|
-
helperInfo?: string; //????
|
|
13
|
-
hint?: string; // hint message shown below the field
|
|
14
|
-
toolTip?: string; // tooltip for the field
|
|
15
|
-
postWarning?: string; // warning message shown after the field
|
|
16
|
-
postInfo?: string; // info message shown after the field
|
|
17
|
-
required?: boolean; // for basic required validation
|
|
18
|
-
disabled?: boolean; // for disabling the field
|
|
19
|
-
options?: DynamicFormOption[];
|
|
20
|
-
validations?: DynamicFormValidation[];
|
|
21
|
-
order?: number;
|
|
22
|
-
cssClass?: string;
|
|
23
|
-
dependsOn?: string;
|
|
24
|
-
fieldWidth?: string;
|
|
25
|
-
inputWidth?: string;
|
|
26
|
-
labelWidth?: string;
|
|
27
|
-
min?: number;
|
|
28
|
-
max?: number;
|
|
29
|
-
conditionalDisplay?: (formValues: any) => boolean;
|
|
30
|
-
conditionalDisabled?: () => boolean;
|
|
31
|
-
onChange?: (value: any, form: FormGroup) => void;
|
|
32
|
-
onClick?: (value: any, form: FormGroup) => void;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface DynamicFormFrameGroup {
|
|
36
|
-
sections: DynamicFormSection[];
|
|
37
|
-
cssClass?: string;
|
|
38
|
-
innerFrameCssClass?: string;
|
|
39
|
-
titleContainerCssClass?: string;
|
|
40
|
-
titlePrefixCssClass?: string;
|
|
41
|
-
titleCssClass?: string;
|
|
42
|
-
id: string;
|
|
43
|
-
title?: string;
|
|
44
|
-
titleIcon?: string;
|
|
45
|
-
titlePrefix?: string;
|
|
46
|
-
description?: string;
|
|
47
|
-
order?: number;
|
|
48
|
-
conditionalDisplay?: (formValues: any) => boolean;
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface DynamicFormSection {
|
|
53
|
-
id: string;
|
|
54
|
-
title?: string;
|
|
55
|
-
description?: string;
|
|
56
|
-
order?: number;
|
|
57
|
-
fields: DynamicFormField[];
|
|
58
|
-
preWarning?: string; // warning message shown before the section
|
|
59
|
-
postWarning?: string; // warning message shown after the section
|
|
60
|
-
preInfo?: string; // info message shown before the section
|
|
61
|
-
postInfo?: string; // info message shown after the section
|
|
62
|
-
conditionalDisplay?: (formValues: any) => boolean;
|
|
63
|
-
conditionalDisabled?: () => boolean;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
export interface DynamicFormOption {
|
|
68
|
-
label: string;
|
|
69
|
-
toolTip?: string;
|
|
70
|
-
helpInfo?: string;
|
|
71
|
-
errorInfo?: string;
|
|
72
|
-
displayError?: () => boolean;
|
|
73
|
-
hint?: string;
|
|
74
|
-
value: any;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface DynamicFormValidation {
|
|
78
|
-
type: 'required' | 'minLength' | 'maxLength' | 'pattern' | 'min' | 'max' | 'email' | 'custom' | 'compare';
|
|
79
|
-
value?: any;
|
|
80
|
-
message: string;
|
|
81
|
-
compareToField?: string; // For 'compare' validation
|
|
82
|
-
validator?: (value: any) => boolean;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface DynamicFormConfig {
|
|
86
|
-
id: string;
|
|
87
|
-
title?: string;
|
|
88
|
-
description?: string;
|
|
89
|
-
frames: DynamicFormFrameGroup[];
|
|
90
|
-
hideSubmitButton?: () => boolean; // function to determine if the submit button should be hidden
|
|
91
|
-
hideCancelButton?: () => boolean; // function to determine if the cancel button should be hidden
|
|
92
|
-
submitLabel?: string; // label for the submit button
|
|
93
|
-
cancelLabel?: string; // label for the cancel button
|
|
94
|
-
submitIcon?: string; // icon for the submit button
|
|
95
|
-
cancelIcon?: string; // icon for the cancel button
|
|
96
|
-
preWarning?: string; // warning message shown before the form
|
|
97
|
-
postWarning?: string; // warning message shown after the form
|
|
98
|
-
preInfo?: string; // info message shown before the form
|
|
99
|
-
postInfo?: string; // info message shown after the form
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export interface DynamicFormValue {
|
|
103
|
-
[key: string]: any;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface DynamicFormError {
|
|
107
|
-
fieldId: string;
|
|
108
|
-
message: string;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export interface DynamicFormState {
|
|
112
|
-
values: DynamicFormValue;
|
|
113
|
-
errors: DynamicFormError[];
|
|
114
|
-
touched: { [key: string]: boolean };
|
|
115
|
-
isSubmitting: boolean;
|
|
116
|
-
isValid: boolean;
|
|
117
|
-
}
|