Browse Source

refactoring

pull/24547/head
erdemcaygor 11 months ago
parent
commit
753350d150
  1. 9
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field.component.ts
  2. 75
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-control.ts
  3. 99
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.html
  4. 56
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.ts
  5. 1
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/index.ts
  6. 129
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.ts
  7. 27
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.models.ts
  8. 56
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.service.ts
  9. 1
      npm/ng-packs/packages/components/dynamic-form/src/public-api.ts

9
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field.component.ts

@ -1,9 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'abp-dynamic-form-field',
template: ``,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DynamicFormFieldComponent {}

75
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-control.ts

@ -0,0 +1,75 @@
import {Observable} from 'rxjs';
import {AbstractControlDirective, NgControl} from '@angular/forms';
import {Directive} from '@angular/core';
@Directive()
export abstract class MatFormFieldControl<T> {
/** The value of the control. */
value: T | null;
/**
* Stream that emits whenever the state of the control changes such that the parent `MatFormField`
* needs to run change detection.
*/
readonly stateChanges: Observable<void>;
/** The element ID for this control. */
readonly id: string;
/** The placeholder for this control. */
readonly placeholder: string;
/** Gets the AbstractControlDirective for this control. */
readonly ngControl: NgControl | AbstractControlDirective | null;
/** Whether the control is focused. */
readonly focused: boolean;
/** Whether the control is empty. */
readonly empty: boolean;
/** Whether the control is required. */
readonly required: boolean;
/** Whether the control is disabled. */
readonly disabled: boolean;
/** Whether the control is in an error state. */
readonly errorState: boolean;
/**
* An optional name for the control type that can be used to distinguish `mat-form-field` elements
* based on their control type. The form field will add a class,
* `mat-form-field-type-{{controlType}}` to its root element.
*/
readonly controlType?: string;
/**
* Whether the input is currently in an autofilled state. If property is not present on the
* control it is assumed to be false.
*/
readonly autofilled?: boolean;
/**
* Value of `aria-describedby` that should be merged with the described-by ids
* which are set by the form-field.
*/
readonly userAriaDescribedBy?: string;
/**
* Whether to automatically assign the ID of the form field as the `for` attribute
* on the `<label>` inside the form field. Set this to true to prevent the form
* field from associating the label with non-native elements.
*/
readonly disableAutomaticLabeling?: boolean;
/** Gets the list of element IDs that currently describe this control. */
readonly describedByIds?: string[];
/** Sets the list of element IDs that currently describe this control. */
abstract setDescribedByIds(ids: string[]): void;
/** Handles a click on the control's container. */
abstract onContainerClick(event: MouseEvent): void;
}

99
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.html

@ -0,0 +1,99 @@
@if (isVisible) {
<div class="field-container">
@if (field.type === 'text') {
<!-- Text Input -->
<div class="form-group">
<label [for]="field.key">{{ field.label }}</label>
<input
[id]="field.key"
[formControlName]="field.key"
[placeholder]="field.placeholder || ''"
class="form-control"
[class.is-invalid]="isFieldInvalid()">
@if (isFieldInvalid()) {
<div class="invalid-feedback">
{{ getErrorMessage() }}
</div>
}
</div>
} @else if (field.type === 'select') {
<!-- Select Dropdown -->
<div class="form-group">
<label [for]="field.key">{{ field.label }}</label>
<select
[id]="field.key"
[formControlName]="field.key"
class="form-control"
[class.is-invalid]="isFieldInvalid()">
<option value="">Please select...</option>
@for (option of field.options; track option.key) {
<option
[value]="option.key">
{{ option.value }}
</option>
}
</select>
@if (isFieldInvalid()) {
<div class="invalid-feedback">
{{ getErrorMessage() }}
</div>
}
</div>
} @else if (field.type === 'checkbox') {
<!-- Checkbox -->
<div class="form-group form-check">
<input
type="checkbox"
[id]="field.key"
[formControlName]="field.key"
class="form-check-input"
[class.is-invalid]="isFieldInvalid()">
<label class="form-check-label" [for]="field.key">
{{ field.label }}
</label>
@if (isFieldInvalid()) {
<div class="invalid-feedback">
{{ getErrorMessage() }}
</div>
}
</div>
} @else if (field.type === 'email') {
<!-- Email Input -->
<div class="form-group">
<label [for]="field.key">{{ field.label }}</label>
<input
type="email"
[id]="field.key"
[formControlName]="field.key"
[placeholder]="field.placeholder || ''"
class="form-control"
[class.is-invalid]="isFieldInvalid()">
@if (isFieldInvalid()) {
<div class="invalid-feedback">
{{ getErrorMessage() }}
</div>
}
</div>
} @else if (field.type === 'textarea') {
<!-- Textarea -->
<div class="form-group">
<label [for]="field.key">{{ field.label }}</label>
<textarea
[id]="field.key"
[formControlName]="field.key"
[placeholder]="field.placeholder || ''"
rows="4"
class="form-control"
[class.is-invalid]="isFieldInvalid()">
</textarea>
@if (isFieldInvalid()) {
<div class="invalid-feedback">
{{ getErrorMessage() }}
</div>
}
</div>
}
</div>
<!-- Add more field types as needed-->
}

