argos/dmp-frontend/src/app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service.ts

350 lines
12 KiB
TypeScript

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<String, boolean>();
private elementComputationalMap = new Map<String, Map<String, boolean>>(); /// keep saved the values of each form control validity value
private _changeMade$ = new Subject<void>();
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<Rule>, 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<String, boolean>();
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);
visibilityDependencySource.forEach(x => {
const shouldBeHidden = value !== null && (this.parseValue(value) !== this.parseValue(x.sourceControlValue));
// if(value !== null && )
visibilityMap.set(sourceId, !shouldBeHidden);
});
}
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<String, boolean>();
this.elementComputationalMap.clear();
this.elementComputationalMap = new Map<String, Map<String, boolean>>();
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<String, boolean>();
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;
(<FormArray>(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<String, boolean>();
visibilityMap.set(sourceId, this.DEFAULTVISIBILITY);
const isVisible = this._computeVisibility(targetId);
this._emitChangesIfNeeded(targetId, isVisible);
this.elementVisibilityMap.set(targetId, isVisible);
}
}