import { DocumentClassification } from './../../../../shared/models/document-classification.interface'; import { Category } from './../../../../shared/models/category.interface'; import { DocumentSubclassificationsService } from './../../../../shared/services/administration/document-subclassifications.service'; import { DocumentSubclassification } from './../../../../shared/models/document-subclassification.interface'; import { IPowerClient } from './../../../../shared/models/ipower-client.interface'; import { Template } from './../../../../shared/models/template.interface'; import { Component, Input, OnInit } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { CategoriesService } from 'src/app/shared/services/administration/categories.service'; import { DocumentClassificationsService } from 'src/app/shared/services/administration/document-classifications.service'; import { IpowerClientsService } from 'src/app/shared/services/administration/ipower-clients.service'; import { ErrorHandlingService } from 'src/app/shared/services/error-handling/error-handling.service'; @Component({ selector: 'app-template-form', templateUrl: './template-form.component.html', styleUrls: ['./template-form.component.scss'] }) export class TemplateFormComponent implements OnInit { /* * Inputs */ @Input() dialogLayout: boolean = false; // Controls .scss classes to allow for better dispay in a dialog. @Input() requiredFields: string[] | boolean = false; // True/False indicates that all/none are required. String[] specifies the form controls required. @Input() displayValidationMessagesEvenIfPristine: boolean = false; // Serves for manually treating the controls as dirty. @Input() excludedFormControls: string[]; // Specifies the form controls to be removed and NOT displayed. /* * Reactive Form */ templateForm = this.fb.group({ documentClassification: [null], categoryName: [null], categoryCode: [null], iPowerClientName: [null], iPowerClientCode: [null], documentSubclassification: [null], abbyyTemplateCode: [null] }); initiallySetFormValue: Template; /* * Other Variables */ documentClassificationsList: DocumentClassification[]; categoryNameSuggestions: string[]; categoryCodeSuggestions: string[]; categorySuggestions: Category[]; selectedCategory: Category; iPowerClientNameSuggestions: string[]; iPowerClientCodeSuggestions: string[]; iPowerClientSuggestions: IPowerClient[]; selectedIPowerClient: IPowerClient; documentSubclassificationsList: DocumentSubclassification[]; availableDocumentSubclassifications: DocumentSubclassification[]; subCategoryCodeDisabled: boolean = true; /* * Constructor & Initialisers */ constructor( private fb: FormBuilder, private documentClassificationsService: DocumentClassificationsService, private categoriesService: CategoriesService, private iPowerClientsService: IpowerClientsService, private documentSubclassificationsService: DocumentSubclassificationsService, private errorHandlingService: ErrorHandlingService ) { } ngOnInit(): void { this.initData(); this.initValidators() this.excludeFields(); } initData() { this.documentClassificationsService.getAll().subscribe( value => this.documentClassificationsList = value, err => this.errorHandlingService.showHttpResponseError(err) ); // This is NOT the list of Subclassifications used for the dropdown. this.documentSubclassificationsService.getAll().subscribe( value => this.documentSubclassificationsList = value, err => this.errorHandlingService.showHttpResponseError(err) ); } // TODO: Have this mechanism offer the use of custom validators too. initValidators() { if (!this.requiredFields) { return; } // In a true/false case. if (typeof this.requiredFields == 'boolean') { // If true, enable the required validator for all controls. if (this.requiredFields) { Object.keys(this.templateForm.controls).forEach(key => this.templateForm.controls[key].setValidators(Validators.required)); } // If false, do nothing. return; } // If it was a string array, enable the validators for all provided field-names. (this.requiredFields).forEach(field => this.templateForm.controls[field].setValidators(Validators.required)) } excludeFields() { if (!this.excludedFormControls) { return; } this.excludedFormControls.forEach(field => this.templateForm.removeControl(field)); } /* * Auto-suggest & Auto-complete Category */ autosuggestCategoryName(event) { if (!event.query || event.query.length < 3) { this.categoryNameSuggestions = []; return; } let classId = this.templateForm.get('documentClassification').value ? this.templateForm.get('documentClassification').value.classificationId : null; this.categoriesService.autosuggestCategoryName(event.query, classId).subscribe( (values: Category[]) => { let temp: string[] = []; this.categorySuggestions = values; values.map(val => temp.push(val.categoryName)); this.categoryNameSuggestions = temp }, err => this.errorHandlingService.showHttpResponseError(err) ); } autosuggestCategoryCode(event) { if (event.query.length < 3) { this.categoryCodeSuggestions = []; return; } let classId = this.templateForm.get('documentClassification').value ? this.templateForm.get('documentClassification').value.classificationId : null; this.categoriesService.autosuggestCategoryCode(event.query, classId).subscribe( (values: Category[]) => { let temp: string[] = []; this.categorySuggestions = values; values.map(val => temp.push(val.categoryCode)); this.categoryCodeSuggestions = temp }, err => this.errorHandlingService.showHttpResponseError(err) ); } categoryNameSelected(name: string) { this.selectedCategory = this.categorySuggestions.find(cat => cat.categoryName == name); this.templateForm.get('categoryCode')?.patchValue(this.selectedCategory.categoryCode); this.templateForm.updateValueAndValidity(); } categoryCodeSelected(code: string) { this.selectedCategory = this.categorySuggestions.find(cat => cat.categoryCode == code); this.templateForm.get('categoryName')?.patchValue(this.selectedCategory?.categoryName); this.templateForm.updateValueAndValidity(); } /* * Auto-suggest & Auto-complete IPower Client */ autosuggestIPowerClientName(event): void { if (!event.query || event.query.length < 3) { this.iPowerClientNameSuggestions = []; return; } this.iPowerClientsService.getClientsByNameOnly(event.query).subscribe( (values: IPowerClient[]) => { this.iPowerClientSuggestions = values; const temp: string[] = []; values.map(val => temp.push(val.name)); this.iPowerClientNameSuggestions = temp; }, err => this.errorHandlingService.showHttpResponseError(err) ); } autosuggestIPowerClientCode(event): void { if (event.query.length < 3) { this.iPowerClientCodeSuggestions = []; return; } this.iPowerClientsService.getClientsByCodeOnly(event.query).subscribe( (values: IPowerClient[]) => { this.iPowerClientSuggestions = values; const temp: string[] = []; values.map(val => temp.push(val.clientCode)); this.iPowerClientCodeSuggestions = temp; }, err => this.errorHandlingService.showHttpResponseError(err) ); } iPowerClientNameSelected(name: string) { this.selectedIPowerClient = this.iPowerClientSuggestions.find(client => client.name == name); this.templateForm.get('iPowerClientCode')?.patchValue(this.selectedIPowerClient ? this.selectedIPowerClient.clientCode : ''); this.templateForm.updateValueAndValidity(); } iPowerClientCodeSelected(code: string) { this.selectedIPowerClient = this.iPowerClientSuggestions.find(client => client.clientCode == code); this.templateForm.get('iPowerClientName')?.patchValue(this.selectedIPowerClient ? this.selectedIPowerClient.name : ''); this.templateForm.updateValueAndValidity(); } /* * Other Methods */ documentClassificationSelected(selection: DocumentClassification) { this.availableDocumentSubclassifications = this.documentSubclassificationsList.filter(element => element.documentClassification.classificationId == selection.classificationId); this.templateForm.get('documentSubclassification')?.setValue(this.availableDocumentSubclassifications[0]); //TODO CHECK this.subCategoryCodeDisabled = false; } /* * Utility Methods */ // Auto-suggest inputs - We have to ensure our reactive form holds values that represent the selectedCategory, otherwise truncate it. syncSelectedCategory() { // Ιf our form has no value, truncate the selectedCategory either way. if (!this.templateForm.get('categoryName')?.value && !this.templateForm.get('categoryCode').value) { this.selectedCategory = null; return; } // If both or either of our form's values match the selectedCategory's and the other one doesn't have a value, all is good. // Just sync the values in case one is missing. Otherwise truncate the selectedCategory. if ( this.templateForm.get('categoryName')?.value == this.selectedCategory.categoryName || this.templateForm.get('categoryCode')?.value == this.selectedCategory.categoryCode ) { this.selectedCategory.categoryName = this.templateForm.get('categoryName')?.value; this.selectedCategory.categoryCode = this.templateForm.get('categoryCode')?.value; } // If both our values were different from the selectedCategory's, truncate it. This is an extremely abnormal scenario. else { console.error('WARNING - syncSelectedCategory()', 'Both of our form\'s values were different from the selectedCategory\'s.'); this.selectedCategory = null; this.templateForm.get('categoryName')?.setValue(''); this.templateForm.get('categoryCode')?.setValue(''); this.templateForm.updateValueAndValidity(); } } // Auto-suggest inputs - We have to ensure our reactive form holds values that represent the selectedIPowerClient, otherwise truncate it. syncSelectedIPowerClient() { // Ιf our form has no value, truncate the selectedIPowerClient either way. if (!this.templateForm.get('iPowerClientName')?.value && !this.templateForm.get('iPowerClientCode')?.value) { this.selectedIPowerClient = null; return; } // If both or either of our form's values match the selectedIPowerClient's and the other one doesn't have a value, all is good. // Just sync the values in case one is missing. Otherwise truncate the selectedIPowerClient. if ( this.templateForm.get('iPowerClientName')?.value == this.selectedIPowerClient.name || this.templateForm.get('iPowerClientCode')?.value == this.selectedIPowerClient.clientCode ) { this.selectedIPowerClient.name = this.templateForm.get('iPowerClientName')?.value; this.selectedIPowerClient.clientCode = this.templateForm.get('iPowerClientCode')?.value; } // If both our values were different from the selectedIPowerClient's, truncate it. This is an extremely abnormal scenario. else { console.error('WARNING - syncSelectedIPowerClient()', 'Both of our form\'s values were different from the selectedIPowerClient\'s.'); this.selectedIPowerClient = null; this.templateForm.get('iPowerClientName')?.setValue(''); this.templateForm.get('iPowerClientCode')?.setValue(''); this.templateForm.updateValueAndValidity(); } } /* * API methods */ public resetForm(): void { this.templateForm.reset(); this.selectedCategory = null; this.selectedIPowerClient = null; } public formValue(): Template { // A field ('documentSubclassification') may be excluded, so we have to check for that too. let subCatCode = this.templateForm.get('documentSubclassification') ? this.templateForm.get('documentSubclassification').value : null; let abbyy = this.templateForm.get('abbyyTemplateCode') ? this.templateForm.get('abbyyTemplateCode').value : null; let docClass = this.templateForm.get('documentClassification') ? this.templateForm.get('documentClassification').value : null; // We keep track of those two using object-variables separate from our ReactiveForm. // Thus, we now have to make sure the ReactiveForm really has values that match those objects, before we forward them. // Also, keep in mind that our auto-suggest inputs limit the user in choosing one of their values. this.syncSelectedCategory(); this.syncSelectedIPowerClient(); let formValue: Template = { id: this.initiallySetFormValue ? this.initiallySetFormValue.id : null, category: this.selectedCategory, subCategoryCode: subCatCode ? subCatCode.subclassificationName : '', abbyyTemplate: abbyy, client: this.selectedIPowerClient, documentClassification: docClass }; return formValue; } public setValue(value: Template): void { if (!value) { return; } this.initiallySetFormValue = value; // If a documentClassification is already selected, enable the subclassification dropdown. this.subCategoryCodeDisabled = !value.documentClassification; this.templateForm.get('documentClassification')?.setValue(value.documentClassification); // Having set the documentClassification -and provided there was one- we must also set the availableDocumentSubclassifications. this.availableDocumentSubclassifications = this.documentSubclassificationsList.filter(element => element.documentClassification.classificationId == value.documentClassification.classificationId ); this.selectedCategory = value.category; this.templateForm.get('categoryName')?.setValue(value.category.categoryName); this.templateForm.get('categoryCode')?.setValue(value.category.categoryCode); this.selectedIPowerClient = value.client; this.templateForm.get('iPowerClientName')?.setValue(value.client.name); this.templateForm.get('iPowerClientCode')?.setValue(value.client.clientCode); this.templateForm.get('abbyyTemplateCode')?.setValue(value.abbyyTemplate); // To set the subcategory/subclassification we also have to make sure the documentClassification is the same. // WARNING: Since we have enabled the [forceSelection] option of the , // if the availableDocumentSubclassifications are not set, documentSubclassification won't be displayed. this.templateForm.get('documentSubclassification').setValue( this.documentSubclassificationsList.find(subClass => ( subClass.subclassificationName == value.subCategoryCode && subClass.documentClassification.classificationId == value.documentClassification.classificationId )) ); this.templateForm.updateValueAndValidity(); } public isValid(): boolean { return this.templateForm.valid; } }