56
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.ts

@ -0,0 +1,56 @@
import { ChangeDetectionStrategy, Component, InjectionToken } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
export const ABP_DYNAMIC_FORM_FIELD = new InjectionToken<DynamicFormFieldComponent>('AbpDynamicFormField');
@Component({
selector: 'abp-dynamic-form-field',
templateUrl: './dynamic-form-field.component.html',
providers: [{ provide: ABP_DYNAMIC_FORM_FIELD, useExisting: DynamicFormFieldComponent }],
host: { 'class': 'abp-dynamic-form-field' },
exportAs: 'abpDynamicFormField',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
NgTemplateOutlet
]
})
export class DynamicFormFieldComponent {
field = input<FormFieldConfig>();
@Input() isVisible: boolean = true;
ngOnInit() {
const control = this.form.get(this.field.key);
if (control) {
control.valueChanges.subscribe(value => {
this.fieldChange.emit({ fieldKey: this.field.key, value });
});
}
}
isFieldInvalid(): boolean {
const control = this.form.get(this.field.key);
return !!(control && control.invalid && (control.dirty || control.touched));
}
getErrorMessage(): string {
const control = this.form.get(this.field.key);
if (!control || !control.errors) return '';
const validators = this.field.validators || [];
for (const validator of validators) {
if (control.errors[validator.type]) {
return validator.message;
}
}
// Fallback error messages
if (control.errors['required']) return `${this.field.label} is required`;
if (control.errors['email']) return 'Please enter a valid email address';
if (control.errors['minlength']) return `Minimum length is ${control.errors['minlength'].requiredLength}`;
if (control.errors['maxlength']) return `Maximum length is ${control.errors['maxlength'].requiredLength}`;
return 'Invalid input';
}
}

1
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/index.ts

@ -0,0 +1 @@
export * from './dynamic-form-field.component';

129
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.ts

