diff --git a/package-lock.json b/package-lock.json index ab35e052..a2e35f6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@angular/platform-browser-dynamic": "^19.2.14", "@angular/router": "^19.2.14", "@danielmoncada/angular-datetime-picker": "^19.0.0", - "@pega/auth": "~0.2.31", + "@pega/auth": "~0.2.34", "@tinymce/tinymce-angular": "^8.0.1", "core-js": "^3.39.0", "dayjs": "^1.11.13", @@ -48,7 +48,7 @@ "@angular/compiler-cli": "^19.2.14", "@angular/language-service": "^19.2.14", "@pega/configs": "^0.16.3", - "@pega/constellationjs": "^0.25.1", + "@pega/constellationjs": "~25.1.0", "@pega/pcore-pconnect-typedefs": "~4.1.0", "@playwright/test": "^1.54.2", "@types/jasmine": "~5.1.4", @@ -6977,9 +6977,9 @@ "optional": true }, "node_modules/@pega/auth": { - "version": "0.2.31", - "resolved": "https://registry.npmjs.org/@pega/auth/-/auth-0.2.31.tgz", - "integrity": "sha512-QQxuiK1Xzl9O/dGZ6iKuCRcncuhJZqYQHu9WklGODtp16OwA00SgRe2Qapit4/h/asj8lWCIquSnQ2NoNTEJlA==", + "version": "0.2.34", + "resolved": "https://registry.npmjs.org/@pega/auth/-/auth-0.2.34.tgz", + "integrity": "sha512-Npm8LmoYCckU0QEVoinPGAMArFNsT8LfJGF02aPFUlVeOfO8KEKOFbbyTtp2Mrv1pxA1fPDorjjEcP2jxLCsLw==", "license": "Apache-2.0", "dependencies": { "node-fetch": "^3.3.2", @@ -7893,9 +7893,9 @@ } }, "node_modules/@pega/constellationjs": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@pega/constellationjs/-/constellationjs-0.25.1.tgz", - "integrity": "sha512-Tu+4ktczEnnbkz87uFPVeECcPA7QFDhQ3/B6ooBhrIZG4GUbnhvma8neYwNUzV2fevM4eSxk57qU6nlGZyjfqA==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@pega/constellationjs/-/constellationjs-25.1.0.tgz", + "integrity": "sha512-UVGT4sAMtnPNYmZb7KU+Lqt4mxBtlmxric0o1sgAt5g9t2X40Gj/HTNeki9+5bLrQ3Zri4JIC+StRphzFGCHqA==", "dev": true, "license": "See LICENSE in LICENSE file or 'ConstellationJS Engine License.pdf' in dist/", "dependencies": { diff --git a/package.json b/package.json index 15373739..57b19460 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@angular/platform-browser-dynamic": "^19.2.14", "@angular/router": "^19.2.14", "@danielmoncada/angular-datetime-picker": "^19.0.0", - "@pega/auth": "~0.2.31", + "@pega/auth": "~0.2.34", "@tinymce/tinymce-angular": "^8.0.1", "core-js": "^3.39.0", "dayjs": "^1.11.13", @@ -94,7 +94,7 @@ "@angular/compiler-cli": "^19.2.14", "@angular/language-service": "^19.2.14", "@pega/configs": "^0.16.3", - "@pega/constellationjs": "^0.25.1", + "@pega/constellationjs": "~25.1.0", "@pega/pcore-pconnect-typedefs": "~4.1.0", "@playwright/test": "^1.54.2", "@types/jasmine": "~5.1.4", diff --git a/packages/angular-sdk-components/src/lib/_components/field/auto-complete/auto-complete.component.ts b/packages/angular-sdk-components/src/lib/_components/field/auto-complete/auto-complete.component.ts index 96180547..fef3bf4e 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/auto-complete/auto-complete.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/auto-complete/auto-complete.component.ts @@ -1,14 +1,14 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy, Output, EventEmitter } from '@angular/core'; +import { Component, EventEmitter, OnInit, Output, forwardRef, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatOptionModule } from '@angular/material/core'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; import { DatapageService } from '../../../_services/datapage.service'; import { handleEvent } from '../../../_helpers/event-util'; @@ -44,67 +44,23 @@ interface AutoCompleteProps extends PConnFieldProps { forwardRef(() => ComponentMapperComponent) ] }) -export class AutoCompleteComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; +export class AutoCompleteComponent extends FieldBase implements OnInit { + protected dataPageService = inject(DatapageService); + @Output() onRecordChange: EventEmitter = new EventEmitter(); - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; configProps$: AutoCompleteProps; - label$ = ''; - value$ = ''; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; options$: any[]; - componentReference = ''; - testId: string; listType: string; columns = []; - helperText: string; - placeholder: string; - - fieldControl = new FormControl('', null); parameters: {}; - hideLabel: boolean; filteredOptions: Observable; filterValue = ''; - actionsApi: object; - propName: string; - - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils, - private dataPageService: DatapageService - ) {} - - async ngOnInit(): Promise { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - // call updateSelf when initializing - // this.updateSelf(); - await this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } + // Override ngOnInit method + override async ngOnInit(): Promise { + super.ngOnInit(); this.filteredOptions = this.fieldControl.valueChanges.pipe( startWith(''), @@ -119,54 +75,32 @@ export class AutoCompleteComponent implements OnInit, OnDestroy { this.fieldControl.setValue(this.value$); } - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - private _filter(value: string): string[] { const filterVal = (value || this.filterValue).toLowerCase(); return this.options$?.filter(option => option.value?.toLowerCase().includes(filterVal)); } - // Callback passed when subscribing to store change - async onStateChange() { - await this.checkAndUpdate(); - } - - async checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - await this.updateSelf(); - } - } + /** + * Updates the component when there are changes in the state. + */ + override async updateSelf(): Promise { + // Resolve configuration properties + this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as AutoCompleteProps; - // updateSelf - async updateSelf(): Promise { - // starting very simple... + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - // moved this from ngOnInit() and call this from there instead... - this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as AutoCompleteProps; + // Set component specific properties + const { value, listType, parameters } = this.configProps$; - if (this.configProps$.value != undefined) { - const index = this.options$?.findIndex(element => element.key === this.configProps$.value); - this.value$ = index > -1 ? this.options$[index].value : this.configProps$.value; + if (value != undefined) { + const index = this.options$?.findIndex(element => element.key === value); + this.value$ = index > -1 ? this.options$[index].value : value; this.fieldControl.setValue(this.value$); } - this.setPropertyValuesFromProps(); - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; + this.listType = listType; + this.parameters = parameters; const context = this.pConn$.getContextName(); const { columns, datasource } = this.generateColumnsAndDataSource(); @@ -174,34 +108,7 @@ export class AutoCompleteComponent implements OnInit, OnDestroy { if (columns) { this.columns = this.preProcessColumns(columns); } - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - this.componentReference = this.pConn$.getStateProps().value; if (this.listType === 'associated') { const optionsList = this.utils.getOptionList(this.configProps$, this.pConn$.getDataObject('')); // 1st arg empty string until typedef marked correctly this.setOptions(optionsList); @@ -211,27 +118,6 @@ export class AutoCompleteComponent implements OnInit, OnDestroy { const results = await this.dataPageService.getDataPageData(datasource, this.parameters, context); this.fillOptions(results); } - - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); - } - } - - setPropertyValuesFromProps() { - this.testId = this.configProps$.testId; - this.label$ = this.configProps$.label; - this.placeholder = this.configProps$.placeholder || ''; - this.displayMode$ = this.configProps$.displayMode; - this.listType = this.configProps$.listType; - this.hideLabel = this.configProps$.hideLabel; - this.helperText = this.configProps$.helperText; - this.parameters = this.configProps$?.parameters; } generateColumnsAndDataSource() { @@ -313,10 +199,6 @@ export class AutoCompleteComponent implements OnInit, OnDestroy { } fieldOnChange(event: Event) { - // this works - this.pConn$.setValue( this.componentReference, `property: ${this.componentReference}`); - // this works - this.pConn$.setValue( this.componentReference, this.fieldControl.value); - // PConnect wants to use changeHandler for onChange - // this.angularPConnect.changeHandler( this, event); const value = (event.target as HTMLInputElement).value; this.filterValue = value; handleEvent(this.actionsApi, 'change', this.propName, value); @@ -337,21 +219,4 @@ export class AutoCompleteComponent implements OnInit, OnDestroy { this.onRecordChange.emit(value); } } - - getErrorMessage() { - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/check-box/check-box.component.ts b/packages/angular-sdk-components/src/lib/_components/field/check-box/check-box.component.ts index 65ab0327..bf830586 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/check-box/check-box.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/check-box/check-box.component.ts @@ -1,12 +1,11 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, OnInit, forwardRef, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatOptionModule } from '@angular/material/core'; -import { interval } from 'rxjs'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; import { PConnFieldProps } from '../../../_types/PConnProps.interface'; import { deleteInstruction, insertInstruction, updateNewInstructions } from '../../../_helpers/instructions-utils'; @@ -36,29 +35,12 @@ interface CheckboxProps extends Omit { styleUrls: ['./check-box.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatCheckboxModule, MatFormFieldModule, MatOptionModule, forwardRef(() => ComponentMapperComponent)] }) -export class CheckBoxComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class CheckBoxComponent extends FieldBase implements OnInit, OnDestroy { configProps$: CheckboxProps; - label$ = ''; - value$: any = ''; caption$?: string = ''; - testId = ''; showLabel$ = false; isChecked$ = false; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; - componentReference = ''; - helperText: string; trueLabel$?: string; falseLabel$?: string; @@ -70,85 +52,31 @@ export class CheckBoxComponent implements OnInit, OnDestroy { selectedvalues: any; referenceList: string; listOfCheckboxes: any[] = []; - actionsApi: object; - propName: string; variant?: string; - fieldControl = new FormControl('', null); - - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); + // Override ngOnInit method + override ngOnInit(): void { + super.ngOnInit(); if (this.selectionMode === 'multi' && this.referenceList?.length > 0 && !this.bReadonly$) { this.pConn$.setReferenceList(this.selectionList); updateNewInstructions(this.pConn$, this.selectionList); } - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } } - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { // moved this from ngOnInit() and call this from there instead... this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as CheckboxProps; - this.testId = this.configProps$.testId; - this.displayMode$ = this.configProps$.displayMode; - this.label$ = this.configProps$.label; + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); + if (this.label$ != '') { this.showLabel$ = true; } - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; this.variant = this.configProps$.variant; // multi case @@ -176,53 +104,10 @@ export class CheckBoxComponent implements OnInit, OnDestroy { } this.caption$ = this.configProps$.caption; - this.helperText = this.configProps$.helperText; this.trueLabel$ = this.configProps$.trueLabel || 'Yes'; this.falseLabel$ = this.configProps$.falseLabel || 'No'; - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - if (this.bDisabled$ || this.bReadonly$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - this.componentReference = this.pConn$.getStateProps().value; - - if (this.value$ === 'true' || this.value$ == true) { - this.isChecked$ = true; - } else { - this.isChecked$ = false; - } - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); - } + this.isChecked$ = this.value$ === 'true' || this.value$ == true; } } @@ -260,21 +145,4 @@ export class CheckBoxComponent implements OnInit, OnDestroy { context: '' }); } - - getErrorMessage() { - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/currency/currency.component.ts b/packages/angular-sdk-components/src/lib/_components/field/currency/currency.component.ts index 8c2acecc..8af1ba2e 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/currency/currency.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/currency/currency.component.ts @@ -1,17 +1,16 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; import { NgxCurrencyDirective, NgxCurrencyInputMode } from 'ngx-currency'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; import { handleEvent } from '../../../_helpers/event-util'; import { getCurrencyCharacters, getCurrencyOptions } from '../../../_helpers/currency-utils'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; import { format } from '../../../_helpers/formatters'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface CurrrencyProps extends PConnFieldProps { // If any, enter additional props that only exist on Currency here @@ -26,175 +25,59 @@ interface CurrrencyProps extends PConnFieldProps { styleUrls: ['./currency.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, NgxCurrencyDirective, forwardRef(() => ComponentMapperComponent)] }) -export class CurrencyComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class CurrencyComponent extends FieldBase { configProps$: CurrrencyProps; + override fieldControl = new FormControl(null, { updateOn: 'blur' }); - label$ = ''; - value$: any; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; - componentReference = ''; - testId: string; - helperText: string; - placeholder: string; - currencyISOCode = 'USD'; currencyOptions: object = {}; - - fieldControl = new FormControl(null, { updateOn: 'blur' }); currencySymbol: string; thousandSeparator: string; decimalSeparator: string; - inputMode: any; decimalPrecision: number | undefined; formattedValue: string; formatter; + inputMode = NgxCurrencyInputMode.Natural; - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve configuration properties + this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as CurrrencyProps; - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); + // Extract and normalize the value property + const { value } = this.configProps$; + if (value) { + this.value$ = typeof value === 'string' ? parseFloat(value) : value; this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); } - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } + // update currency properties + this.updateCurrencyProperties(this.configProps$); } - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // starting very simple... - - // moved this from ngOnInit() and call this from there instead... - this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as CurrrencyProps; - this.testId = this.configProps$.testId; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - this.inputMode = NgxCurrencyInputMode.Natural; - let nValue: any = this.configProps$.value; - if (nValue) { - if (typeof nValue === 'string') { - nValue = parseFloat(nValue); - } - this.value$ = nValue; - } else { - this.value$ = null; - } - this.fieldControl.setValue(this.value$); - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - const currencyISOCode = this.configProps$?.currencyISOCode ?? ''; + /** + * Updates the currency properties + * + * @param {Object} configProps - Configuration properties. + * @param {boolean} configProps.allowDecimals - Whether to allow decimal values. + * @param {string} configProps.currencyISOCode - The ISO code of the currency. + * @param {string} configProps.formatter - The formatter type (e.g., 'currency'). + */ + protected updateCurrencyProperties(configProps): void { + const { allowDecimals, currencyISOCode = 'USD', formatter } = configProps; const theSymbols = getCurrencyCharacters(currencyISOCode); this.currencySymbol = theSymbols.theCurrencySymbol; this.thousandSeparator = theSymbols.theDigitGroupSeparator; this.decimalSeparator = theSymbols.theDecimalIndicator; - this.formatter = this.configProps$.formatter; - - if (this.displayMode$ === 'DISPLAY_ONLY' || this.displayMode$ === 'STACKED_LARGE_VAL') { - const theCurrencyOptions = getCurrencyOptions(currencyISOCode); - if (this.formatter) { - this.formattedValue = format(this.value$, this.formatter.toLowerCase(), theCurrencyOptions); - } else { - this.formattedValue = format(this.value$, 'currency', theCurrencyOptions); - } - } - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } + this.decimalPrecision = allowDecimals ? 2 : 0; - if (this.configProps$.currencyISOCode != null) { - this.currencyISOCode = this.configProps$.currencyISOCode; - } - - this.decimalPrecision = this.configProps$?.allowDecimals ? 2 : 0; - - this.componentReference = this.pConn$.getStateProps().value; - - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); + if (['DISPLAY_ONLY', 'STACKED_LARGE_VAL'].includes(this.displayMode$)) { + this.formattedValue = format(this.value$, formatter ? formatter.toLowerCase() : 'currency', getCurrencyOptions(currencyISOCode)); } } @@ -219,21 +102,4 @@ export class CurrencyComponent implements OnInit, OnDestroy { handleEvent(actionsApi, 'changeNblur', propName, value); } } - - getErrorMessage() { - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/date-time/date-time.component.ts b/packages/angular-sdk-components/src/lib/_components/field/date-time/date-time.component.ts index e3a30cf8..b270da6e 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/date-time/date-time.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/date-time/date-time.component.ts @@ -1,20 +1,19 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, OnInit, forwardRef, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { OwlDateTimeModule, OwlNativeDateTimeModule } from '@danielmoncada/angular-datetime-picker'; -import { interval } from 'rxjs'; import dayjs from 'dayjs'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; -import { dateFormatInfoDefault, getDateFormatInfo } from '../../../_helpers/date-format-utils'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; +import { getDateFormatInfo } from '../../../_helpers/date-format-utils'; import { handleEvent } from '../../../_helpers/event-util'; import { format } from '../../../_helpers/formatters'; import { DateFormatters } from '../../../_helpers/formatters/date'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface DateTimeProps extends PConnFieldProps { // If any, enter additional props that only exist on DateTime here @@ -35,162 +34,57 @@ interface DateTimeProps extends PConnFieldProps { forwardRef(() => ComponentMapperComponent) ] }) -export class DateTimeComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class DateTimeComponent extends FieldBase implements OnInit, OnDestroy { configProps$: DateTimeProps; - label$ = ''; - value$: any; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; - componentReference = ''; - testId = ''; - helperText: string; - - fieldControl = new FormControl('', null); stepHour = 1; stepMinute = 1; stepSecond = 1; public color = 'primary'; - // Start with default dateFormatInfo - dateFormatInfo = dateFormatInfoDefault; - // and then update, as needed, based on locale, etc. - theDateFormat = getDateFormatInfo(); - placeholder: string; - actionsApi: object; - propName: string; formattedValue$: any; + theDateFormat = getDateFormatInfo(); timezone = PCore.getEnvironmentInfo()?.getTimeZone(); + override placeholder = `${this.theDateFormat.dateFormatStringLC}, hh:mm A`; - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - this.placeholder = `${this.theDateFormat.dateFormatStringLC}, hh:mm A`; - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - // Then, continue on with other initialization - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); + override ngOnInit(): void { + super.ngOnInit(); if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); let dateTimeValue = this.value$ ?? ''; if (this.value$) { dateTimeValue = dayjs(DateFormatters?.convertToTimezone(this.value$, { timezone: this.timezone }))?.toISOString(); } this.fieldControl.setValue(dateTimeValue); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; } } - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve config properties + this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as DateTimeProps; - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - // updateSelf - updateSelf(): void { - // starting very simple... - // moved this from ngOnInit() and call this from there instead... - this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as DateTimeProps; + // Extract the value property + const { value } = this.configProps$; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - this.testId = this.configProps$.testId; - this.helperText = this.configProps$.helperText; - this.value$ = this.configProps$?.value; - let dateTimeValue = this.configProps$?.value ?? ''; + // Update component properties + this.value$ = value; + let dateTimeValue = value ?? ''; if (this.value$) { dateTimeValue = dayjs(DateFormatters?.convertToTimezone(this.value$, { timezone: this.timezone }))?.toISOString(); } this.fieldControl.setValue(dateTimeValue); - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - if (this.displayMode$ === 'DISPLAY_ONLY' || this.displayMode$ === 'STACKED_LARGE_VAL') { + if (['DISPLAY_ONLY', 'STACKED_LARGE_VAL'].includes(this.displayMode$)) { this.formattedValue$ = format(this.value$, 'datetime', { format: `${this.theDateFormat.dateFormatString} hh:mm A` }); } - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - this.componentReference = this.pConn$.getStateProps().value; - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); - } } fieldOnDateChange(event: any) { @@ -203,19 +97,4 @@ export class DateTimeComponent implements OnInit, OnDestroy { } handleEvent(this.actionsApi, 'changeNblur', this.propName, event.value); } - - getErrorMessage() { - let errMessage = ''; - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/date/date.component.html b/packages/angular-sdk-components/src/lib/_components/field/date/date.component.html index 968e5be0..235a0e4c 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/date/date.component.html +++ b/packages/angular-sdk-components/src/lib/_components/field/date/date.component.html @@ -11,7 +11,7 @@ #dateInput [attr.data-test-id]="testId" [matDatepicker]="pegadate" - [placeholder]="dateFormatInfo.dateFormatStringLC" + [placeholder]="theDateFormat.dateFormatStringLC" type="text" [value]="value$" [required]="bRequired$" diff --git a/packages/angular-sdk-components/src/lib/_components/field/date/date.component.ts b/packages/angular-sdk-components/src/lib/_components/field/date/date.component.ts index c0d5d1dd..cb8dba1c 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/date/date.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/date/date.component.ts @@ -1,20 +1,19 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, Inject, OnDestroy } from '@angular/core'; +import { Component, OnInit, forwardRef, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatNativeDateModule } from '@angular/material/core'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MAT_DATE_FORMATS } from '@angular/material/core'; import { MomentDateModule } from '@angular/material-moment-adapter'; -import { interval } from 'rxjs'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; -import { dateFormatInfoDefault, getDateFormatInfo } from '../../../_helpers/date-format-utils'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; +import { getDateFormatInfo } from '../../../_helpers/date-format-utils'; import { format } from '../../../_helpers/formatters'; import { handleEvent } from '../../../_helpers/event-util'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface DateProps extends PConnFieldProps { // If any, enter additional props that only exist on Date here @@ -55,153 +54,32 @@ class MyFormat { ], providers: [{ provide: MAT_DATE_FORMATS, useClass: MyFormat }] }) -export class DateComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class DateComponent extends FieldBase implements OnInit, OnDestroy { configProps$: DateProps; - label$ = ''; - value$: any; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; - componentReference = ''; - testId = ''; - helperText: string; - placeholder: string; - - fieldControl = new FormControl('', null); - - // Start with default dateFormatInfo - dateFormatInfo = dateFormatInfoDefault; - // and then update, as needed, based on locale, etc. theDateFormat = getDateFormatInfo(); - actionsApi: object; - propName: string; formattedValue$: any; - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils, - @Inject(MAT_DATE_FORMATS) private config: MyFormat - ) {} - - ngOnInit(): void { - this.dateFormatInfo = this.theDateFormat; - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // starting very simple... - // moved this from ngOnInit() and call this from there instead... + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve config properties this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as DateProps; - this.value$ = this.configProps$.value; - this.testId = this.configProps$.testId; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - if (this.displayMode$ === 'DISPLAY_ONLY' || this.displayMode$ === 'STACKED_LARGE_VAL') { + // Extract and normalize the value property + const { value } = this.configProps$; + this.value$ = value; + + // Format value for display modes + if (['DISPLAY_ONLY', 'STACKED_LARGE_VAL'].includes(this.displayMode$)) { this.formattedValue$ = format(this.value$, 'date', { format: this.theDateFormat.dateFormatString }); } - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - this.componentReference = this.pConn$.getStateProps().value; - - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); - } } fieldOnDateChange(event: any) { @@ -217,18 +95,20 @@ export class DateComponent implements OnInit, OnDestroy { return this.fieldControl.status === 'INVALID'; } - getErrorMessage() { - let errMessage = ''; + override getErrorMessage() { // look for validation messages for json, pre-defined or just an error pushed from workitem (400) if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; + return this.angularPConnectData.validateMessage ?? ''; } + if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = `${this.fieldControl.errors['matDatepickerParse'].text} is not a valid date value`; + return 'You must enter a value'; + } + + if (this.fieldControl.errors) { + return `${this.fieldControl.errors['matDatepickerParse'].text} is not a valid date value`; } - return errMessage; + + return ''; } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/decimal/decimal.component.ts b/packages/angular-sdk-components/src/lib/_components/field/decimal/decimal.component.ts index 089e820e..7b9e2a70 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/decimal/decimal.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/decimal/decimal.component.ts @@ -1,17 +1,16 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { NgxCurrencyDirective, NgxCurrencyInputMode } from 'ngx-currency'; -import { interval } from 'rxjs'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; import { handleEvent } from '../../../_helpers/event-util'; import { getCurrencyCharacters, getCurrencyOptions } from '../../../_helpers/currency-utils'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; import { format } from '../../../_helpers/formatters'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface DecimalProps extends PConnFieldProps { // If any, enter additional props that only exist on Decimal here @@ -35,176 +34,69 @@ interface DecimalProps extends PConnFieldProps { forwardRef(() => ComponentMapperComponent) ] }) -export class DecimalComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class DecimalComponent extends FieldBase { configProps$: DecimalProps; + override fieldControl = new FormControl(null, null); - label$ = ''; - value$: any; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; - componentReference = ''; - testId: string; - helperText: string; - placeholder: string; - - fieldControl = new FormControl(null, null); decimalSeparator: string; thousandSeparator: string; currencySymbol = ''; decimalPrecision: number | undefined; formatter; formattedValue: any; - inputMode: any; + inputMode: any = NgxCurrencyInputMode.Natural; suffix = ''; - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve config properties + this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as DecimalProps; - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); + // Update common properties + this.updateComponentCommonProperties(this.configProps$); - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); + // Extract and normalize the value property + const { value } = this.configProps$; + if (value) { + this.value$ = typeof value === 'string' ? parseFloat(value) : value; this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); } - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } + // updates decimal properties + this.updateDecimalProperties(this.configProps$); } - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // starting very simple... - - // moved this from ngOnInit() and call this from there instead... - this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as DecimalProps; - this.testId = this.configProps$.testId; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - this.inputMode = NgxCurrencyInputMode.Natural; - let nValue: any = this.configProps$.value; - if (nValue) { - if (typeof nValue === 'string') { - nValue = parseFloat(nValue); - } - this.value$ = nValue; - this.fieldControl.setValue(this.value$); - } - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - const showGroupSeparators = this.configProps$.showGroupSeparators; - const currencyISOCode = this.configProps$?.currencyISOCode ?? ''; - + /** + * Updates decimal properties based on the provided configuration. + * + * @param {Object} configProps - Configuration properties. + * @param {string} configProps.currencyISOCode - ISO code of the currency. + * @param {string} configProps.formatter - Formatter type (e.g., 'decimal', 'currency'). + * @param {boolean} configProps.showGroupSeparators - Whether to show group separators. + */ + protected updateDecimalProperties(configProps): void { + const { currencyISOCode = '', formatter, showGroupSeparators } = configProps; + + // Extract currency symbols and options const theSymbols = getCurrencyCharacters(currencyISOCode); this.decimalSeparator = theSymbols.theDecimalIndicator; this.thousandSeparator = showGroupSeparators ? theSymbols.theDigitGroupSeparator : ''; const theCurrencyOptions = getCurrencyOptions(currencyISOCode); - this.formatter = this.configProps$.formatter; + const formatterLower = formatter?.toLowerCase() || 'decimal'; + this.formattedValue = format(this.value$, formatterLower, theCurrencyOptions); - if (this.formatter) { - this.formattedValue = format(this.value$, this.formatter.toLowerCase(), theCurrencyOptions); - } else { - this.formattedValue = format(this.value$, 'decimal', theCurrencyOptions); - } - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.bReadonly$ && this.formatter === 'Currency') { + if (this.bReadonly$ && formatter === 'Currency') { this.currencySymbol = theSymbols.theCurrencySymbol; } - if (this.bReadonly$ && this.formatter === 'Percentage') { + if (this.bReadonly$ && formatter === 'Percentage') { this.suffix = '%'; } this.decimalPrecision = this.configProps$?.decimalPrecision ?? 2; - - this.componentReference = this.pConn$.getStateProps().value; - - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); - } } fieldOnBlur(event: any) { @@ -229,21 +121,4 @@ export class DecimalComponent implements OnInit, OnDestroy { handleEvent(actionsApi, 'changeNblur', propName, value); } } - - getErrorMessage() { - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/dropdown/dropdown.component.ts b/packages/angular-sdk-components/src/lib/_components/field/dropdown/dropdown.component.ts index 5c0a836d..ff208304 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/dropdown/dropdown.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/dropdown/dropdown.component.ts @@ -1,14 +1,12 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy, EventEmitter, Output } from '@angular/core'; +import { Component, OnInit, forwardRef, OnDestroy, Output, EventEmitter } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatOptionModule } from '@angular/material/core'; import { MatSelectModule } from '@angular/material/select'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; import isEqual from 'fast-deep-equal'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { DatapageService } from '../../../_services/datapage.service'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; import { handleEvent } from '../../../_helpers/event-util'; import { PConnFieldProps } from '../../../_types/PConnProps.interface'; @@ -70,68 +68,18 @@ interface DropdownProps extends PConnFieldProps { styleUrls: ['./dropdown.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatSelectModule, MatOptionModule, forwardRef(() => ComponentMapperComponent)] }) -export class DropdownComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; +export class DropdownComponent extends FieldBase implements OnInit, OnDestroy { @Output() onRecordChange: EventEmitter = new EventEmitter(); - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; configProps$: DropdownProps; - label$ = ''; - value$ = ''; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; options$: IOption[]; - componentReference = ''; - testId = ''; - helperText: string; - hideLabel: any; theDatasource: any[] | null; - fieldControl = new FormControl('', null); - fieldMetadata: any[]; localeContext = ''; localeClass = ''; localeName = ''; localePath = ''; localizedValue = ''; - actionsApi: object; - propName: string; - - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils, - private dataPageService: DatapageService - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - - // call updateSelf when initializing - this.checkAndUpdate(); - // this should get called afer checkAndUpdate - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } set options(options: IOption[]) { this.options$ = options; @@ -145,80 +93,34 @@ export class DropdownComponent implements OnInit, OnDestroy { } } - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve configuration properties + this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as DropdownProps; - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } + // Set component specific properties + this.updateDropdownProperties(this.configProps$); } - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... - this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as DropdownProps; - if (this.configProps$.value != undefined) { - this.value$ = this.configProps$.value; - } + /** + * Updates dropdown properties based on the provided configuration. + * @param configProps - Configuration properties + */ + updateDropdownProperties(configProps) { + const { value, fieldMetadata, datasource } = configProps; - this.testId = this.configProps$.testId; - this.displayMode$ = this.configProps$.displayMode; - this.label$ = this.configProps$.label; - this.helperText = this.configProps$.helperText; - this.hideLabel = this.configProps$.hideLabel; - const datasource = this.configProps$.datasource; - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); + this.value$ = value; if (!isEqual(datasource, this.theDatasource)) { // inbound datasource is different, so update theDatasource this.theDatasource = datasource || null; } - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - this.componentReference = this.pConn$.getStateProps().value; - if (this.value$ === '' && !this.bReadonly$) { this.value$ = 'Select'; } @@ -235,8 +137,7 @@ export class DropdownComponent implements OnInit, OnDestroy { const className = this.pConn$.getCaseInfo().getClassName(); const refName = this.propName?.slice(this.propName.lastIndexOf('.') + 1); - this.fieldMetadata = this.configProps$.fieldMetadata; - const metaData = Array.isArray(this.fieldMetadata) ? this.fieldMetadata.filter(field => field?.classID === className)[0] : this.fieldMetadata; + const metaData = Array.isArray(fieldMetadata) ? fieldMetadata.filter(field => field?.classID === className)[0] : fieldMetadata; let displayName = metaData?.datasource?.propertyForDisplayText; displayName = displayName?.slice(displayName.lastIndexOf('.') + 1); @@ -252,16 +153,8 @@ export class DropdownComponent implements OnInit, OnDestroy { ); this.localizedValue = this.options$?.find(opt => opt.key === this.value$)?.value || this.localizedValue; - this.getDatapageData(); - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - timer.unsubscribe(); - }); - } + this.getDatapageData(); } getDatapageData() { @@ -353,21 +246,4 @@ export class DropdownComponent implements OnInit, OnDestroy { this.pConn$.getLocaleRuleNameFromKeys(this.localeClass, this.localeContext, this.localeName) ); } - - getErrorMessage() { - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/email/email.component.ts b/packages/angular-sdk-components/src/lib/_components/field/email/email.component.ts index dc65d756..2fe5787b 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/email/email.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/email/email.component.ts @@ -1,14 +1,13 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, OnInit, forwardRef, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; import { handleEvent } from '../../../_helpers/event-util'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface EmailProps extends PConnFieldProps { // If any, enter additional props that only exist on Email here @@ -20,141 +19,22 @@ interface EmailProps extends PConnFieldProps { styleUrls: ['./email.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, forwardRef(() => ComponentMapperComponent)] }) -export class EmailComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class EmailComponent extends FieldBase implements OnInit, OnDestroy { configProps$: EmailProps; - label$ = ''; - value$: string; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; - componentReference = ''; - testId: string; - helperText: string; - placeholder: string; - - fieldControl = new FormControl('', null); - actionsApi: object; - propName: string; - - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve configuration properties this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as EmailProps; - this.testId = this.configProps$.testId; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - - if (this.configProps$.value != undefined) { - this.value$ = this.configProps$.value; - } - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - - this.componentReference = this.pConn$.getStateProps().value; - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - timer.unsubscribe(); - }); - } + // Set component specific properties + const { value } = this.configProps$; + this.value$ = value; } fieldOnChange(event: any) { @@ -177,21 +57,4 @@ export class EmailComponent implements OnInit, OnDestroy { handleEvent(this.actionsApi, 'changeNblur', this.propName, value); } } - - getErrorMessage() { - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/field.base.ts b/packages/angular-sdk-components/src/lib/_components/field/field.base.ts new file mode 100644 index 00000000..50103609 --- /dev/null +++ b/packages/angular-sdk-components/src/lib/_components/field/field.base.ts @@ -0,0 +1,149 @@ +import { Directive, inject, Input, OnDestroy, OnInit } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; + +import { AngularPConnectData, AngularPConnectService } from '../../_bridge/angular-pconnect'; +import { Utils } from '../../_helpers/utils'; + +@Directive() +export class FieldBase implements OnInit, OnDestroy { + @Input() pConn$: typeof PConnect; + @Input() formGroup$: FormGroup; + + protected angularPConnect = inject(AngularPConnectService); + protected utils = inject(Utils); + + protected angularPConnectData: AngularPConnectData = {}; + + fieldControl: FormControl = new FormControl('', null); + controlName$: string; + actionsApi: object; + propName: string; + + bHasForm$ = true; + testId: string; + helperText: string; + placeholder: string; + value$: any = ''; + label$ = ''; + hideLabel = false; + bRequired$ = false; + bReadonly$ = false; + bDisabled$ = false; + bVisible$ = true; + displayMode$ = ''; + + /** + * Initializes the component, registers with AngularPConnect, and sets up form control. + */ + ngOnInit(): void { + // First thing in initialization is registering and subscribing to the AngularPConnect service + this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange.bind(this)); + this.controlName$ = this.angularPConnect.getComponentID(this); + + // call checkAndUpdate + this.checkAndUpdate(); + + if (this.formGroup$) { + this.formGroup$.addControl(this.controlName$, this.fieldControl); + this.fieldControl.setValue(this.value$); + this.bHasForm$ = true; + } else { + this.bReadonly$ = true; + this.bHasForm$ = false; + } + + this.actionsApi = this.pConn$.getActionsApi(); + this.propName = this.pConn$.getStateProps().value; + } + + /** + * Cleans up the component by removing it from the form group and unsubscribing from any observables. + */ + ngOnDestroy(): void { + if (this.formGroup$) { + this.formGroup$.removeControl(this.controlName$); + } + + if (this.angularPConnectData.unsubscribeFn) { + this.angularPConnectData.unsubscribeFn(); + } + } + + // Callback passed when subscribing to store change + onStateChange() { + this.checkAndUpdate(); + } + + // Should always check the bridge to see if the component should update itself (re-render) + checkAndUpdate() { + const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); + + // ONLY call updateSelf when the component should update + if (bUpdateSelf) { + this.updateSelf(); + } + } + + // will be overriden by child components + updateSelf(): void {} + + /** + * Updates the component's common properties based on the provided configuration. + * + * @param configProps The configuration properties to update. + */ + protected updateComponentCommonProperties(configProps) { + // Extract properties from config + const { testId, label, hideLabel, displayMode = '', helperText, placeholder, required, visibility = true, disabled, readOnly } = configProps; + + // Update component properties + this.testId = testId; + this.label$ = label; + this.hideLabel = hideLabel; + this.displayMode$ = displayMode; + this.helperText = helperText; + this.placeholder = placeholder || ''; + + // Convert boolean properties + this.bVisible$ = this.utils.getBooleanValue(visibility); + this.bRequired$ = this.utils.getBooleanValue(required); + this.bDisabled$ = this.utils.getBooleanValue(disabled); + this.bReadonly$ = this.utils.getBooleanValue(readOnly); + + // Enable or disable field control + this.fieldControl[this.bDisabled$ ? 'disable' : 'enable'](); + + // Display error message if validation message exists + this.displayValidationMessage(); + } + + /** + * Displays the validation message if it exists. + */ + private displayValidationMessage(): void { + if (this.angularPConnectData.validateMessage) { + setTimeout(() => { + this.fieldControl.setErrors({ message: true }); + this.fieldControl.markAsTouched(); + }, 100); + } + } + + /** + * Retrieves the error message for the current field control. + * + * @returns The error message, or an empty string if no error is found. + */ + getErrorMessage() { + // look for validation messages for json, pre-defined or just an error pushed from workitem (400) + if (this.fieldControl.hasError('message')) { + return this.angularPConnectData.validateMessage ?? ''; + } + + if (this.fieldControl.hasError('required')) { + return 'You must enter a value'; + } + + return this.fieldControl.errors?.toString() ?? ''; + } +} diff --git a/packages/angular-sdk-components/src/lib/_components/field/group/group.component.ts b/packages/angular-sdk-components/src/lib/_components/field/group/group.component.ts index c97c1501..4561d949 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/group/group.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/group/group.component.ts @@ -24,6 +24,10 @@ export class GroupComponent implements OnInit { @Input() pConn$: typeof PConnect; @Input() formGroup$: FormGroup; + // Used with AngularPConnect + angularPConnectData: AngularPConnectData = {}; + configProps$: GroupProps; + arChildren$: any[]; visibility$?: boolean; showHeading$?: boolean; @@ -31,10 +35,6 @@ export class GroupComponent implements OnInit { instructions$: string; collapsible$: boolean; - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; - configProps$: GroupProps; - constructor(private angularPConnect: AngularPConnectService) {} ngOnInit(): void { @@ -59,6 +59,9 @@ export class GroupComponent implements OnInit { } } + /** + * Updates the component when there are changes in the state. + */ updateSelf(): void { this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as GroupProps; this.arChildren$ = ReferenceComponent.normalizePConnArray(this.pConn$.getChildren()); diff --git a/packages/angular-sdk-components/src/lib/_components/field/integer/integer.component.ts b/packages/angular-sdk-components/src/lib/_components/field/integer/integer.component.ts index ae3f308c..60b81cce 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/integer/integer.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/integer/integer.component.ts @@ -1,14 +1,13 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; import { handleEvent } from '../../../_helpers/event-util'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface IntegerProps extends PConnFieldProps { // If any, enter additional props that only exist on Integer here @@ -20,143 +19,24 @@ interface IntegerProps extends PConnFieldProps { styleUrls: ['./integer.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, forwardRef(() => ComponentMapperComponent)] }) -export class IntegerComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class IntegerComponent extends FieldBase { configProps$: IntegerProps; + override fieldControl = new FormControl(null, null); - label$ = ''; - value$: number; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; - componentReference = ''; - testId: string; - helperText: string; - placeholder: string; - - fieldControl = new FormControl(null, null); - actionsApi: object; - propName: string; - - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve configuration properties this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as IntegerProps; - this.testId = this.configProps$.testId; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - let nValue: any = this.configProps$.value; - if (nValue) { - if (typeof nValue === 'string') { - nValue = parseInt(nValue, 10); - } - this.value$ = nValue; - } - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - this.componentReference = this.pConn$.getStateProps().value; - - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - timer.unsubscribe(); - }); + // Extract and normalize the value property + const { value } = this.configProps$; + if (value) { + this.value$ = typeof value === 'string' ? parseInt(value, 10) : value; } } @@ -180,23 +60,4 @@ export class IntegerComponent implements OnInit, OnDestroy { handleEvent(this.actionsApi, 'changeNblur', this.propName, value); } } - - getErrorMessage() { - // field control gets error message from here - - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/multiselect/multiselect.component.ts b/packages/angular-sdk-components/src/lib/_components/field/multiselect/multiselect.component.ts index 72fa5d1c..9ad8a22d 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/multiselect/multiselect.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/multiselect/multiselect.component.ts @@ -1,6 +1,6 @@ import { CommonModule } from '@angular/common'; -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { Component } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatChipsModule } from '@angular/material/chips'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -8,8 +8,8 @@ import { MatOptionModule } from '@angular/material/core'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatIconModule } from '@angular/material/icon'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { doSearch, getDisplayFieldsMetaData, getGroupDataForItemsTree, preProcessColumns } from './utils'; import { deleteInstruction, insertInstruction } from '../../../_helpers/instructions-utils'; import { handleEvent } from '../../../_helpers/event-util'; @@ -30,39 +30,16 @@ import { handleEvent } from '../../../_helpers/event-util'; MatChipsModule ] }) -export class MultiselectComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class MultiselectComponent extends FieldBase { + configProps$: any; - label$ = ''; - value$ = ''; - bRequired$ = false; - bDisabled$ = false; - bVisible$ = true; - controlName$: string; - bHasForm$ = true; listType: string; - placeholder: string; - fieldControl = new FormControl('', null); parameters: {}; - hideLabel: boolean; - configProps$: any; referenceList: any; selectionKey: string; primaryField: string; - initialCaseClass: any; showSecondaryInSearchOnly = false; - isGroupData = false; - referenceType; - secondaryFields; - groupDataSource = []; - matchPosition = 'contains'; - maxResultsDisplay; - groupColumnsConfig = [{}]; selectionList; listActions: any; selectedItems: any[] = []; @@ -71,64 +48,32 @@ export class MultiselectComponent implements OnInit, OnDestroy { dataApiObj: any; itemsTree: any[] = []; trigger: any; - actionsApi: object; - propName: string; - - constructor( - private angularPConnect: AngularPConnectService, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } + /** + * Updates the component when there are changes in the state. + */ + override updateSelf() { + // Resolve configuration properties + this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()); - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf() { - this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()); + this.setPropertyValuesFromProps(); + const { + groupDataSource = [], + parameters = {}, + listType = '', + showSecondaryInSearchOnly = false, + isGroupData = false, + referenceType, + secondaryFields, + matchPosition = 'contains', + maxResultsDisplay, + groupColumnsConfig = [{}] + } = this.configProps$; let { datasource = [], columns = [{}] } = this.configProps$; - this.setPropertyValuesFromProps(); if (this.referenceList.length > 0) { datasource = this.referenceList; @@ -146,8 +91,8 @@ export class MultiselectComponent implements OnInit, OnDestroy { } ]; let secondaryColumns: any = []; - if (this.secondaryFields) { - secondaryColumns = this.secondaryFields.map(secondaryField => ({ + if (secondaryFields) { + secondaryColumns = secondaryFields.map(secondaryField => ({ value: secondaryField, display: 'true', secondary: 'true', @@ -163,7 +108,7 @@ export class MultiselectComponent implements OnInit, OnDestroy { } ]; } - if (this.referenceType === 'Case') { + if (referenceType === 'Case') { columns = [...columns, ...secondaryColumns]; } } @@ -173,54 +118,36 @@ export class MultiselectComponent implements OnInit, OnDestroy { const dataConfig = { dataSource: datasource, - groupDataSource: this.groupDataSource, - isGroupData: this.isGroupData, - showSecondaryInSearchOnly: this.showSecondaryInSearchOnly, - parameters: this.parameters, - matchPosition: this.matchPosition, - listType: this.listType, - maxResultsDisplay: this.maxResultsDisplay || '100', + groupDataSource, + isGroupData, + showSecondaryInSearchOnly, + parameters, + matchPosition, + listType, + maxResultsDisplay: maxResultsDisplay || '100', columns: preProcessColumns(columns), - groupColumnsConfig: preProcessColumns(this.groupColumnsConfig), + groupColumnsConfig: preProcessColumns(groupColumnsConfig), associationFilter: undefined, ignoreCase: undefined }; const groupsDisplayFieldMeta = this.listType !== 'associated' ? getDisplayFieldsMetaData(dataConfig.groupColumnsConfig) : null; - this.itemsTreeBaseData = getGroupDataForItemsTree(this.groupDataSource, groupsDisplayFieldMeta, this.showSecondaryInSearchOnly) || []; + this.itemsTreeBaseData = getGroupDataForItemsTree(groupDataSource, groupsDisplayFieldMeta, this.showSecondaryInSearchOnly) || []; - this.itemsTree = this.isGroupData ? getGroupDataForItemsTree(this.groupDataSource, groupsDisplayFieldMeta, this.showSecondaryInSearchOnly) : []; + this.itemsTree = isGroupData ? getGroupDataForItemsTree(groupDataSource, groupsDisplayFieldMeta, this.showSecondaryInSearchOnly) : []; this.displayFieldMeta = this.listType !== 'associated' ? getDisplayFieldsMetaData(dataConfig.columns) : null; this.listActions = this.pConn$.getListActions(); this.pConn$.setReferenceList(this.selectionList); - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - if (this.listType !== 'associated') { PCore.getDataApi() ?.init(dataConfig, contextName) .then(async dataObj => { this.dataApiObj = dataObj; - if (!this.isGroupData) { + if (!isGroupData) { this.getCaseListBasedOnParams(this.value$ ?? '', '', [...this.selectedItems], [...this.itemsTree]); } }); @@ -228,23 +155,9 @@ export class MultiselectComponent implements OnInit, OnDestroy { } setPropertyValuesFromProps() { - this.label$ = this.configProps$.label; - this.placeholder = this.configProps$.placeholder || ''; - this.listType = this.configProps$.listType ? this.configProps$.listType : ''; - this.hideLabel = this.configProps$.hideLabel; - this.parameters = this.configProps$?.parameters ? this.configProps$?.parameters : {}; this.referenceList = this.configProps$?.referenceList; this.selectionKey = this.configProps$?.selectionKey; this.primaryField = this.configProps$?.primaryField; - this.initialCaseClass = this.configProps$?.initialCaseClass; - this.showSecondaryInSearchOnly = this.configProps$?.showSecondaryInSearchOnly ? this.configProps$?.showSecondaryInSearchOnly : false; - this.isGroupData = this.configProps$?.isGroupData ? this.configProps$.isGroupData : false; - this.referenceType = this.configProps$?.referenceType; - this.secondaryFields = this.configProps$?.secondaryFields; - this.groupDataSource = this.configProps$?.groupDataSource ? this.configProps$?.groupDataSource : []; - this.matchPosition = this.configProps$?.matchPosition ? this.configProps$?.matchPosition : 'contains'; - this.maxResultsDisplay = this.configProps$?.maxResultsDisplay; - this.groupColumnsConfig = this.configProps$?.groupColumnsConfig ? this.configProps$?.groupColumnsConfig : [{}]; this.selectionList = this.configProps$?.selectionList; this.value$ = this.configProps$?.value; } @@ -266,15 +179,17 @@ export class MultiselectComponent implements OnInit, OnDestroy { const initalItemsTree = isTriggeredFromSearch || !currentItemsTree ? [...this.itemsTreeBaseData] : [...currentItemsTree]; + const { initialCaseClass, isGroupData, showSecondaryInSearchOnly } = this.configProps$; + doSearch( searchText, group, - this.initialCaseClass, + initialCaseClass, this.displayFieldMeta, this.dataApiObj, initalItemsTree, - this.isGroupData, - this.showSecondaryInSearchOnly, + isGroupData, + showSecondaryInSearchOnly, selectedRows || [] ).then(res => { this.itemsTree = res || []; @@ -346,21 +261,4 @@ export class MultiselectComponent implements OnInit, OnDestroy { deleteInstruction(this.pConn$, this.selectionList, this.selectionKey, data); } } - - getErrorMessage() { - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/percentage/percentage.component.ts b/packages/angular-sdk-components/src/lib/_components/field/percentage/percentage.component.ts index 179f37e2..0f3e4642 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/percentage/percentage.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/percentage/percentage.component.ts @@ -1,19 +1,19 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; import { NgxCurrencyDirective, NgxCurrencyInputMode } from 'ngx-currency'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; import { handleEvent } from '../../../_helpers/event-util'; import { getCurrencyCharacters } from '../../../_helpers/currency-utils'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; import { format } from '../../../_helpers/formatters'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; -interface PercentageProps extends PConnFieldProps { +interface PercentageProps extends Omit { + value?: number; showGroupSeparators?: string; decimalPrecision?: number; currencyISOCode?: string; @@ -26,155 +26,54 @@ interface PercentageProps extends PConnFieldProps { styleUrls: ['./percentage.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, NgxCurrencyDirective, forwardRef(() => ComponentMapperComponent)] }) -export class PercentageComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class PercentageComponent extends FieldBase { configProps$: PercentageProps; + override fieldControl = new FormControl(null, null); - label$ = ''; - value$: number; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; - componentReference = ''; - testId: string; - helperText: string; - placeholder: string; decimalSeparator: string; thousandSeparator: string; - inputMode: any; + inputMode: any = NgxCurrencyInputMode.Natural; decimalPrecision: number | undefined; - fieldControl = new FormControl(null, null); - actionsApi: object; - propName: string; formattedValue: string; - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve configuration properties + this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as PercentageProps; - // Then, continue on with other initialization - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; + // Set component specific properties + const { value } = this.configProps$; + if (value) { + this.value$ = value; + this.fieldControl.setValue(value); } - } - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } + // update percentage properties + this.updatePercentageProperties(this.configProps$); } - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... - this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as PercentageProps; - this.testId = this.configProps$.testId; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - this.inputMode = NgxCurrencyInputMode.Natural; - const nValue: any = this.configProps$.value; - if (nValue) { - this.value$ = nValue; - this.fieldControl.setValue(nValue); - } - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - const showGroupSeparators = this.configProps$.showGroupSeparators; + /** + * Updates the percentage properties + * + * @param {Object} configProps - Configuration properties. + * @param {boolean} configProps.showGroupSeparators - Whether to show group separators. + * @param {number} configProps.decimalPrecision - The number of decimal places to display. + */ + updatePercentageProperties(configProps): void { + const { showGroupSeparators, decimalPrecision } = configProps; const theSymbols = getCurrencyCharacters(''); this.decimalSeparator = theSymbols.theDecimalIndicator; this.thousandSeparator = showGroupSeparators ? theSymbols.theDigitGroupSeparator : ''; + this.decimalPrecision = decimalPrecision ?? 2; - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - - if (this.displayMode$ === 'DISPLAY_ONLY' || this.displayMode$ === 'STACKED_LARGE_VAL') { - this.formattedValue = nValue ? format(nValue, 'percentage') : ''; - } - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - this.decimalPrecision = this.configProps$?.decimalPrecision ?? 2; - - this.componentReference = this.pConn$.getStateProps().value; - - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - timer.unsubscribe(); - }); + if (['DISPLAY_ONLY', 'STACKED_LARGE_VAL'].includes(this.displayMode$)) { + this.formattedValue = this.value$ ? format(this.value$, 'percentage') : ''; } } @@ -210,20 +109,4 @@ export class PercentageComponent implements OnInit, OnDestroy { handleEvent(this.actionsApi, 'changeNblur', this.propName, value); } } - - getErrorMessage() { - // field control gets error message from here - let errMessage = ''; - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/phone/phone.component.ts b/packages/angular-sdk-components/src/lib/_components/field/phone/phone.component.ts index e9e86cd0..fc507a87 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/phone/phone.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/phone/phone.component.ts @@ -1,14 +1,13 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; import { MatTelInput } from 'mat-tel-input'; import { parsePhoneNumberFromString } from 'libphonenumber-js'; -import { Utils } from '../../../_helpers/utils'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { handleEvent } from '../../../_helpers/event-util'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; +import { handleEvent } from '../../../_helpers/event-util'; import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface PhoneProps extends PConnFieldProps { @@ -21,141 +20,28 @@ interface PhoneProps extends PConnFieldProps { styleUrls: ['./phone.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatTelInput, forwardRef(() => ComponentMapperComponent)] }) -export class PhoneComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class PhoneComponent extends FieldBase { configProps$: PhoneProps; - label$ = ''; - value$: string; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; - testId: string; - helperText: string; - placeholder: string; - - fieldControl = new FormControl('', null); - - actionsApi: object; - propName: string; preferredCountries: string[] = ['us']; - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve config properties this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as PhoneProps; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - this.testId = this.configProps$.testId; - if (this.configProps$.value != undefined) { - this.value$ = this.configProps$.value; + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); + + // Extract and normalize the value property + const { value } = this.configProps$; + if (value) { + this.value$ = value; this.fieldControl.setValue(this.value$); this.updatePreferredCountries(); } - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); - } } fieldOnBlur() { @@ -183,20 +69,20 @@ export class PhoneComponent implements OnInit, OnDestroy { } } - getErrorMessage() { - let errMessage = ''; - + override getErrorMessage() { // look for validation messages for json, pre-defined or just an error pushed from workitem (400) if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; + return this.angularPConnectData.validateMessage ?? ''; } + if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = 'Invalid Phone'; + return 'You must enter a value'; + } + + if (this.fieldControl.errors) { + return 'Invalid Phone'; } - return errMessage; + return ''; } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/radio-buttons/radio-buttons.component.ts b/packages/angular-sdk-components/src/lib/_components/field/radio-buttons/radio-buttons.component.ts index 057de3b1..7400334f 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/radio-buttons/radio-buttons.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/radio-buttons/radio-buttons.component.ts @@ -1,15 +1,15 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatRadioModule } from '@angular/material/radio'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; +import { Utils } from '../../../_helpers/utils'; import { handleEvent } from '../../../_helpers/event-util'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface IOption { key: string; @@ -30,159 +30,58 @@ interface RadioButtonsProps extends PConnFieldProps { providers: [Utils], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatRadioModule, forwardRef(() => ComponentMapperComponent)] }) -export class RadioButtonsComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class RadioButtonsComponent extends FieldBase { configProps$: RadioButtonsProps; - label$ = ''; - value$ = ''; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; bInline$ = false; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; + options$: IOption[]; componentReference = ''; - testId: string; - helperText: string; - placeholder: string; - - fieldControl = new FormControl('', null); fieldMetadata: any[]; localeContext = ''; localeClass = ''; localeName = ''; localePath = ''; localizedValue = ''; - actionsApi: object; - propName: string; variant?: string; - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve config properties this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as RadioButtonsProps; - if (this.configProps$.value != undefined) { - this.value$ = this.configProps$.value; - } - - this.testId = this.configProps$.testId; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - - this.variant = this.configProps$.variant; - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - if (this.configProps$.inline != null) { - this.bInline$ = this.utils.getBooleanValue(this.configProps$.inline); - } + // Extract and normalize the value property + const { value } = this.configProps$; + this.value$ = value; - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.configProps$.inline != null) { - this.bInline$ = this.utils.getBooleanValue(this.configProps$.inline); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } + // Set component specific properties + this.updateRadioButtonsProperties(this.configProps$); + } - this.componentReference = this.pConn$.getStateProps().value; + /** + * Updates radio buttons properties based on the provided config props. + * @param configProps Configuration properties. + */ + protected updateRadioButtonsProperties(configProps) { + const { inline, fieldMetadata, variant } = configProps; - this.options$ = this.utils.getOptionList(this.configProps$, this.pConn$.getDataObject()); + this.variant = variant; + this.bInline$ = this.utils.getBooleanValue(inline); - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; + // Get options from config props and data object + this.options$ = this.utils.getOptionList(configProps, this.pConn$.getDataObject()); + // Extract metadata and locale information const className = this.pConn$.getCaseInfo().getClassName(); const refName = this.propName?.slice(this.propName.lastIndexOf('.') + 1); + const metaData = Array.isArray(fieldMetadata) ? fieldMetadata.filter(field => field?.classID === className)[0] : fieldMetadata; - this.fieldMetadata = this.configProps$.fieldMetadata; - const metaData = Array.isArray(this.fieldMetadata) ? this.fieldMetadata.filter(field => field?.classID === className)[0] : this.fieldMetadata; - + // Determine display name and locale context let displayName = metaData?.datasource?.propertyForDisplayText; displayName = displayName?.slice(displayName.lastIndexOf('.') + 1); this.localeContext = metaData?.datasource?.tableType === 'DataPage' ? 'datapage' : 'associated'; @@ -190,20 +89,12 @@ export class RadioButtonsComponent implements OnInit, OnDestroy { this.localeName = this.localeContext === 'datapage' ? metaData?.datasource?.name : refName; this.localePath = this.localeContext === 'datapage' ? displayName : this.localeName; + // Get localized value this.localizedValue = this.pConn$.getLocalizedValue( this.value$, this.localePath, this.pConn$.getLocaleRuleNameFromKeys(this.localeClass, this.localeContext, this.localeName) ); - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); - } } isSelected(buttonValue: string): boolean { @@ -221,21 +112,4 @@ export class RadioButtonsComponent implements OnInit, OnDestroy { this.pConn$.getLocaleRuleNameFromKeys(this.localeClass, this.localeContext, this.localeName) ); } - - getErrorMessage() { - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/rich-text/rich-text.component.ts b/packages/angular-sdk-components/src/lib/_components/field/rich-text/rich-text.component.ts index f8e0cdb8..87f0a2ec 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/rich-text/rich-text.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/rich-text/rich-text.component.ts @@ -1,8 +1,8 @@ -import { Component, OnInit, Input, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; import { handleEvent } from '../../../_helpers/event-util'; import { PConnFieldProps } from '../../../_types/PConnProps.interface'; @@ -17,102 +17,31 @@ interface RichTextProps extends PConnFieldProps { styleUrls: ['./rich-text.component.scss'], imports: [CommonModule, ReactiveFormsModule, forwardRef(() => ComponentMapperComponent)] }) -export class RichTextComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class RichTextComponent extends FieldBase { configProps$: RichTextProps; - label$ = ''; - value$ = ''; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - testId: string; - helperText: string; - placeholder: any; info: any; error: boolean; status: any; - actionsApi: object; - propName: string; - - constructor( - private angularPConnect: AngularPConnectService, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - - // call updateSelf when initializing - this.checkAndUpdate(); - } - - ngOnDestroy(): void { - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve config properties this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as RichTextProps; - const stateProps = this.pConn$.getStateProps(); - this.status = stateProps?.status; - - if (this.configProps$.value != undefined) { - this.value$ = this.configProps$.value; - } - - this.testId = this.configProps$.testId; - this.displayMode$ = this.configProps$.displayMode; - this.label$ = this.configProps$.label; - this.placeholder = this.configProps$.placeholder; - this.info = stateProps?.validatemessage || this.configProps$.helperText; - this.error = stateProps?.status === 'error'; - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } + // Extract and normalize the value property + const { value, helperText } = this.configProps$; + this.value$ = value; - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } + const { status, validatemessage } = this.pConn$.getStateProps(); + this.status = status; + this.info = validatemessage || helperText; + this.error = status === 'error'; } fieldOnChange(editorValue: any) { diff --git a/packages/angular-sdk-components/src/lib/_components/field/scalar-list/scalar-list.component.ts b/packages/angular-sdk-components/src/lib/_components/field/scalar-list/scalar-list.component.ts index 09c0d290..e0b28a41 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/scalar-list/scalar-list.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/scalar-list/scalar-list.component.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common'; -import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; +import { Component, forwardRef } from '@angular/core'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; import { PConnFieldProps } from '../../../_types/PConnProps.interface'; @@ -19,83 +19,27 @@ interface ScalarListProps extends Omit { styleUrls: ['./scalar-list.component.scss'], imports: [CommonModule, forwardRef(() => ComponentMapperComponent)] }) -export class ScalarListComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - angularPConnectData: AngularPConnectData = {}; +export class ScalarListComponent extends FieldBase { configProps$: ScalarListProps; - label$ = ''; - value$: any; - displayMode$?: string = ''; items: any[]; isDisplayModeEnabled = false; - controlName$: string; - fieldControl = new FormControl('', null); - bHasForm$ = true; - bReadonly$ = false; - - constructor(private angularPConnect: AngularPConnectService) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - // Then, continue on with other initialization - - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve config properties + this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as ScalarListProps; - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } + // Extract properties from config props + const { componentType, displayMode = '', label, value, restProps } = this.configProps$; - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); + // Update component properties + this.label$ = label; + this.displayMode$ = displayMode; - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as ScalarListProps; - this.label$ = this.configProps$.label; - const componentType = this.configProps$.componentType; - const scalarValues = this.configProps$.value; - this.displayMode$ = this.configProps$.displayMode; - const restProps = this.configProps$.restProps; - console.log('scalar values: ', scalarValues); - this.items = scalarValues?.map(scalarValue => { - console.log('scalar value: ', scalarValue); + this.items = value?.map(scalarValue => { return this.pConn$.createComponent( { type: componentType, @@ -112,6 +56,7 @@ export class ScalarListComponent implements OnInit, OnDestroy { {} ); // 2nd, 3rd, and 4th args empty string/object/null until typedef marked correctly as optional; }); + this.isDisplayModeEnabled = ['STACKED_LARGE_VAL', 'DISPLAY_ONLY'].includes(this.displayMode$ as string); this.value$ = this.items; } diff --git a/packages/angular-sdk-components/src/lib/_components/field/selectable-card/selectable-card.component.ts b/packages/angular-sdk-components/src/lib/_components/field/selectable-card/selectable-card.component.ts index 9883f8e7..e9f5bc2b 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/selectable-card/selectable-card.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/selectable-card/selectable-card.component.ts @@ -1,13 +1,12 @@ -import { Component, Input, OnInit, OnDestroy, EventEmitter, Output } from '@angular/core'; +import { Component, Input, OnInit, EventEmitter, Output } from '@angular/core'; +import { CommonModule } from '@angular/common'; import { MatCardModule } from '@angular/material/card'; import { MatRadioModule } from '@angular/material/radio'; import { MatCheckboxModule } from '@angular/material/checkbox'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; -import { CommonModule } from '@angular/common'; import { deleteInstruction, insertInstruction } from '../../../_helpers/instructions-utils'; import { handleEvent } from '../../../_helpers/event-util'; -import { Utils } from '../../../_helpers/utils'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; +import { FieldBase } from '../field.base'; interface SelectableCardProps extends PConnFieldProps { selectionList: any; @@ -30,21 +29,15 @@ interface SelectableCardProps extends PConnFieldProps { templateUrl: './selectable-card.component.html', styleUrl: './selectable-card.component.scss' }) -export class SelectableCardComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; +export class SelectableCardComponent extends FieldBase implements OnInit { @Input() type: string; @Output() valueChange: EventEmitter = new EventEmitter(); - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; configProps$: SelectableCardProps; - value$: any; readOnly = false; disabled = false; - displayMode$: string | undefined; radioBtnValue; additionalProps; - testId; showNoValue = false; selectionKey?: string; defaultStyle = {}; @@ -61,17 +54,9 @@ export class SelectableCardComponent implements OnInit, OnDestroy { } ]; - actionsApi: object; - propName: string; + override ngOnInit(): void { + super.ngOnInit(); - constructor( - private angularPConnect: AngularPConnectService, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); // styles used in displaying common field props this.defaultStyle = { display: 'grid', @@ -84,37 +69,11 @@ export class SelectableCardComponent implements OnInit, OnDestroy { margin: '0.5rem', fontSize: '0.875rem' }; - this.checkAndUpdate(); - } - - ngOnDestroy(): void { - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } } - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - updateSelf(): void { + override updateSelf(): void { this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as SelectableCardProps; - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - const hideFieldLabels = this.configProps$.hideFieldLabels; const datasource: any = this.configProps$.datasource; const additionalProps: any = this.configProps$.additionalProps; @@ -163,7 +122,7 @@ export class SelectableCardComponent implements OnInit, OnDestroy { if (this.type === 'checkbox') { this.testId = this.configProps$.testId; - this.displayMode$ = this.configProps$.displayMode; + this.displayMode$ = this.configProps$.displayMode ?? ''; this.selectionKey = this.configProps$.selectionKey; recordKey = this.selectionKey?.split('.').pop() ?? ''; diff --git a/packages/angular-sdk-components/src/lib/_components/field/text-area/text-area.component.ts b/packages/angular-sdk-components/src/lib/_components/field/text-area/text-area.component.ts index c30e167a..290d84e0 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/text-area/text-area.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/text-area/text-area.component.ts @@ -1,14 +1,13 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; import { handleEvent } from '../../../_helpers/event-util'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface TextAreaProps extends PConnFieldProps { // If any, enter additional props that only exist on TextArea here @@ -21,141 +20,27 @@ interface TextAreaProps extends PConnFieldProps { styleUrls: ['./text-area.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, forwardRef(() => ComponentMapperComponent)] }) -export class TextAreaComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class TextAreaComponent extends FieldBase { configProps$: TextAreaProps; - label$ = ''; - value$ = ''; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; nMaxLength$: number; - displayMode$?: string = ''; - controlName$: string; - bHasForm$ = true; - componentReference = ''; - testId: string; - helperText: string; - - fieldControl = new FormControl('', null); - actionsApi: object; - propName: string; - - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve config properties this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as TextAreaProps; - if (this.configProps$.value != undefined) { - this.value$ = this.configProps$.value; - } - this.nMaxLength$ = this.pConn$.getFieldMetadata(this.pConn$.getRawConfigProps()?.value)?.maxLength || 100; - this.testId = this.configProps$.testId; - this.displayMode$ = this.configProps$.displayMode; - this.label$ = this.configProps$.label; - this.helperText = this.configProps$.helperText; - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - this.componentReference = this.pConn$.getStateProps().value; + // Extract properties from config + const { value } = this.configProps$; - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); - } + // Set component specific properties + this.value$ = value; + this.nMaxLength$ = this.pConn$.getFieldMetadata(this.pConn$.getRawConfigProps()?.value)?.maxLength || 100; } fieldOnChange(event: any) { @@ -178,23 +63,4 @@ export class TextAreaComponent implements OnInit, OnDestroy { handleEvent(this.actionsApi, 'changeNblur', this.propName, value); } } - - getErrorMessage() { - // field control gets error message from here - - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/text-input/text-input.component.ts b/packages/angular-sdk-components/src/lib/_components/field/text-input/text-input.component.ts index 305df956..3872e89f 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/text-input/text-input.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/text-input/text-input.component.ts @@ -1,14 +1,13 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; -import { AngularPConnectService, AngularPConnectData } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; import { handleEvent } from '../../../_helpers/event-util'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface TextInputProps extends PConnFieldProps { // If any, enter additional props that only exist on TextInput here @@ -21,141 +20,22 @@ interface TextInputProps extends PConnFieldProps { styleUrls: ['./text-input.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, forwardRef(() => ComponentMapperComponent)] }) -export class TextInputComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // For interaction with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class TextInputComponent extends FieldBase { configProps$: TextInputProps; - label$ = ''; - value$ = ''; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - testId = ''; - bHasForm$ = true; - componentReference = ''; - helperText: string; - placeholder: string; - - fieldControl = new FormControl('', null); - actionsApi: object; - propName: string; - - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve configuration properties this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as TextInputProps; - if (this.configProps$.value != undefined) { - this.value$ = this.configProps$.value; - } - - this.testId = this.configProps$.testId; - - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - - this.componentReference = this.pConn$.getStateProps().value; - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - timer.unsubscribe(); - }); - } + // Get and set component specific properties + const { value } = this.configProps$; + this.value$ = value; } fieldOnChange(event: any) { @@ -178,21 +58,4 @@ export class TextInputComponent implements OnInit, OnDestroy { handleEvent(this.actionsApi, 'changeNblur', this.propName, value); } } - - getErrorMessage() { - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/time/time.component.ts b/packages/angular-sdk-components/src/lib/_components/field/time/time.component.ts index ae9cb524..78d92b3d 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/time/time.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/time/time.component.ts @@ -1,15 +1,14 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; import { handleEvent } from '../../../_helpers/event-util'; import { format } from '../../../_helpers/formatters'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface TimeProps extends PConnFieldProps { // If any, enter additional props that only exist on Time here @@ -21,147 +20,29 @@ interface TimeProps extends PConnFieldProps { styleUrls: ['./time.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, forwardRef(() => ComponentMapperComponent)] }) -export class TimeComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class TimeComponent extends FieldBase { configProps$: TimeProps; - - label$ = ''; - value$: string; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - testId = ''; - bHasForm$ = true; - componentReference = ''; - helperText: string; - placeholder: string; - - fieldControl = new FormControl('', null); - actionsApi: object; - propName: string; formattedValue$: any; - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve config properties this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as TimeProps; - this.testId = this.configProps$.testId; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - if (this.configProps$.value != undefined) { - this.value$ = this.configProps$.value; - } - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); + // Extract the value property + const { value } = this.configProps$; + this.value$ = value; - if (this.displayMode$ === 'DISPLAY_ONLY' || this.displayMode$ === 'STACKED_LARGE_VAL') { + if (['DISPLAY_ONLY', 'STACKED_LARGE_VAL'].includes(this.displayMode$)) { this.formattedValue$ = format(this.value$, 'timeonly', { format: 'hh:mm A' }); } - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } - - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - this.componentReference = this.pConn$.getStateProps().value; - - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); - } } fieldOnChange(event: any) { @@ -189,19 +70,4 @@ export class TimeComponent implements OnInit, OnDestroy { handleEvent(this.actionsApi, 'changeNblur', this.propName, value); } } - - getErrorMessage() { - let errMessage = ''; - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/url/url.component.ts b/packages/angular-sdk-components/src/lib/_components/field/url/url.component.ts index 8ed73e34..aefbb17a 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/url/url.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/url/url.component.ts @@ -1,14 +1,13 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { interval } from 'rxjs'; -import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { Utils } from '../../../_helpers/utils'; + +import { FieldBase } from '../field.base'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; -import { PConnFieldProps } from '../../../_types/PConnProps.interface'; import { handleEvent } from '../../../_helpers/event-util'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface URLProps extends PConnFieldProps { // If any, enter additional props that only exist on URL here @@ -20,142 +19,22 @@ interface URLProps extends PConnFieldProps { styleUrls: ['./url.component.scss'], imports: [CommonModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, forwardRef(() => ComponentMapperComponent)] }) -export class UrlComponent implements OnInit, OnDestroy { - @Input() pConn$: typeof PConnect; - @Input() formGroup$: FormGroup; - - // Used with AngularPConnect - angularPConnectData: AngularPConnectData = {}; +export class UrlComponent extends FieldBase { configProps$: URLProps; - label$ = ''; - value$ = ''; - bRequired$ = false; - bReadonly$ = false; - bDisabled$ = false; - bVisible$ = true; - displayMode$?: string = ''; - controlName$: string; - testId = ''; - bHasForm$ = true; - componentReference = ''; - helperText: string; - placeholder: string; - - fieldControl = new FormControl('', null); - actionsApi: object; - propName: string; - - constructor( - private angularPConnect: AngularPConnectService, - private cdRef: ChangeDetectorRef, - private utils: Utils - ) {} - - ngOnInit(): void { - // First thing in initialization is registering and subscribing to the AngularPConnect service - this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.controlName$ = this.angularPConnect.getComponentID(this); - - // Then, continue on with other initialization - - // call updateSelf when initializing - // this.updateSelf(); - this.checkAndUpdate(); - - if (this.formGroup$) { - // add control to formGroup - this.formGroup$.addControl(this.controlName$, this.fieldControl); - this.fieldControl.setValue(this.value$); - this.bHasForm$ = true; - } else { - this.bReadonly$ = true; - this.bHasForm$ = false; - } - } - - ngOnDestroy(): void { - if (this.formGroup$) { - this.formGroup$.removeControl(this.controlName$); - } - - if (this.angularPConnectData.unsubscribeFn) { - this.angularPConnectData.unsubscribeFn(); - } - } - - // Callback passed when subscribing to store change - onStateChange() { - this.checkAndUpdate(); - } - - checkAndUpdate() { - // Should always check the bridge to see if the component should - // update itself (re-render) - const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); - - // ONLY call updateSelf when the component should update - if (bUpdateSelf) { - this.updateSelf(); - } - } - - // updateSelf - updateSelf(): void { - // moved this from ngOnInit() and call this from there instead... + /** + * Updates the component when there are changes in the state. + */ + override updateSelf(): void { + // Resolve config properties this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as URLProps; - if (this.configProps$.value != undefined) { - this.value$ = this.configProps$.value; - } - - this.testId = this.configProps$.testId; - this.label$ = this.configProps$.label; - this.displayMode$ = this.configProps$.displayMode; - this.helperText = this.configProps$.helperText; - this.placeholder = this.configProps$.placeholder || ''; - - this.actionsApi = this.pConn$.getActionsApi(); - this.propName = this.pConn$.getStateProps().value; - - // timeout and detectChanges to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.configProps$.required != null) { - this.bRequired$ = this.utils.getBooleanValue(this.configProps$.required); - } - this.cdRef.detectChanges(); - }); - - if (this.configProps$.visibility != null) { - this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); - } - - // disabled - if (this.configProps$.disabled != undefined) { - this.bDisabled$ = this.utils.getBooleanValue(this.configProps$.disabled); - } + // Update component common properties + this.updateComponentCommonProperties(this.configProps$); - if (this.bDisabled$) { - this.fieldControl.disable(); - } else { - this.fieldControl.enable(); - } - - if (this.configProps$.readOnly != null) { - this.bReadonly$ = this.utils.getBooleanValue(this.configProps$.readOnly); - } - - this.componentReference = this.pConn$.getStateProps().value; - - // trigger display of error message with field control - if (this.angularPConnectData.validateMessage != null && this.angularPConnectData.validateMessage != '') { - const timer = interval(100).subscribe(() => { - this.fieldControl.setErrors({ message: true }); - this.fieldControl.markAsTouched(); - - timer.unsubscribe(); - }); - } + // Extract and normalize the value property + const { value } = this.configProps$; + this.value$ = value; } fieldOnChange(event: any) { @@ -178,21 +57,4 @@ export class UrlComponent implements OnInit, OnDestroy { handleEvent(this.actionsApi, 'changeNblur', this.propName, value); } } - - getErrorMessage() { - let errMessage = ''; - - // look for validation messages for json, pre-defined or just an error pushed from workitem (400) - if (this.fieldControl.hasError('message')) { - errMessage = this.angularPConnectData.validateMessage ?? ''; - return errMessage; - } - if (this.fieldControl.hasError('required')) { - errMessage = 'You must enter a value'; - } else if (this.fieldControl.errors) { - errMessage = this.fieldControl.errors.toString(); - } - - return errMessage; - } } diff --git a/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.html b/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.html index df59a25c..5feb530c 100644 --- a/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.html +++ b/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.html @@ -74,7 +74,7 @@

