import { ApplicationRef, Injectable, NgZone } from '@angular/core'; import { AbstractControl, FormArray, FormGroup } from '@angular/forms'; import { DatasetProfileFieldViewStyle } from '@app/core/common/enum/dataset-profile-field-view-style'; import { isNumeric } from '@app/utilities/enhancers/utils'; import { Subject } from 'rxjs'; import { Rule } from '../../../../core/model/dataset-profile-definition/rule'; import { VisibilityRule } from './models/visibility-rule'; import { VisibilityRuleSource } from './models/visibility-rule-source'; import { VisibilityRulesContext } from './models/visibility-rules-context'; @Injectable() export class VisibilityRulesService { private readonly VISIBILITY_RULE_LOGIC: 'OR' | 'AND' = 'OR'; private readonly DEFAULTVISIBILITY = false; private visibilityRuleContext: VisibilityRulesContext; private form: AbstractControl; private elementVisibilityMap = new Map(); private elementComputationalMap = new Map>(); /// keep saved the values of each form control validity value private _changeMade$ = new Subject(); constructor( public applicationReference: ApplicationRef, public ngZone: NgZone ) { } public checkElementVisibility(id: string): boolean { if (this.visibilityRuleContext.rules.filter(item => item.targetControlId === id).length === 0) { return true; } return this.elementVisibilityMap.has(id) ? this.elementVisibilityMap.get(id) : false; } public buildVisibilityRules(item: Array, form: AbstractControl) { this.visibilityRuleContext = new VisibilityRulesContext(); this.visibilityRuleContext.buildVisibilityRuleContext(item || []); this.form = form; this.resetVisibilityRules(); } public updateValueAndVisibility(id: string, value: any) { const visibilityRules = this.visibilityRuleContext.rules.filter(item => item.sourceVisibilityRules.filter(source => source.sourceControlId === id).length > 0); visibilityRules.forEach(item => this.evaluateVisibility(item, value, id)); } private evaluateVisibility(visibilityRule: VisibilityRule, value: any, sourceId: string) {// source controlId is the same const targetId = visibilityRule.targetControlId; const visibilityMap = this.elementComputationalMap.get(targetId) ? this.elementComputationalMap.get(targetId) : new Map(); if (value instanceof Array) { const parsedSourceControlValues = visibilityRule.sourceVisibilityRules.map(e => this.parseValue(e.sourceControlValue)); const parsedValues = value.map(e => this.parseValue(e)); const isVisible = parsedValues.map(v => parsedSourceControlValues.includes(v)).reduce((acc, current) => acc || current, false); // if(isVisible){ // this._emitChangesIfNeeded(visibilityRule.targetControlId, true); // this.elementVisibilityMap.set(visibilityRule.targetControlId, true); // return; // } visibilityMap.set(sourceId, isVisible); } else { const visibilityDependencySource = visibilityRule.sourceVisibilityRules.filter(x => x.sourceControlId === sourceId); const shouldBeVisible = visibilityDependencySource.reduce((isVisible, x) => { const shouldBeHidden = value !== null && (this.parseValue(value) !== this.parseValue(x.sourceControlValue)); return this.VISIBILITY_RULE_LOGIC === 'OR'? (isVisible || !shouldBeHidden) : (isVisible && !shouldBeHidden); // if(value !== null && ) }, this.VISIBILITY_RULE_LOGIC === 'AND'); visibilityMap.set(sourceId, shouldBeVisible); } this.elementComputationalMap.set(targetId, visibilityMap);// unnessecary const isVisible = this._computeVisibility(targetId); this._emitChangesIfNeeded(targetId, isVisible); this.elementVisibilityMap.set(targetId, isVisible); if (!isVisible) { this.resetControlWithId(this.form, targetId); } // for (let i = 0; i < visibilityRule.sourceVisibilityRules.length; i++) { // if (value != null && (this.parseValue(value) !== this.parseValue(visibilityRule.sourceVisibilityRules[i].sourceControlValue))) { // this._emitChangesIfNeeded(visibilityRule.targetControlId, false); // this.elementVisibilityMap.set(visibilityRule.targetControlId, false); // this.resetControlWithId(this.form, visibilityRule.targetControlId); // //this.updateValueAndVisibility(visibilityRule.targetControlId, null); // // this.clearValues(targetPathKey); // return; // } // } // this._emitChangesIfNeeded(visibilityRule.targetControlId, true); // this.elementVisibilityMap.set(visibilityRule.targetControlId, true); // this.updateValueAndVisibility(visibilityRule.targetControlId, null); } private _computeVisibility(targetId: string): boolean { const visibilityMap = this.elementComputationalMap.get(targetId); const values = visibilityMap.values(); let currentVal = values.next(); let visibilityValues: boolean[] = []; while (!currentVal.done) { visibilityValues.push(currentVal.value); currentVal = values.next(); } if (visibilityValues.length) { return visibilityValues.reduce((r, c) => { if (this.VISIBILITY_RULE_LOGIC === 'OR') { return r || c; } else { return r && c; } }, visibilityValues[0]); } return this.DEFAULTVISIBILITY; } private resetVisibilityRules() { this.elementVisibilityMap.clear(); this.elementVisibilityMap = new Map(); this.elementComputationalMap.clear(); this.elementComputationalMap = new Map>(); this._populateComputationMap(); /// !IMPORTANT FOR THE AND LOGIC this._changeMade$.next(); } private _populateComputationMap(): void { this.visibilityRuleContext.rules.forEach(rule => { const targetId = rule.targetControlId; const visibilityMap = this.elementComputationalMap.get(targetId) ? this.elementComputationalMap.get(targetId) : new Map(); rule.sourceVisibilityRules.forEach(vr => { visibilityMap.set(vr.sourceControlId, this.DEFAULTVISIBILITY); }); this.elementComputationalMap.set(targetId, visibilityMap); }); } parseValue(value: any) { if (typeof value === 'string') { if (isNumeric(value)) { return value; } else if (value === 'true') { return true; } else if (value === 'false') { return false; } else { return this.translate(value); } } else { return value; } } search(path, obj, target) { for (const k in obj) { if (obj.hasOwnProperty(k)) { if (obj[k] === target) { return path + '.' + k; } else if (typeof obj[k] === 'object') { const result = this.search(path + '.' + k, obj[k], target); if (result) { return result; } } } } return false; } scanIfChildsOfCompositeFieldHasVisibleItems(compositeFieldParent: FormGroup): boolean { let isVisible = false; ((compositeFieldParent.get('fields'))).controls.forEach(element => { if (this.checkElementVisibility(element.get('id').value)) { isVisible = true; } }); return isVisible; } private translate(item: any) { try { return JSON.parse(item).value; } catch (error) { return item; } } private resetControlWithId(formControl: AbstractControl, id: string) { if (formControl instanceof FormGroup) { if ((formControl as FormGroup).contains('id') && (formControl as FormGroup).contains('value') && (formControl as FormGroup).get('id').value === id) { this.resetFieldFormGroup(formControl); } if ((formControl as FormGroup).contains('id') && (formControl as FormGroup).contains('fields') && (formControl as FormGroup).get('id').value === id) { this.resetCompositeFieldFormGroup(formControl); } else { Object.keys(formControl.controls).forEach(item => { const control = formControl.get(item); this.resetControlWithId(control, id); }); } } else if (formControl instanceof FormArray) { formControl.controls.forEach(item => { this.resetControlWithId(item, id); }); } } private resetFieldFormGroup(formGroup: FormGroup) { const renderStyle = formGroup.getRawValue().viewStyle.renderStyle; if (renderStyle === DatasetProfileFieldViewStyle.Validation || renderStyle === DatasetProfileFieldViewStyle.DatasetIdentifier) { formGroup.get('value').setValue({ identifier: '', type: '' }); } else { formGroup.get('value').setValue(formGroup.get('defaultValue').value ? this.parseValue(formGroup.get('defaultValue').value.value) : undefined); } } private resetCompositeFieldFormGroup(formGroup: FormGroup) { (formGroup.get('fields') as FormArray).controls.forEach((element: FormGroup) => { this.resetFieldFormGroup(element); }); (formGroup.get('multiplicityItems') as FormArray).controls.splice(0); } private _emitChangesIfNeeded(id: string, valueToBeSet: boolean) { if (this.elementVisibilityMap.has(id)) { if (this.elementVisibilityMap.get(id) != valueToBeSet) { this._changeMade$.next(); } } else { this._changeMade$.next(); } } public get visibilityChange() { return this._changeMade$.asObservable(); } public getVisibilityDependency(targetId: string): VisibilityRuleSource[] | null { return this.visibilityRuleContext.rules.reduce((hasDependency, rule) => { if (hasDependency) return hasDependency; if (rule.targetControlId === targetId) { return rule.sourceVisibilityRules; } return null; }, null) as VisibilityRuleSource[]; } public getVisibilityTargets(sourceId: string): string[] { return this.visibilityRuleContext.rules.filter(x => { const result = x.sourceVisibilityRules.filter(y => y.sourceControlId === sourceId); return result.length; }).map(x => x.targetControlId); } // public appendVisibilityRule(rule: VisibilityRule): void{ // const existingTargetRule = this.visibilityRuleContext.rules.find( r => r.targetControlId === rule.targetControlId); // if(existingTargetRule){ // rule.sourceVisibilityRules.forEach(svr =>{ // existingTargetRule.sourceVisibilityRules.push(svr); // }); // }else{ // this.visibilityRuleContext.rules.push(rule); // } // } //removes rule that has the specific id either as a source either as a target public removeAllIdReferences(id: string): void { // * Remove from visibility rues and visibility rules context //remove as a target const temp = this.visibilityRuleContext.rules.map((x, i) => (x.targetControlId === id) ? i : null); const indexes = temp.filter(x => x !== null); indexes.reverse().forEach(index => this.visibilityRuleContext.rules.splice(index, 1)); this.elementVisibilityMap.delete(id); //remove as a source const tbd = this.visibilityRuleContext.rules.reduce((to_be_deleted, rule, ruleIdx) => { const idxs = rule.sourceVisibilityRules.map((x, i) => (x.sourceControlId === id) ? i : null).filter(x => x !== null); idxs.reverse().forEach(index => rule.sourceVisibilityRules.splice(index, 1)); if (!rule.sourceVisibilityRules.length) { to_be_deleted.push(ruleIdx); } return to_be_deleted }, []); //clean up empty tbd.reverse().forEach(index => { this.visibilityRuleContext.rules.splice(index, 1); }); // * Remove from computational map // as a target if (this.elementComputationalMap.get(id)) { this.elementComputationalMap.delete(id); } // as a source const keyIterator = this.elementComputationalMap.keys(); let currentKey = keyIterator.next(); while (!currentKey.done) { const currentVals = this.elementComputationalMap.get(currentKey.value); currentVals.delete(id); currentKey = keyIterator.next(); } } public addNewRule(rule: Rule): void { const targetId = rule.targetField; const sourceId = rule.sourceField; this.visibilityRuleContext.addToVisibilityRulesContext(rule); const visibilityMap = this.elementComputationalMap.get(targetId) ? this.elementComputationalMap.get(targetId) : new Map(); visibilityMap.set(sourceId, this.DEFAULTVISIBILITY); const isVisible = this._computeVisibility(targetId); this._emitChangesIfNeeded(targetId, isVisible); this.elementVisibilityMap.set(targetId, isVisible); } }