@ -1,4 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, input, output, inject } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { DynamicFormService } from './dynamic-form.service';
import { FormFieldConfig } from './dynamic-form.models';
import { DynamicFormFieldComponent } from './dynamic-form-field';
@Component({
selector: 'abp-dynamic-form',
@ -6,4 +11,124 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DynamicFormComponent {}
export class DynamicFormComponent {
fields = input<FormFieldConfig[]>([]);
submitButtonText = input<string>('Submit');
formSubmit = output<any>();
formCancel = output<void>();
private dynamicFormService = inject(DynamicFormService);
dynamicForm!: FormGroup;
isSubmitting = false;
fieldVisibility: { [key: string]: boolean } = {};
ngOnInit() {
this.dynamicForm = this.dynamicFormService.createFormGroup(this.fields());
this.initializeFieldVisibility();
this.setupConditionalLogic();
}
get sortedFields(): FormFieldConfig[] {
return this.fields().sort((a, b) => (a.order || 0) - (b.order || 0));
}
onSubmit() {
if (this.dynamicForm.valid) {
this.isSubmitting = true;
this.formSubmit.emit(this.dynamicForm.value);
} else {
this.markAllFieldsAsTouched();
}
}
onCancel() {
this.formCancel.emit();
}
onFieldChange(event: { fieldKey: string; value: any }) {
this.evaluateConditionalLogic(event.fieldKey);
}
isFieldVisible(field: FormFieldConfig): boolean {
return this.fieldVisibility[field.key] !== false;
}
private initializeFieldVisibility() {
this.fields().forEach(field => {
this.fieldVisibility[field.key] = !field.conditionalLogic?.length;
});
}
private setupConditionalLogic() {
this.fields().forEach(field => {
if (field.conditionalLogic) {
field.conditionalLogic.forEach(rule => {
const dependentControl = this.dynamicForm.get(rule.dependsOn);
if (dependentControl) {
dependentControl.valueChanges.subscribe(() => {
this.evaluateConditionalLogic(field.key);
});
}
});
}
});
}
private evaluateConditionalLogic(fieldKey: string) {
const field = this.fields().find(f => f.key === fieldKey);
if (!field?.conditionalLogic) return;
field.conditionalLogic.forEach(rule => {
const dependentValue = this.dynamicForm.get(rule.dependsOn)?.value;
const conditionMet = this.evaluateCondition(dependentValue, rule.condition, rule.value);
this.applyConditionalAction(fieldKey, rule.action, conditionMet);
});
}
private evaluateCondition(fieldValue: any, condition: string, ruleValue: any): boolean {
switch (condition) {
case 'equals':
return fieldValue === ruleValue;
case 'notEquals':
return fieldValue !== ruleValue;
case 'contains':
return fieldValue && fieldValue.includes && fieldValue.includes(ruleValue);
case 'greaterThan':
return Number(fieldValue) > Number(ruleValue);
case 'lessThan':
return Number(fieldValue) < Number(ruleValue);
default:
return false;
}
}
private applyConditionalAction(fieldKey: string, action: string, shouldApply: boolean) {
const control = this.dynamicForm.get(fieldKey);
switch (action) {
case 'show':
this.fieldVisibility[fieldKey] = shouldApply;
break;
case 'hide':
this.fieldVisibility[fieldKey] = !shouldApply;
break;
case 'enable':
if (control) {
shouldApply ? control.enable() : control.disable();
}
break;
case 'disable':
if (control) {
shouldApply ? control.disable() : control.enable();
}
break;
}
}
private markAllFieldsAsTouched() {
Object.keys(this.dynamicForm.controls).forEach(key => {
this.dynamicForm.get(key)?.markAsTouched();
});
}
}

27
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.models.ts

@ -0,0 +1,27 @@
export interface FormFieldConfig {
key: string;
value?: any;
type: 'text' | 'email' | 'number' | 'select' | 'checkbox' | 'date' | 'textarea';
label: string;
placeholder?: string;
required?: boolean;
disabled?: boolean;
options?: { key: string; value: any }[];
validators?: ValidatorConfig[];
conditionalLogic?: ConditionalRule[];
order?: number;
gridSize?: number;
}
export interface ValidatorConfig {
type: 'required' | 'email' | 'minLength' | 'maxLength' | 'pattern' | 'custom';
value?: any;
message: string;
}
export interface ConditionalRule {
dependsOn: string;
condition: 'equals' | 'notEquals' | 'contains' | 'greaterThan' | 'lessThan';
value: any;
action: 'show' | 'hide' | 'enable' | 'disable';
}

56
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.service.ts

@ -1,8 +1,56 @@
import { Injectable } from '@angular/core';
import {Injectable} from '@angular/core';
import {FormControl, FormGroup, ValidatorFn, Validators} from '@angular/forms';
import {FormFieldConfig, ValidatorConfig} from './dynamic-form.models';
@Injectable({
providedIn: 'root',
providedIn: 'root'
})
export class DynamicFormFieldService {
// Add any shared logic for dynamic form fields here if needed in the future
export class DynamicFormService {
createFormGroup(fields: FormFieldConfig[]): FormGroup {
const group: any = {};
fields.forEach(field => {
const validators = this.buildValidators(field.validators || []);
const initialValue = this.getInitialValue(field);
group[field.key] = new FormControl({
value: initialValue,
disabled: field.disabled || false
}, validators);
});
return new FormGroup(group);
}
private buildValidators(validatorConfigs: ValidatorConfig[]): ValidatorFn[] {
return validatorConfigs.map(config => {
switch (config.type) {
case 'required':
return Validators.required;
case 'email':
return Validators.email;
case 'minLength':
return Validators.minLength(config.value);
case 'maxLength':
return Validators.maxLength(config.value);
case 'pattern':
return Validators.pattern(config.value);
default:
return Validators.nullValidator;
}
});
}
private getInitialValue(field: FormFieldConfig): any {
switch (field.type) {
case 'checkbox':
return false;
case 'number':
return 0;
default:
return '';
}
}
}

1
npm/ng-packs/packages/components/dynamic-form/src/public-api.ts

@ -1 +1,2 @@
export * from './dynamic-form.component';
export * from './dynamic-form-field';

Loading…
Cancel
Save