{{ heading$ }}

- +
diff --git a/packages/angular-sdk-components/src/public-api.ts b/packages/angular-sdk-components/src/public-api.ts index c473adc4..ae7aef84 100644 --- a/packages/angular-sdk-components/src/public-api.ts +++ b/packages/angular-sdk-components/src/public-api.ts @@ -8,6 +8,8 @@ export * from './lib/_bridge/component-mapper/component-mapper.component'; export * from './lib/_bridge/helpers/sdk_component_map'; export * from './lib/_bridge/angular-pconnect'; +export * from './lib/_components/field/field.base'; + export * from './lib/_components/field/auto-complete/auto-complete.component'; export * from './lib/_components/field/cancel-alert/cancel-alert.component'; export * from './lib/_components/field/check-box/check-box.component'; diff --git a/projects/angular-test-app/tests/common.js b/projects/angular-test-app/tests/common.js index fd775b15..e3a88634 100644 --- a/projects/angular-test-app/tests/common.js +++ b/projects/angular-test-app/tests/common.js @@ -2,8 +2,8 @@ const { expect } = require('@playwright/test'); const { config } = require('./config'); const createCase = async (caseTypeName, page) => { - const createCase = page.locator('mat-list-item[id="create-case-button"]'); - await createCase.click(); + const createCaseBtn = page.locator('mat-list-item[id="create-case-button"]'); + await createCaseBtn.click(); const caseType = page.locator(`mat-list-item[id="case-list-item"] > span:has-text("${caseTypeName}")`); await caseType.click(); }; @@ -32,7 +32,6 @@ const login = async (username, password, page) => { }; const getAttributes = async element => { - // eslint-disable-next-line no-return-await return await element.evaluate(async ele => ele.getAttributeNames()); };