import { Component, EventEmitter, Inject, Input, OnInit, Output, } from '@angular/core'; import { PpConditionConfig, PpForm, PpFormField, PpHideFormFields, PpRelativeValidation, PpValueItem, } from './forms.type'; import * as _ from 'underscore'; import { AbstractControl, FormArray, FormControl, FormGroup, Validators, } from '@angular/forms'; import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { Subscription } from 'rxjs'; import { PpDataParserService, PpHttpConfig, PpHttpOptions, PpHttpParams, PpHttpService, } from '@penpencil/common'; import { QuillToolbarOptions } from './utils/quill-config.util'; import { PpNotificationService } from './services/notification.service'; import { PpConditionValidationService } from './services/condition-validation.service'; import { MatDialog } from '@angular/material/dialog'; import { PpHelper } from './utils/helper'; import { MatChipInputEvent } from '@angular/material/chips'; import { PpButton } from './components/button/button.type'; import { PEN_PENCIL_FORMS_API_CONFIG } from './forms.constant'; @Component({ selector: 'pp-form', templateUrl: './forms.component.html', styleUrls: ['./forms.component.scss'], }) // Unsubscribe all subscriptions on ngOnDestroy // @PpAutoUnsubscribe export class PpFormComponent implements OnInit { // Form Config @Input() formConfig!: PpForm; // Stepper Form @Input() stepperForm!: boolean; // Stepper Form @Input() backButton!: PpButton; // Before Submit Form value event to parent @Output() beforeSubmitFormValue = new EventEmitter(true); // Form submitted event to parent @Output() formSubmitted = new EventEmitter(true); // Back button event to parent @Output() backButtonClick = new EventEmitter(true); // Next button event to parent @Output() nextButtonClick = new EventEmitter(true); // Form submitted api response to parent @Output() getData = new EventEmitter(true); @Output() getExistingFormData = new EventEmitter(true); // Underscore package public _ = _; // FormGroup public form!: FormGroup; // FormFields public formFields!: PpFormField[]; // FormButton public formButton!: PpButton; // Error Message public errorMsg!: string; // Loader public isLoad!: boolean; // Data soure from api/local public dataSource: any; // Display no. of columns as per row public columnPerRow!: number; // Header Name public header!: string; // Display submit button public disableSubmitButton = false; // Rich text editor options public quillToolbarOptions = QuillToolbarOptions; // Tag keys to create readonly separatorKeysCodes: number[] = [ENTER, COMMA]; // Subscription private formSubscription!: Subscription; private selectSubscription!: Subscription; private autoCompleteSubscription!: Subscription; private uploadSubscription!: Subscription; private submitFormSubscription!: Subscription; private asyncValidateSubscription!: Subscription; constructor( private apiService: PpHttpService, private notificationService: PpNotificationService, private conditionValidationService: PpConditionValidationService, private dataParserService: PpDataParserService, public dialog: MatDialog, @Inject(PEN_PENCIL_FORMS_API_CONFIG) private config: PpHttpConfig ) {} ngOnInit() { // Validate Form Config // FormConfigValidator.validateFormConfig(this.formConfig); // Defining form fields this.formFields = this.formConfig.formFields; // Defining form buttons this.formButton = this.formConfig.formButton; // Defining Header Name this.header = this.formConfig.header!; // Defining Field Per Row this.columnPerRow = this.formConfig.columnPerRow || 1; // Check for form type if (this.formConfig.type === 'create') { // Configure Form this.form = this.configureForm(this.formFields); // Check values for select field and get in case of api this.checkValuesForSelectFields(this.formFields); // Set value for autocomplete this.setDefaultForAutocomplete(this.formFields); } else { // Process Form Data In case of edit form this.processFormData().then((res) => { // Add select all option to all values arrays of type multi-checkbox this.formFields.forEach((formField: PpFormField) => { // if(formField.type==='multicheckbox' && formField.values && formField.values.length) { // formField.values.unshift({value: 'selectAll', viewValue: 'Select All'}); // } // Setting value to form this.setDefaultValue(formField); }); // Configure Form this.form = this.configureForm(this.formFields); // Check values for select field and get in case of api this.checkValuesForSelectFields(this.formFields); // Set value for autocomplete this.setDefaultForAutocomplete(this.formFields); }); } } /** * Process Form Data */ private processFormData(): Promise { return new Promise((resolve) => { // Page Loader Start this.isLoad = true; // Checking Default Data Source Existence if (this.formConfig.dataSource) { // Assigning data this.dataSource = this.formConfig.dataSource; // Page Loader Stop this.isLoad = false; // Resolving Promise resolve(true); } else { // If No Default Data Source Then Call API // Api Call this.formSubscription = this.apiService .request({ ...this.formConfig?.getApi!, config: this.config }) .subscribe( (res) => { // Assigning data this.dataSource = PpHelper.getValueWithApiKey( res, this.formConfig.apiDataKey! ); // Page Loader Stop this.isLoad = false; // Resolving Promise resolve(true); }, (err) => { // Remove Loader this.isLoad = false; resolve(true); } ); } }); } /** * * @param formField : FormField * Set default value in case of edit */ private setDefaultValue(formField: PpFormField) { // Checking value if (formField.value && formField.type != 'number') { // Setting value formField.defaultValue = _.isEmpty( PpHelper.getValueWithApiKey(this.dataSource, formField.value) ) ? undefined : PpHelper.getValueWithApiKey(this.dataSource, formField.value); } if (formField.value && formField.type === 'number') { // Setting value formField.defaultValue = _.isEmpty( PpHelper.getValueWithApiKey(this.dataSource, formField.value) ) ? undefined : parseFloat( this.dataParserService .getValue(this.dataSource, formField.value) .toString() ); } // Check for group if (formField.type === 'group') { // loop formfields and set value formField?.formFields?.forEach((groupField: PpFormField) => { this.setDefaultValue(groupField); }); } } /** * * @param formFields :FormField[] * Check whether select fields has values from api */ private checkValuesForSelectFields(formFields: PpFormField[]) { formFields.forEach(async (formField) => { // Checking form field type if (formField.type === 'group' || formField.type === 'array') { // Check values for select field in group and get in case of api this.checkValuesForSelectFields(formField.formFields!); } else if ( formField.type === 'select' || (formField.type === 'tags' && formField?.tags?.type === 'select') ) { // Check for select type if (formField.select && formField.select.type === 'api') { // Init values formField.values = []; // Set values await this.setValuesForSelectFields(formField); // Check for related fields if (formField.relatedTo && formField.relatedTo.length) { // Set related fields values this.getRelatedFieldsData(formField); } } } }); } /** * * @param formFields :CanFormField[] */ private setDefaultForAutocomplete(formFields: PpFormField[]) { formFields.forEach(async (formField) => { // Checking form field type if (formField.type === 'group' || formField.type === 'array') { // Check values for select field in group and get in case of api this.setDefaultForAutocomplete(formField.formFields!); } else if (formField.type === 'autocomplete') { // Check for select type if (formField.autoComplete && formField.defaultValue) { if (formField.autoComplete.type === 'local') { if (formField.values) { const index = formField.values.findIndex( (each) => each.value === formField.defaultValue ); if (index > -1) { formField.autoComplete.autocompleteSearchText = formField.values[index].viewValue; } } } else if ( formField && formField.autoComplete && formField.autoComplete.api && formField.autoComplete.type === 'api' ) { formField.autoComplete.api.params = new PpHttpParams(); const api: PpHttpOptions = Object.assign( {}, formField.autoComplete.api ); if (!api.params) { api.params = new PpHttpParams(); } api.params = api.params.append( formField.autoComplete.apiValueKey!, formField.defaultValue ); this.apiService .request({ ...api, config: this.config }) .subscribe((res) => { const data = PpHelper.getValueWithApiKey( res, formField.autoComplete?.apiDataKey! ); if (formField.autoComplete) formField.autoComplete.dataSource = data; if (_.isArray(data) && formField && formField.autoComplete) { // Set value formField.autoComplete.autocompleteSearchText = PpHelper.getValueWithApiKey( data[0], formField.autoComplete.apiViewValueKey!, formField.autoComplete.viewValueSeparators ); } }); } } } }); } /** * * @param formField : FormField * Set values in case of api in select fields */ private setValuesForSelectFields(formField: PpFormField) { return new Promise((resolve) => { // Check for api if (formField && formField.select?.api) { // Api request this.selectSubscription = this.apiService .request({ ...formField.select.api, config: this.config }) .subscribe((res) => { // Get data using key const data = PpHelper.getValueWithApiKey( res, formField.select?.apiDataKey! ); // Check whether data is array or not if (_.isArray(data) && formField.select) { // Assign dataSource formField.select.dataSource = data; // Loop data data.forEach((element) => { // If convertType is string, convert value to string const value = formField.select?.convertType === 'string' ? this.dataParserService .getValue(element, formField.select.apiValueKey!) .toString() : PpHelper.getValueWithApiKey( element, formField.select?.apiValueKey! ); // Add object to values formField.values?.push({ value: value, viewValue: this.getValueWithApiKey( element, formField.select?.apiViewValueKey!, formField.select?.viewValueSeparators ), }); }); } resolve(false); }); } else { resolve(false); } }); } /** * * @param formFields :CanFormField[] * Create Formgroup */ private configureForm(formFields: PpFormField[]) { // Initialize group let group: any = {}; // Loop form fields formFields.forEach((element) => { // Check for type if (element.type === 'group') { // Configure nested form group[element.name] = this.configureForm(element.formFields!); if (element.defaultValue) { group[element.name].patchValue(element.defaultValue); } } else if (element.type === 'array') { // Check for default value if (element.defaultValue && _.isArray(element.defaultValue)) { // Init formarray const formArray = new FormArray([]) as FormArray; // Loop on values element.defaultValue.forEach((each) => { formArray.push(this.configureForm(element.formFields!)); }); // Assign formarray to group group[element.name] = formArray; // Assign default value group[element.name].patchValue(element.defaultValue); } else { // Assign formarray to group group[element.name] = new FormArray([ this.configureForm(element.formFields!), ]); } } else { // Initialize validations const validators = this.setValidators(element); // Async validator let asyncValidators: any = []; if (element.type === 'text') { if (element.asyncValidation) { asyncValidators.push(this.asyncValidator.bind(this, element)); } } if(element.type==='image' && element.file?.src && element.file?.viewValue) { let imageValue; if(element.file.src?.dataSource && element.file.src?.srcDataKey) { imageValue = PpHelper.getValueWithApiKey( element.file.src.dataSource, element.file.src.srcDataKey, element.file.src.srcKeySeparator!, true ); } else if(element.file.src?.srcForImage) { // In case user want to give image url directly imageValue = element.file.src.srcForImage; } element.file.viewValue.value = !PpHelper.isEmpty(imageValue) ? imageValue : undefined } // Add select all option to all values arrays of type multi-checkbox if (element.type === 'multicheckbox' && element.values && element.values.length && element.values[0].value!=='selectAll') { element.values.unshift({ value: 'selectAll', viewValue: 'Select All' }); } // Create Form controls for each form field group[element.name] = new FormControl( { value: element.defaultValue, disabled: element.disabled }, validators, asyncValidators ); } // Hide or show keys on basis of value this.hideFormFields(element.hideFormFields!, element.defaultValue); }); return new FormGroup(group); } /** * * @param formField :CanFormField * Add validation to field */ private setValidators(formField: PpFormField) { // Init validators const validators = []; // If value is required, add required validation to array if (formField.required && formField.required.value) { validators.push(Validators.required); } // If there is pattern, add pattern validation to array if ( formField.type === 'text' || formField.type === 'number' || formField.type === 'password' ) { if (formField.pattern) { validators.push(Validators.pattern(formField.pattern.value)); } } // Check for relative validation if (formField.relativeValidation) { // Loop relativeValidation formField.relativeValidation.forEach((eachField) => { // Check for type switch (eachField.type) { case 'min': // Add relativeMinValidator to array validators.push( this.relativeMinValidator.bind(this, eachField, formField) ); break; case 'max': // Add relativeMaxValidator to array validators.push( this.relativeMaxValidator.bind(this, eachField, formField) ); break; case 'equals': // Add relativeEqualValidator to array validators.push( this.relativeEqualsValidator.bind(this, eachField, formField) ); break; case 'notEquals': // Add relativeEqualValidator to array validators.push( this.relativeNotEqualsValidator.bind(this, eachField, formField) ); break; } }); } return validators; } /** * * @param validation :CanRelativeValidation * @param control :FormControl * Compare values and return result */ private relativeMinValidator( validation: PpRelativeValidation, formField: PpFormField, control: FormControl ) { // Check for form existence if (this.form) { const value = PpHelper.getValueWithApiKey( this.form.getRawValue(), validation.key ); if (value && control.value) { if (formField.type === 'date') { if ( _.isNumber(Date.parse(value)) && _.isNumber(Date.parse(control.value)) && new Date(value) > new Date(control.value) ) { return { relativeMin: validation.errorMessage }; } } else { if (value > control.value) { return { relativeMin: validation.errorMessage }; } } } } return null; } /** * * @param validation :CanRelativeValidation * @param control :FormControl * Compare values and return result */ private relativeMaxValidator( validation: PpRelativeValidation, formField: PpFormField, control: FormControl ) { // Check for form existence if (this.form) { const value = PpHelper.getValueWithApiKey( this.form.getRawValue(), validation.key ); if (value && control.value) { if (formField.type === 'date') { if ( _.isNumber(Date.parse(value)) && _.isNumber(Date.parse(control.value)) && new Date(value) < new Date(control.value) ) { return { relativeMax: validation.errorMessage }; } } else { if (value < control.value) { return { relativeMax: validation.errorMessage }; } } } } return null; } /** * * @param validation :CanRelativeValidation * @param control :FormControl * Compare values and return result */ private relativeEqualsValidator( validation: PpRelativeValidation, formField: PpFormField, control: FormControl ) { // Check for form existence if (this.form) { const value = PpHelper.getValueWithApiKey( this.form.getRawValue(), validation.key ); if (value !== control.value) { return { relativeEqual: validation.errorMessage }; } } return null; } /** * * @param validation :CanRelativeValidation * @param control :FormControl * Compare values and return result */ private relativeNotEqualsValidator( validation: PpRelativeValidation, formField: PpFormField, control: FormControl ) { // Check for form existence if (this.form) { const value = PpHelper.getValueWithApiKey( this.form.getRawValue(), validation.key ); if (value && control.value && value === control.value) { return { relativeNotEqual: validation.errorMessage }; } } return null; } /** * * @param formField :CanFormField * @param control :FormControl */ private asyncValidator(formField: PpFormField, control: FormControl) { return new Promise((resolve) => { // Check for asyncValidateSubscription // if (this.asyncValidateSubscription) { // this.asyncValidateSubscription.unsubscribe(); // } // Check for form if (this.form && formField.asyncValidation) { formField.asyncValidation.minCharacters = formField.asyncValidation.minCharacters || 1; if ( control.value && control.value.trim() && control.value.length >= formField.asyncValidation.minCharacters ) { if (!formField.asyncValidation.api.params) { formField.asyncValidation.api.params = new PpHttpParams(); } formField.asyncValidation.api.params = formField.asyncValidation.api.params?.set( formField.asyncValidation.apiParamKey, control.value ); this.asyncValidateSubscription = this.apiService .request({ ...formField.asyncValidation.api, config: this.config }) .subscribe( (res: any) => { const data = PpHelper.getValueWithApiKey( res, formField.asyncValidation?.apiDataKey! ); if ( !this.conditionValidationService.validate( formField.asyncValidation?.condition!, data ) ) { resolve({ asyncValidationError: formField.asyncValidation?.errorMessage, }); } else { resolve(null); } }, (err: any) => { resolve(null); } ); } else { resolve(null); } } else { resolve(null); } }); } /** * * @param control :AbstractControl * Validate every field after any change */ private validateAllFields(control: AbstractControl) { // Check control type is Formgroup or not if (control instanceof FormGroup) { // Def Formgroup const group = control as FormGroup; // Loop on formcontrols in formgroup for (const field in group.controls) { // Def control const c = group.controls[field]; // Recursive call this.validateAllFields(c); } } else { // Validate control control.updateValueAndValidity({ onlySelf: false }); } } /** * * @param event :Event * @param control :CanFormField * @param spinner * @param input * @param form :FormGroup * Upload File and add res file path to form value */ public upload( event: any, control: PpFormField, spinner: any, input: any, form: FormGroup ) { if (event.target.files && event.target.files.length) { // Check for spinner if (spinner) { // Visible spinner spinner._elementRef.nativeElement.style.display = 'block'; } if ( control.type === 'image' && !control.file?.multipleSelect && control.file?.formatImage ) { // const dialogRef = this.dialog.open(CanImageFormatterComponent, { // data: { event: event, file: event.target.files[0], control: control }, // width: control.file.formatImage.modalWidth // ? control.file.formatImage.modalWidth + 'px' // : '600px', // maxHeight: '90vh', // disableClose: true, // panelClass: 'image-modal', // }); // dialogRef.afterClosed().subscribe((dialogResult) => { // if (dialogResult) { // form.controls[control.name].setValue( // CanHelper.mapValueWithApiKey(dialogResult, control.file.apiKey) // ); // // Form Value Change // this.formValueChange(control, form); // } // if (spinner) { // // Hide spinner // spinner._elementRef.nativeElement.style.display = 'none'; // } // // clear input // input.clear(); // }); } else { // initialze form data const formData: FormData = new FormData(); // Loop over Files for (let i = 0; i < event.target.files.length; i++) { // file def's const file = event.target.files[i]; // add file to formdata formData.append(control.file?.payloadName!, file, file.name); } // clear input input.clear(); // call the api this.uploadSubscription = this.apiService .request({ ...control.file?.api!, config: this.config }, formData) .subscribe( (res: any) => { if (control.file?.multipleSelect) { // Check whether the value is array or not if (!_.isArray(form.controls[control.name].value)) { form.controls[control.name].setValue([]); } // Get image paths const files = PpHelper.getValueWithApiKey( res, control.file.apiKey ); for (let each of files) { // Add image paths to form control value form.controls[control.name].value.push(each); // Check for limit if ( control.file.limit && form.controls[control.name].value && form.controls[control.name].value.length === control.file.limit ) { break; } } } else { form.controls[control.name].setValue( PpHelper.getValueWithApiKey(res, control.file?.apiKey!) ); if(control.file && control.file.viewValue){ const imageValue = PpHelper.getValueWithApiKey( res, control.file?.viewValue?.apiViewValueKey!, control.file?.viewValue?.viewValueKeySeparator, true ); control.file.viewValue.value = !PpHelper.isEmpty(imageValue) ? imageValue : undefined } } // Form Value Change this.formValueChange(control, form); // Check for spinner if (spinner) { // Hide spinner spinner._elementRef.nativeElement.style.display = 'none'; } }, (err: any) => { // Check for spinner if (spinner) { // Hide spinner spinner._elementRef.nativeElement.style.display = 'none'; } } ); } } } /** * * @param key : string * @param index : number * * Remove image or doc from form value */ public removeFile( event: any, control: PpFormField, index: number, form: FormGroup ) { event.stopPropagation(); form.controls[control.name].value.splice(index, 1); } /** * * @param event :MatChipInputEvent * @param key :string * @param form :FormGroup * Add tag to form control */ public addTags( event: MatChipInputEvent, control: PpFormField, form: FormGroup ): void { const input = event.input; const value = event.value; // Add tag if ((value || '').trim()) { // If array then push the new value if (_.isArray(form.controls[control.name].value)) { form.controls[control.name].value.push(value); // Else create new array with value } else { form.controls[control.name].setValue([value]); } } // Reset the input value if (input) { input.value = ''; } } /** * * @param value * @param control :CanFormField * @param input * @param form :FormGroup * Add tag to form control from autocomplete */ public addTagsFromAutocomplete( value: any, control: PpFormField, input: any, form: FormGroup ) { // If array then push the new value if (_.isArray(form.controls[control.name].value)) { form.controls[control.name].value.push(value); // Else create new array with value } else { form.controls[control.name].setValue([value]); } // Reset the input value input.value = ''; } /** * * @param key : any * @param values :CanValueItem[] * Return Value to be displayed as tag */ public getDisplayValueForTags(key: any, values: PpValueItem[]) { // Check for values if (values && values.length) { // Find value index const index = values.findIndex((val) => val.value === key); // If value present if (index > -1) { // Return display value return values[index].viewValue; } } return undefined; } /** * * @param options :CanValueItem[] * @param selectedValues :Array * Filter values that are not selected */ public getDisplayOptionsForTags( options: PpValueItem[], selectedValues: Array ) { return options.filter((option) => { // Check for selectedValues if (selectedValues && selectedValues.length) { // Find index in selectedValues const index = selectedValues.findIndex((each) => each === option.value); // If value is selected then remove from array if (index > -1) { return false; } } return true; }); } /** * * @param formField :CanFormField * Emit when form value changes */ public formValueChange(formField: PpFormField, form: FormGroup): void { // Emit Before Submit Event to Parent this.beforeSubmitFormValue.emit(form.value); // Set Form Dirty; this.setFormDirty(form); // Set value change in form control this.setFormControlDirty(formField, form); // change Relative Values that are affected by this field this.changeRelativeValues(formField, form); // Validate all fields on any change this.validateAllFields(this.form); // Hide or show keys on basis of value this.hideFormFields( formField.hideFormFields!, form.controls[formField.name].value ); } /** * * @param formField: CanFormField * @param form: FormGroup */ private changeRelativeValues(formField: PpFormField, form: FormGroup) { // Check for relative Set Value fields if (formField.relativeSetFields && formField.relativeSetFields.length) { formField.relativeSetFields.forEach((element) => { // Check type if (element.type === 'same') { // Set same field value to them const value = _.isEmpty( PpHelper.getValueWithApiKey(form.getRawValue(), formField.name) ) ? undefined : PpHelper.getValueWithApiKey(form.getRawValue(), formField.name); this.setFormFieldValue(element.key, value, form); } else if (element.type === 'different') { // Check differentType if (element.differentType === 'dataSource') { // Check for datasource if ( formField.type === 'select' && formField.select && formField.select.dataSource ) { const data = this.getObjectFromArray( formField.select.dataSource, formField.select.apiValueKey!, form.controls[formField.name].value ); const value = _.isEmpty( PpHelper.getValueWithApiKey(data!, element.value!) ) ? undefined : PpHelper.getValueWithApiKey(data!, element.value!); this.setFormFieldValue(element.key, value, form); } else if ( formField.type === 'autocomplete' && formField.autoComplete && formField.autoComplete.dataSource ) { const data = this.getObjectFromArray( formField.autoComplete.dataSource, formField.autoComplete.apiValueKey!, form.controls[formField.name].value ); const value = _.isEmpty( PpHelper.getValueWithApiKey(data!, element.value!) ) ? undefined : PpHelper.getValueWithApiKey(data!, element.value!); this.setFormFieldValue(element.key, value, form); } // Remove value if (formField.select && !formField.select.dataSource) { this.setFormFieldValue(element.key, undefined, form); } } } }); } // Check for related fields if (formField.relatedTo && formField.relatedTo.length) { // Set related fields values this.getRelatedFieldsData(formField); } } /** * * @param formField :CanFormField * Set Related Fields Values */ private getRelatedFieldsData(formField: PpFormField) { // Loop relatedTo keys formField.relatedTo?.forEach((element) => { // Get related field const relatedField = this.getRelativeFormField(element); // Check for relatedField if (relatedField) { // Check for type if ( relatedField.type === 'select' || (relatedField.type === 'tags' && relatedField.tags?.type === 'select') ) { // Check for select type 'relative if (relatedField.select && relatedField.select.type === 'relative') { // Init emptyVariables let emptyVariables = false; // To remove instance of object const api: PpHttpOptions = Object.assign( {}, relatedField.select.api ); // Get variables from url const variables = PpHelper.getVariablesFromUrl(api.apiPath); // Replace each variable with value for (const variable of variables) { const value = PpHelper.getValueWithApiKey( this.form.getRawValue(), variable ); // If value is not empty, set value if (!PpHelper.isEmpty(value)) { api.apiPath = api.apiPath.replace('${' + variable + '}', value); } else { // Else, set emptyVariables to true and break loop emptyVariables = true; break; } } // Check for emptyVariables if (!emptyVariables) { // Check for query params const queryParams = relatedField.select.relativeApiInfo && relatedField.select.relativeApiInfo.queryParams ? relatedField.select.relativeApiInfo.queryParams : undefined; if (queryParams && queryParams.length) { // Check for api params if (!api.params) { // Init params api.params = new PpHttpParams(); } for (const eachParam of queryParams) { // Get Param Value const value = PpHelper.mapValueWithApiKey( this.form.getRawValue(), eachParam.value ); // If value is not empty, set value if (!PpHelper.isEmpty(value)) { if (eachParam.regex) { api.params = api.params.set( eachParam.key, JSON.stringify({ ilike: '%' + value + '%' }) ); } else { api.params = api.params.set(eachParam.key, value); } } else { // Else, set emptyVariables to true and break loop emptyVariables = true; break; } } } // If there is no empty variables if (!emptyVariables) { // Init related field values relatedField.values = []; // Api request this.selectSubscription = this.apiService .request({ ...api, config: this.config }) .subscribe((res: any) => { // get data const data = PpHelper.getValueWithApiKey( res, relatedField.select?.apiDataKey! ); // Check whether data is array or not if (_.isArray(data) && formField.select) { // Assign dataSource formField.select.dataSource = data; data.forEach((element) => { // Check for convert type 'string'. If yes, then convert it to string. const value = relatedField.select?.convertType === 'string' ? this.dataParserService .getValue( element, relatedField.select?.apiValueKey! ) .toString() : PpHelper.getValueWithApiKey( element, relatedField.select?.apiValueKey! ); // Push object to values relatedField.values?.push({ value: value, viewValue: this.getValueWithApiKey( element, relatedField.select?.apiViewValueKey!, relatedField.select?.viewValueSeparators ), }); }); } }); } } } } } }); } /** * * @param name : string * Get Relative Form Field */ private getRelativeFormField(name: string) { // Return false in case of empty if (PpHelper.isEmpty(name)) { return false; } // Storing the nested key in array const keys = name.split('.'); // Def FormField let formField: PpFormField; // Find Index const index = this.formFields.findIndex((each) => each.name === keys[0]); // If found if (index > -1) { // Assign value formField = this.formFields[index]; // Check for keys length if (keys.length === 1) { // Return formfield return formField; } else { // Nested Object // Loop keys from index 1 as 0 is already assigned for (let i = 1; i < keys.length; i++) { // Check for formField type 'group' if (formField.type === 'group') { // Find index in group form fields const groupFieldIndex = formField.formFields?.findIndex( (each) => each.name === keys[i] ); // If found if ( groupFieldIndex && groupFieldIndex > -1 && formField.formFields ) { // Set that form field formField = formField.formFields[groupFieldIndex]; } else { // Return in case of not found return false; } } else { // Return in case of not found return false; } } // Check the formField type, if it is 'group', then return false if (formField.type !== 'group') { return formField; } else { return false; } } } else { return false; } } /** * * @param name : string * Set Relative Form Field Value */ private setFormFieldValue(name: string, value: any, form: FormGroup) { // Return false in case of empty if (!name) { return false; } // Storing the nested key in array const keys = name.split('.'); // Check whether key is in present in form controls if (keys[0] in form.controls) { // Check key length if (keys.length === 1) { // Set value form.controls[keys[0]].setValue(value); form.controls[keys[0]].markAsDirty(); } else { let fg: any; // Init form fg = form.controls[keys[0]]; // Loop keys for (let i = 1; i < keys.length; i++) { // Check for controls and keys if ('controls' in fg && keys[i] in fg.controls) { // Check for last index if (i === keys.length - 1) { // Set value fg.controls[keys[i]].setValue(value); form.controls[keys[0]].markAsDirty(); } else { // Set form fg = fg.controls[keys[i]]; } } else { return false; } } } } else { return false; } return false; } /** * Submit Form */ public submitForm(): void { if (this.form.valid) { // Disable Submit Button this.disableSubmitButton = true; let valuesObj; if (this.formConfig.sendAll) { valuesObj = Object.assign({}, this.form.getRawValue()); } else { valuesObj = this.getAllEditedValues(this.form); } if(this.formConfig.submitApi?.relativeApiInfo && this.formConfig.submitApi.relativeApiInfo?.queryParams && this.formConfig.submitApi?.relativeApiInfo?.queryParams?.length) { const queryKeys = this.formConfig.submitApi.relativeApiInfo.queryParams.map( el => el.value); let obj: any = {}; _.forEach( this.formFields, (formField) => { if(queryKeys.includes(formField.name)) { obj[formField.name] = this.form.controls[formField.name].value } }) let param = new PpHttpParams() this.formConfig.submitApi.relativeApiInfo.queryParams.forEach( el => { param = param.append(`${el.key}`, `${obj[el.value]}`); }) this.formConfig.submitApi.params = param; } // Remove values that are not required to send this.removeNotIncludedValues(this.formFields, valuesObj, this.form); // Remove 'selectAll' value from multicheckbox this.removeAllFromMultiCheckBox(this.formFields, valuesObj, this.form); // Check for stepper form if (this.stepperForm) { if( this.formConfig.hasAction || this.formConfig.hasAction === undefined ) { if (!PpHelper.isEmpty(valuesObj)) { // Start Loader this.isLoad = true; this.submitFormSubscription = this.apiService .request( { ...this.formConfig.submitApi!, config: this.config }, !PpHelper.isEmpty(this.formConfig.existingData) ? {...this.formConfig.existingData, ...valuesObj } : valuesObj ) .subscribe( (res: any) => { // Show success message this.notificationService.showSuccess( this.formConfig.submitSuccessMessage || 'Saved successfully!' ); // Emit event after form submitted this.formSubmitted.emit(true); this.getData.emit(res); // Enable Submit Button this.disableSubmitButton = false; // Stop Loader this.isLoad = false; }, (error: any) => { // Stop Loader this.isLoad = false; // Enable Submit Button if (error.error instanceof ErrorEvent) { console.error('An error occurred:', error.error.message); } else { console.error( `Backend returned code ${error.status}, ` + `body was: ${JSON.stringify(error.error)}`); } let errorMessage; if (error.error.error && error.error.error.message) { errorMessage = error.error.error.message; } else { errorMessage = 'There were some error(s). Please check your internet connection or try again later.'; } this.errorMsg = errorMessage; this.disableSubmitButton = false; } ); } else { // Emit event after form submitted this.formSubmitted.emit(true); // Enable Submit Button this.disableSubmitButton = false; } } else{ this.nextButtonClick.emit(true); } this.getExistingFormData.emit(!PpHelper.isEmpty(this.formConfig.existingData) ? {...this.formConfig.existingData, ...valuesObj } : valuesObj); this.disableSubmitButton = false; } else { if (!PpHelper.isEmpty(valuesObj)) { // Start Loader this.isLoad = true; this.submitFormSubscription = this.apiService .request( { ...this.formConfig.submitApi!, config: this.config }, valuesObj ) .subscribe( (res: any) => { // Show success message this.notificationService.showSuccess( this.formConfig.submitSuccessMessage || 'Saved successfully!' ); // Emit event after form submitted this.formSubmitted.emit(true); this.getData.emit(res); // Enable Submit Button this.disableSubmitButton = false; // Stop Loader this.isLoad = false; }, (error: any) => { // Stop Loader this.isLoad = false; // Enable Submit Button if (error.error instanceof ErrorEvent) { console.error('An error occurred:', error.error.message); } else { console.error( `Backend returned code ${error.status}, ` + `body was: ${JSON.stringify(error.error)}`); } let errorMessage; if (error.error.error && error.error.error.message) { errorMessage = error.error.error.message; } else { errorMessage = 'There were some error(s). Please check your internet connection or try again later.'; } this.errorMsg = errorMessage; this.disableSubmitButton = false; } ); } else { // Emit event after form submitted this.formSubmitted.emit(true); // Enable Submit Button this.disableSubmitButton = false; } } } } private getAllEditedValues(form: FormGroup) { let dirtyValues: any = {}; Object.keys(form.controls).forEach((key) => { const currentControl: any = form.controls[key]; if (currentControl.dirty) { if (currentControl['controls']) dirtyValues[key] = this.getAllEditedValues( currentControl as FormGroup ); else dirtyValues[key] = currentControl.value; } }); return dirtyValues; } /** * * @param formFields :CanFormField[] * @param values :object * * Remove 'selectAll' from multicheckbox. */ private removeAllFromMultiCheckBox( formFields: PpFormField[], values: any, form: FormGroup ): void { formFields.forEach((formField, index) => { if ( formField.type === 'group' && formField.formFields?.length ) { const fg = form.controls[formField.name] as FormGroup; // Remove remove 'selectAll' value from multicheckbox in group this.removeAllFromMultiCheckBox( formField.formFields, values[formField.name], fg ); } else if ( formField.type === 'array' && formField.formFields?.length ) { const fa = form.controls[formField.name] as FormArray; // Remove remove 'selectAll' value from multicheckbox in formArray if (values[formField.name]?.length) { values[formField.name].forEach((value: any, index: number) => { const fg = fa.controls[index] as FormGroup; this.removeAllFromMultiCheckBox(formField.formFields!, value, fg); }); } } else { if (formField.type === 'multicheckbox' && formField?.values?.length && values[formField.name]?.length) { if(values[formField.name][0] === 'selectAll') { values[formField.name].shift(); } else { values[formField.name] = _.filter(values[formField.name], value => value !== 'selectAll'); } } } }); } /** * * @param formFields :CanFormField[] * @param values :object * * Remove values that are not required to send */ private removeNotIncludedValues( formFields: PpFormField[], values: any, form: FormGroup ): void { // Loop Form Fields formFields.forEach((formField) => { // Check for notIncluded in formField if ( formField.send === 'notSend' || (formField.send === 'sendOnModified' && !form.controls[formField.name].dirty) ) { // Delete from object delete values[formField.name]; } else { // Check for type 'group' if ( formField.type === 'group' && formField.formFields && formField.formFields.length ) { const fg = form.controls[formField.name] as FormGroup; // Remove values from group that are not required to send this.removeNotIncludedValues( formField.formFields, values[formField.name], fg ); } else if ( formField.type === 'array' && formField.formFields && formField.formFields.length ) { const fa = form.controls[formField.name] as FormArray; // Remove values from array that are not required to send if (values[formField.name] && values[formField.name].length) { values[formField.name].forEach((value: any, index: number) => { const fg = fa.controls[index] as FormGroup; this.removeNotIncludedValues(formField.formFields!, value, fg); }); } } } }); } /** * * @param data :Array * @param key :string * @param value */ private getObjectFromArray(data: Array, key: string, value: any) { if (data && data.length) { // Find value index const index = data.findIndex((val: any) => val[key] === value); // If value present if (index > -1) { // Return value return data[index]; } } return null; } /** * Set Form Dirty */ private setFormDirty(form: FormGroup): void { this.form.markAsDirty(); form.markAsDirty(); } /** * * @param formFields :CanFormField * Set value has been changed in form field */ private setFormControlDirty(formField: PpFormField, form: FormGroup): void { form.controls[formField.name].markAsDirty(); } /** * * @param hideFields :CanHideFormFields * @param value * * Hide or show fields onn basis on response */ private hideFormFields(hideFields: PpHideFormFields, value: any): void { // Check for keys if (hideFields && hideFields.keys && hideFields.keys.length) { // Loop keys hideFields.keys.forEach((key) => { // Get Formfield on basis of key const formField = this.getRelativeFormField(key); // Check for formfield if (formField) { // Check for operator type switch (hideFields.operator) { case 'equals': formField.isHidden = value === hideFields.value ? true : false; break; case 'notEquals': formField.isHidden = value !== hideFields.value ? true : false; break; default: break; } } }); } } /** * * @param form :FormGroup * @param control :CanFormField * Add new formgroup to formarray */ public addNewToFormArray(form: FormGroup, control: PpFormField): void { // Def's formarray const fa = form.controls[control.name] as FormArray; // Add new formgroup to form array fa.push(this.configureForm(control.formFields!)); } /** * * @param data:object * @param key:string * * Fetching Value with API KEY */ public getValueWithApiKey( data: object, key: string, separators?: Array ): any { return PpHelper.isEmpty(PpHelper.getValueWithApiKey(data, key, separators)) ? undefined : PpHelper.getValueWithApiKey(data, key, separators); } /** * Set value to autocomplete * @param valueItem :CanValueItem * @param control :CanFormField * @param form :FormGroup */ public setValueToAutocomplete( valueItem: PpValueItem, control: PpFormField, form: FormGroup ): void { // Set input field value if (control && control.autoComplete) control.autoComplete.autocompleteSearchText = valueItem.viewValue; // Set control value form.controls[control.name].patchValue(valueItem.value); } /** * * @param control : CanFormField */ public getAutocompletes( control: PpFormField, form: FormGroup, spinner: any ): void { if (control && control.autoComplete) control.autoComplete.minCharacters = control.autoComplete.minCharacters ? control.autoComplete.minCharacters : 1; // Initialize for autocomplete values control.values = []; // Remove subscription if (this.autoCompleteSubscription) { this.autoCompleteSubscription.unsubscribe(); } // Check for spinner if (spinner) { // Hide spinner spinner._elementRef.nativeElement.style.display = 'none'; } // Check for text if ( control.autoComplete?.autocompleteSearchText && control.autoComplete.autocompleteSearchText.trim() && control.autoComplete.autocompleteSearchText.length >= control.autoComplete?.minCharacters! ) { // Check for spinner if (spinner) { // Visible spinner spinner._elementRef.nativeElement.style.display = 'block'; } // Initialize params if ( !control.autoComplete?.api?.params && control.autoComplete && control.autoComplete.api ) { control.autoComplete.api.params = new PpHttpParams(); control.autoComplete.api.params = control.autoComplete.api.params.append( control.autoComplete?.autocompleteParamKeys![0], control.autoComplete.autocompleteSearchText ); } // Get autocomplete list this.autoCompleteSubscription = this.apiService .request({ ...control.autoComplete?.api!, config: this.config }) .subscribe( (res: any) => { // Initialize for autocomplete values control.values = []; const data = PpHelper.getValueWithApiKey( res, control.autoComplete?.apiDataKey! ); if (control.autoComplete) control.autoComplete.dataSource = data; if (_.isArray(data)) { // Add values to particular format for (const each of data) { control.values.push({ value: this.getValueWithApiKey( each, control.autoComplete?.apiValueKey! ), viewValue: this.getValueWithApiKey( each, control.autoComplete?.apiViewValueKey!, control.autoComplete?.viewValueSeparators ), }); } } // Check for spinner if (spinner) { // Hide spinner spinner._elementRef.nativeElement.style.display = 'none'; } }, (err: any) => { // Check for spinner if (spinner) { // Hide spinner spinner._elementRef.nativeElement.style.display = 'none'; } } ); } // Check for empty if ( !control.autoComplete?.autocompleteSearchText && form.controls[control.name].value ) { form.controls[control.name].setValue(undefined); this.formValueChange(control, form); } } /** * * @param displayCondition :CanConditionConfig * @param form :FormGroup * Check whether to display field action or not */ public displayValidate(displayCondition: PpConditionConfig, form: FormGroup) { // Checking Display Condition Existence if (displayCondition) { return this.conditionValidationService.validate( displayCondition, form.getRawValue() ); } return true; } /** * Back button clicked event */ public back(): void { this.backButtonClick.emit(true); } /** * * @param value :CanValueItem * @param control :CanFormField * @param form :FormGroup * Check for multiCheckbox value */ public isMultiCheckboxFieldChecked( value: PpValueItem, control: PpFormField, form: FormGroup ): boolean { // Check whether value is array if (_.isArray(form.controls[control.name].value)) { // Check for index if ( form.controls[control.name].value.findIndex( (each: any) => each === value.value ) > -1 ) { // If found, return true return true; } else { return false; } } else { return false; } } /** * * @param value :CanValueItem * @param control :CanFormField * @param form :FormGroup * * Event that trigger when multiCheckbox value is changed */ public multiSelectCheckboxChange( value: PpValueItem, control: PpFormField, form: FormGroup ): void { // Check whether value is array if (_.isArray(form.controls[control.name].value)) { // In case 'Select All' checkbox is selected/deselected as current value if (value.value === 'selectAll') { const indexOfAllCheckBox = (form.controls[control.name].value as PpValueItem[]).findIndex( (each: any) => each === 'selectAll' ); if (indexOfAllCheckBox > -1) { // If 'Select All' is deselected, deselect all values... form.controls[control.name].setValue([]); } else { // If 'Select All' is selected, select all values... form.controls[control.name].setValue((control.values?.map(el => el.value))); } return; } else { /* If 'Select All' is not selected as current value, Check if there already exists any selected value as 'Select All' */ const indexOfAllCheckBox = form.controls[control.name].value.findIndex( (each: any) => each === 'selectAll' ); // If it exists, then deselect it ('Select All'). if (indexOfAllCheckBox > -1) { form.controls[control.name].value.splice(indexOfAllCheckBox, 1); } } // Find index const index = form.controls[control.name].value.findIndex( (each: any) => each === value.value ); // If index is found, remove the value if (index > -1) { form.controls[control.name].value.splice(index, 1); } else { // Else, add the value form.controls[control.name].value.push(value.value); } } else { if (value.value === 'selectAll') { /* In case the first value is selected as 'Select All', then select all... */ form.controls[control.name].setValue((control.values?.map(el => el.value))); return; } // Set value as empty array form.controls[control.name].setValue([]); // Push value to array form.controls[control.name].value.push(value.value); } } }