/*
* © 2017 Stratio Big Data Inc., Sucursal en España.
*
* This software is licensed under the Apache License, Version 2.0.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the terms of the License for more details.
*
* SPDX-License-Identifier: Apache-2.0.
*/
import {
Component,
Input,
ChangeDetectionStrategy,
EventEmitter,
Output,
forwardRef,
ChangeDetectorRef
} from '@angular/core';
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
FormControl,
NG_VALIDATORS,
FormGroup,
FormArray
} from '@angular/forms';
import { cloneDeep as _cloneDeep } from 'lodash';
/**
* @description {Component} [Form list]
*
* The form list component allows to create dynamically list of items.
*
* @example
*
* {html}
*
* ```
*
*
*
*
* ```
*
*
*/
@Component({
selector: 'st-form-list',
templateUrl: './st-form-list.html',
styleUrls: ['./st-form-list.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StFormListComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => StFormListComponent), multi: true }
]
})
export class StFormListComponent implements ControlValueAccessor {
/** @Input {any} [schema=''] JSON schema of items */
@Input() schema: any;
/** @Input {string} [buttonLabel='Add one more item'] String displayed in the button to add more items */
@Input() buttonLabel: string = 'Add one more item';
/** @Output {any[]} [valueChange] Notify any value change */
@Output() valueChange: EventEmitter = new EventEmitter();
/** @Output {number} [add] Notify the position of the added item and the modified model */
@Output() add: EventEmitter<{position: number, model: any[]}> = new EventEmitter<{position: number, model: any[]}>();
/** @Output {number} [remove] Notify the position of the removed item and the modified model */
@Output() remove: EventEmitter<{position: number, model: any[]}> = new EventEmitter<{position: number, model: any[]}>();
/** @Output {any[]} [blur] Notify when user leaves a field */
@Output() blur: EventEmitter = new EventEmitter();
public formArray: FormArray = new FormArray([]);
private _value: any[] = [];
constructor(private _cd: ChangeDetectorRef) {
}
@Input() get value(): any {
return this._value;
}
set value(value: any) {
if (value !== this._value) {
this._value = _cloneDeep(value);
this.updateForm();
this.onChange(value);
}
}
// Function to call when the value changes.
onChange(_: any): void {
}
onTouched = () => {
}
addItem(): void {
this._value.push({});
this.formArray.push(this.generateItemFormGroup());
this.valueChange.emit(this._value);
this.add.emit({ position: this._value.length - 1, model: this._value });
this.onChange(this._value);
}
removeItem(position: number): void {
this.formArray.removeAt(position);
this._value.splice(position, 1);
this.valueChange.emit(this._value);
this.remove.emit({ position: position, model: this._value });
this.onChange(this._value);
}
isRequired(propertyName: string): boolean {
return propertyName && this.schema.required && this.schema.required.indexOf(propertyName) !== -1;
}
generateItemFormGroup(position?: number): FormGroup {
let formGroup = new FormGroup({});
let properties = Object.keys(this.schema.properties);
for (let i = 0; i < properties.length; ++i) {
let property: string = properties[i];
let value: any = this.schema.properties[property].default;
if (position !== undefined && this._value[i]) {
value = position !== undefined ? this._value[i][property] : null;
}
formGroup.addControl(properties[i], new FormControl(value));
}
return formGroup;
}
validate(control: FormControl): any {
let errors: any = null;
if (this.formArray) {
for (let i = 0; i < this.formArray.controls.length; ++i) {
let rowFormGroup: FormGroup = this.formArray.controls[i];
Object.keys(rowFormGroup.controls).forEach((propertyName) => {
if (rowFormGroup.controls[propertyName] && rowFormGroup.controls[propertyName].errors) {
if (!errors) {
errors = [];
}
if (!errors[i]) {
errors[i] = {};
}
errors[i][propertyName] = rowFormGroup.controls[propertyName].errors;
}
});
}
}
return errors;
}
onChangeProperty(value: any, rowPosition: number, property: string): void {
this._value[rowPosition][property] = value;
this.valueChange.emit(this._value);
setTimeout(() => {
this.onChange(this._value);
});
}
onBlur(): void {
this.blur.emit(this._value);
}
// When value is received from outside
writeValue(value: any): void {
if (value && this._value !== value) {
this._value = _cloneDeep(value);
this.updateForm();
this._cd.markForCheck();
}
}
// Registry the change function to propagate internal model changes
registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
// Allows Angular to disable the form.
setDisabledState(disable: boolean): void {
if (disable) {
this.formArray.disable();
} else {
this.formArray.enable();
}
}
private updateForm(): void {
this.formArray = new FormArray([]);
if (this._value) {
for (let i = 0; i < this._value.length; ++i) {
this.formArray.push(this.generateItemFormGroup(i));
}
}
}
}