first-npm-package-nicule
Version:
This isi first npm package
311 lines (251 loc) • 10.3 kB
text/typescript
import { AfterViewInit, Component, ContentChild, ElementRef, EventEmitter, Injector, Input, OnChanges, Output, SimpleChanges, ViewChild, ViewChildren } from '@angular/core';
import { HypermediaAction, HypermediaField, LabelingService } from 'first-npm-package-nicule/core';
import { NgForm } from '@angular/forms';
import { Subscription } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { FormFieldsComponent } from '../form-fields.component';
import { PARENT_FORM } from '../parent-form';
import { FormFieldComponent } from '../form-field.component';
export class HypermediaForm implements AfterViewInit, OnChanges {
action: HypermediaAction;
tokenOverride: string;
preventSubmissionOnEnter = true;
disabled = false; // this overwrites all inputs
mode: 'regular' | 'narrow' | 'wide' = 'regular'; // this overwrites all inputs
additionalData: any;
actionSuccess = new EventEmitter();
actionError = new EventEmitter();
init = new EventEmitter();
valueChanged = new EventEmitter();
actionSubmit = new EventEmitter();
actionComplete = new EventEmitter();
escapeKeydown = new EventEmitter();
ngForm: NgForm;
formElement: ElementRef;
fallbackFields: FormFieldComponent;
_formFields: FormFieldsComponent;
get formFields(): FormFieldsComponent {
return this._formFields;
}
set formFields(newValue: FormFieldsComponent) {
this._formFields = newValue;
}
isSubmiting = false;
error: string;
fields: Array<HypermediaField & { fieldInputs?: any }> = [];
get value(): any {
return this.ngForm && this.ngForm.value || {};
}
get isValid(): boolean {
return this.ngForm && this.ngForm.valid;
}
private valueChangeSubscription: Subscription;
ngOnChanges(changes: SimpleChanges): void {
if ('action' in changes && this.action) {
const {action} = this as any;
this.fields = action && this.cloneFields(action.fields) || []; // this should be made in a imutable
}
}
cloneFields(source: Array<HypermediaField>): Array<HypermediaField> {
return source.map(({fields, options, flex, ...rest}) => ({
...rest,
fields: fields && fields.map(field => ({...field})),
options: options && options.map(option => ({...option})),
flex: flex && Array.isArray(flex) ? flex.map(flexItem => flexItem) : flex
}));
}
constructor(
private translate: TranslateService,
private labelingService: LabelingService,
private _injector: Injector
) {
}
submit(formValues?: any): void {
if (this.isSubmiting === true) {
return;
}
this.isSubmiting = true;
if (formValues && Object.keys(this.ngForm.form.controls).length > 0) {
this.ngForm.setValue({...this.ngForm.value, ...formValues});
}
this.ngForm.onSubmit(new MouseEvent(''));
}
onSuccess(event = {} as any): void {
this.isSubmiting = false;
this.actionSuccess.emit(event);
}
onError(event = {} as any): void {
this.isSubmiting = false;
if (typeof event === 'string') {
this.error = event;
}
this.actionError.emit(event);
}
onComplete(): void {
this.isSubmiting = false;
this.actionComplete.emit();
const errors = [];
Object.keys(this.ngForm.controls)
.forEach(key => {
const currentField = this.ngForm.controls[key];
if (!currentField.valid) {
errors.push(key);
}
});
if (errors.length) {
this.scrollToError(errors);
}
}
formSubmission(event): void {
this.actionSubmit.emit();
if (!this.preventSubmissionOnEnter) {
return;
}
event.stopPropagation(); // stop the event from propagating on the form, preventing submit
}
showErrors(onlyPrefilledFields = false): void {
const errors = [];
Object.keys(this.ngForm.controls)
.filter(key => !onlyPrefilledFields || this.ngForm.controls[key].value)
.forEach(key => {
const currentField = this.ngForm.controls[key];
this.ngForm.controls[key].markAsDirty();
this.ngForm.controls[key].markAsTouched();
if (!currentField.valid) {
errors.push(key);
}
});
if (errors.length) {
this.scrollToError(errors);
}
}
scrollToError(errors): void {
if (this.formFields && this.formFields.orderer) {
for (const formFieldComponent of this.formFields.orderer.ordered) {
let name = formFieldComponent.named;
const type = formFieldComponent.baseField.type;
let selector;
let foundInGroup = false;
if (type === 'group') {
const fieldsInGroup = formFieldComponent.baseField.fields.map(i => i.name);
foundInGroup = errors.some(r => {
if (fieldsInGroup.indexOf(r) >= 0) {
name = r;
return true;
}
return false;
});
}
if (errors.includes(name) || foundInGroup) {
switch (type) {
case 'boolean':
selector = `[name=${name}]`;
break;
case 'options':
selector = `mat-select[id^=${name}-]`;
break;
default:
selector = `[id^=form-field-${name}-]`;
}
const elem = this.formElement.nativeElement.querySelector(selector);
if (elem) {
elem.scrollIntoView({behavior: 'smooth', block: 'center'});
}
break;
}
}
}
}
reset(resetForm = true): any {
this.error = '';
if (!this.ngForm) {
return;
}
if (resetForm) {
this.ngForm.reset();
}
this.ngForm.control.setErrors({}, {emitEvent: true});
this.reinitializeFields(this.action.fields);
}
triggerSubmit(): void {
this.actionSubmit.emit();
}
private reinitializeFields(fields: Array<HypermediaField> = []): void {
fields.forEach(field => {
if (field.type === 'group') {
this.reinitializeFields(field.fields);
} else {
this.reinitializeField(field);
}
});
}
private reinitializeField({name, value}: HypermediaField): void {
const control = this.ngForm.controls[name];
if (control) {
control.setValue(value);
}
}
setValue(value: any): any {
if (!value) {
return;
}
Object
.keys(value)
.forEach(name => {
const control = this.ngForm.controls[name];
if (control) {
control.setValue(value[name]);
}
});
}
ngDestroy(): void {
this.valueChangeSubscription.unsubscribe();
}
ngAfterViewInit(): void {
const form = this.ngForm;
if (form !== undefined) {
this.init.emit();
this.valueChangeSubscription = form.valueChanges.subscribe(newValue => {
if (this.action && this.action['$$validator']) {
const validationResponse = this.action['$$validator'](newValue);
if (validationResponse) {
const propertyKeys = Object.keys(validationResponse);
propertyKeys.forEach(propertyKey => {
const control = form.controls[propertyKey];
if (control) {
const modelErrors = validationResponse[propertyKey];
const defaultErrors = control.validator(control);
const combinedErrors = {...defaultErrors, ...modelErrors};
const hasErrors = Object.keys(combinedErrors).length > 0;
if (hasErrors) {
control.setErrors(combinedErrors);
} else {
control.setErrors(undefined);
}
}
});
}
}
this.valueChanged.emit(newValue);
});
}
}
getField(named: string): any {
return this.fields.find(({name}) => name === named);
}
getSelectedOptionName(fieldName: string): string {
const field = this.fields
.find(f => f.name === fieldName);
return field.options
.find(option => option.value === field.value)
.name;
}
}