Visibility rule service refactor.

* Take into account other dependencies
* OR and AND logic support
This commit is contained in:
Kristian Ntavidi 2021-09-20 21:34:20 +03:00
parent b40e30ee60
commit d229189783
4 changed files with 198 additions and 61 deletions

View File

@ -61,8 +61,8 @@ export class FormCompositeFieldComponent {
(<FormArray>(this.form.get('multiplicityItems'))).removeAt(0); (<FormArray>(this.form.get('multiplicityItems'))).removeAt(0);
this.visibilityRulesService.annihilateId(compositeFieldId); this.visibilityRulesService.removeAllIdReferences(compositeFieldId);
fieldIds.forEach( x => this.visibilityRulesService.annihilateId(x)); fieldIds.forEach( x => this.visibilityRulesService.removeAllIdReferences(x));
} }
deleteMultipeFieldFromCompositeFormGroup() { deleteMultipeFieldFromCompositeFormGroup() {
@ -73,8 +73,8 @@ export class FormCompositeFieldComponent {
const fieldIds = (this.form.get('fields') as FormArray).controls.map(control => control.get('id').value) as string[]; const fieldIds = (this.form.get('fields') as FormArray).controls.map(control => control.get('id').value) as string[];
this.visibilityRulesService.annihilateId(currentId); this.visibilityRulesService.removeAllIdReferences(currentId);
fieldIds.forEach(x => this.visibilityRulesService.annihilateId(x)); fieldIds.forEach(x => this.visibilityRulesService.removeAllIdReferences(x));
(parent as FormArray).removeAt(index); (parent as FormArray).removeAt(index);
(parent as FormArray).controls.forEach((control, i)=>{ (parent as FormArray).controls.forEach((control, i)=>{

View File

@ -121,12 +121,26 @@ export class FormSectionComponent implements OnInit, OnChanges {
const newId = idMappings.find(y=> y.old === x.sourceControlId); const newId = idMappings.find(y=> y.old === x.sourceControlId);
return {...x, sourceControlId: newId.new}; return {...x, sourceControlId: newId.new};
}); });
const visRule: VisibilityRule = { // const visRule: VisibilityRule = {
targetControlId: idMappings.find(x => x.old === element.id).new, // targetControlId: idMappings.find(x => x.old === element.id).new,
sourceVisibilityRules: updatedRules // sourceVisibilityRules: updatedRules
} // }
const rules = updatedRules.map(x => {
return {
requiredValue: x.sourceControlValue,
sourceField: x.sourceControlId,
targetField: idMappings.find(l=> l.old === element.id).new,
type: ''
} as Rule;
});
rules.forEach(rule =>{
this.visibilityRulesService.addNewRule(rule);
})
this.visibilityRulesService.appendVisibilityRule(visRule); // this.visibilityRulesService.appendVisibilityRule(visRule);
} }
} }
@ -146,11 +160,26 @@ export class FormSectionComponent implements OnInit, OnChanges {
return {...x ,sourceControlId: idMappings.find(y => y.old === element.id).new}; return {...x ,sourceControlId: idMappings.find(y => y.old === element.id).new};
}); });
const visRule: VisibilityRule = { // const visRule: VisibilityRule = {
targetControlId: target, // targetControlId: target,
sourceVisibilityRules: updatedRules // sourceVisibilityRules: updatedRules
} // }
this.visibilityRulesService.appendVisibilityRule(visRule);
const rules = updatedRules.map(x =>{
return {
requiredValue: x.sourceControlValue,
sourceField: x.sourceControlId,
targetField: target,
type: ''
} as Rule;
})
rules.forEach(rule =>{
this.visibilityRulesService.addNewRule(rule);
})
// this.visibilityRulesService.appendVisibilityRule(visRule);
}); });

View File

@ -43,7 +43,10 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit
} }
ngOnInit() { ngOnInit() {
this.visibilityRulesService.buildVisibilityRules(this.visibilityRules, this.form); this.tocentries = this.getTocEntries();
const rules_to_append = this._enrichWithMultiplicityRules(this.tocentries);
this.visibilityRulesService.buildVisibilityRules([...this.visibilityRules, ...rules_to_append ], this.form);
// if (this.form) { // if (this.form) {
// this.form.valueChanges // this.form.valueChanges
@ -54,9 +57,7 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit
// } // }
this.visibilityRulesInstance.emit(this.visibilityRulesService); this.visibilityRulesInstance.emit(this.visibilityRulesService);
this.tocentries = this.getTocEntries();
this._enrichWithMultiplicityRules(this.tocentries);
this.hiddenEntriesIds = this._findHiddenEntries(this.tocentries); this.hiddenEntriesIds = this._findHiddenEntries(this.tocentries);
@ -96,10 +97,10 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit
private _enrichWithMultiplicityRules(tocentries: ToCEntry[]) : void { private _enrichWithMultiplicityRules(tocentries: ToCEntry[]) : Rule[] {
if (tocentries){ if (tocentries){
tocentries.forEach(entry => { return tocentries.map(entry => {
if(entry.type === ToCEntryType.Field) return; // * TODO Me to tora implementation den tha ftasei pote edo if(entry.type === ToCEntryType.Field) return []; // * TODO Me to tora implementation den tha ftasei pote edo
if(entry.type === ToCEntryType.FieldSet){ if(entry.type === ToCEntryType.FieldSet){
@ -109,24 +110,27 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit
// * UPDATE KANEI DESTROY TO COMPONENT H NGIF . PITHANOTATA NA XREIASTEI NA TO KANOUME HIDDEN AN THELOUME KATI ALLO // * UPDATE KANEI DESTROY TO COMPONENT H NGIF . PITHANOTATA NA XREIASTEI NA TO KANOUME HIDDEN AN THELOUME KATI ALLO
const multiplicity = entry.form.get('multiplicity').value; const multiplicity = entry.form.get('multiplicity').value;
if( (multiplicity.max > 1 ) && (multiplicity.min> 0) && (multiplicity.max >= multiplicity.min)){ // has valid multiplicity if( (multiplicity.max > 1 ) && (multiplicity.min> 0) && (multiplicity.max >= multiplicity.min)){ // has valid multiplicity
this._createAndAppendVisibilityRule(entry); return this._createAndAppendVisibilityRule(entry);
} }
} catch { } catch {
} }
return [];
return
} }
if(entry.subEntries){ if(entry.subEntries){
this._enrichWithMultiplicityRules(entry.subEntries); return this._enrichWithMultiplicityRules(entry.subEntries);
} }
}) })
.reduce((r,c)=>{ return [...c, ...r]},[]);
} }
return [];
} }
private _createAndAppendVisibilityRule(entry: ToCEntry): void{ private _createAndAppendVisibilityRule(entry: ToCEntry): Rule[]{
const rules_to_append = [];
if(entry && (entry.type === ToCEntryType.FieldSet)){ if(entry && (entry.type === ToCEntryType.FieldSet)){
@ -183,7 +187,7 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit
const innerDep = original_as_source.filter(x => innerCompositeFieldOriginalIds.includes(x.targetField)); const innerDep = original_as_source.filter(x => innerCompositeFieldOriginalIds.includes(x.targetField));
innerDep.forEach(x =>{ innerDep.forEach(x =>{
const newRule = {...x, sourceField: field.id, targetField: idMappings.find(l => l.original === x.targetField).multiplicityIdValue} as Rule; const newRule = {...x, sourceField: field.id, targetField: idMappings.find(l => l.original === x.targetField).multiplicityIdValue} as Rule;
this.visibilityRulesService.addNewRule(newRule); rules_to_append.push(newRule);
}) })
@ -191,7 +195,7 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit
const outerDep = original_as_source.filter(x => !innerCompositeFieldOriginalIds.includes(x.targetField)); const outerDep = original_as_source.filter(x => !innerCompositeFieldOriginalIds.includes(x.targetField));
outerDep.forEach(x =>{ outerDep.forEach(x =>{
const newRule = {...x, sourceField: field.id}; const newRule = {...x, sourceField: field.id};
this.visibilityRulesService.addNewRule(newRule); rules_to_append.push(newRule);
}) })
} }
@ -203,14 +207,14 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit
const innerDep = original_as_target.filter( x=> innerCompositeFieldOriginalIds.includes(x.sourceField)); const innerDep = original_as_target.filter( x=> innerCompositeFieldOriginalIds.includes(x.sourceField));
innerDep.forEach(x =>{ innerDep.forEach(x =>{
const newRule = {...x, targetField: field.id, sourceField: idMappings.find(l => l.original === x.sourceField).multiplicityIdValue} as Rule; const newRule = {...x, targetField: field.id, sourceField: idMappings.find(l => l.original === x.sourceField).multiplicityIdValue} as Rule;
this.visibilityRulesService.addNewRule(newRule); rules_to_append.push(newRule);
}) })
//outer dependencies //outer dependencies
const outerDep = original_as_target.filter( x=> !innerCompositeFieldOriginalIds.includes(x.sourceField)); const outerDep = original_as_target.filter( x=> !innerCompositeFieldOriginalIds.includes(x.sourceField));
outerDep.forEach(x=>{ outerDep.forEach(x=>{
const newRule = {...x, targetField: field.id} as Rule; const newRule = {...x, targetField: field.id} as Rule;
this.visibilityRulesService.addNewRule(newRule); rules_to_append.push(newRule);
}) })
} }
@ -238,13 +242,15 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit
compositeFieldAsTargetRules.forEach(x =>{ compositeFieldAsTargetRules.forEach(x =>{
idCompositeFieldMappings.forEach(l=>{ idCompositeFieldMappings.forEach(l=>{
const newRule = {...x, targetField: l.newValue}; const newRule = {...x, targetField: l.newValue};
this.visibilityRulesService.addNewRule(newRule); rules_to_append.push(newRule);
}); });
}); });
} }
} }
return rules_to_append;
} }
private _buildRecursively(form: FormGroup,whatAmI:ToCEntryType):ToCEntry{ private _buildRecursively(form: FormGroup,whatAmI:ToCEntryType):ToCEntry{

View File

@ -12,10 +12,12 @@ import { VisibilityRulesContext } from './models/visibility-rules-context';
export class VisibilityRulesService { export class VisibilityRulesService {
private readonly VISIBILITY_RULE_LOGIC: 'OR'| 'AND' = 'OR'; private readonly VISIBILITY_RULE_LOGIC: 'OR'| 'AND' = 'OR';
private readonly DEFAULTVISIBILITY = false;
private visibilityRuleContext: VisibilityRulesContext; private visibilityRuleContext: VisibilityRulesContext;
private form: AbstractControl; private form: AbstractControl;
private elementVisibilityMap = new Map<String, boolean>(); 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>(); private _changeMade$ = new Subject<void>();
@ -40,10 +42,14 @@ export class VisibilityRulesService {
public updateValueAndVisibility(id: string, value: any) { public updateValueAndVisibility(id: string, value: any) {
const visibilityRules = this.visibilityRuleContext.rules.filter(item => item.sourceVisibilityRules.filter(source => source.sourceControlId === id).length > 0); const visibilityRules = this.visibilityRuleContext.rules.filter(item => item.sourceVisibilityRules.filter(source => source.sourceControlId === id).length > 0);
visibilityRules.forEach(item => this.evaluateVisibility(item, value)); visibilityRules.forEach(item => this.evaluateVisibility(item, value, id));
} }
private evaluateVisibility(visibilityRule: VisibilityRule, value: any) { 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){ if (value instanceof Array){
@ -53,35 +59,97 @@ export class VisibilityRulesService {
const isVisible = parsedValues.map(v=>parsedSourceControlValues.includes(v)).reduce((acc,current)=> acc|| current, false); const isVisible = parsedValues.map(v=>parsedSourceControlValues.includes(v)).reduce((acc,current)=> acc|| current, false);
if(isVisible){ // if(isVisible){
this._emitChangesIfNeeded(visibilityRule.targetControlId, true); // this._emitChangesIfNeeded(visibilityRule.targetControlId, true);
this.elementVisibilityMap.set(visibilityRule.targetControlId, true); // this.elementVisibilityMap.set(visibilityRule.targetControlId, true);
return; // 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();
} }
for (let i = 0; i < visibilityRule.sourceVisibilityRules.length; i++) {
if (value != null && (this.parseValue(value) !== this.parseValue(visibilityRule.sourceVisibilityRules[i].sourceControlValue))) { if(visibilityValues.length){
this._emitChangesIfNeeded(visibilityRule.targetControlId, false); return visibilityValues.reduce((r, c)=>{
this.elementVisibilityMap.set(visibilityRule.targetControlId, false); if(this.VISIBILITY_RULE_LOGIC === 'OR'){
this.resetControlWithId(this.form, visibilityRule.targetControlId); return r || c;
//this.updateValueAndVisibility(visibilityRule.targetControlId, null); } else {
// this.clearValues(targetPathKey); return r && c;
return; }
} }, visibilityValues[0]);
} }
this._emitChangesIfNeeded(visibilityRule.targetControlId, true);
this.elementVisibilityMap.set(visibilityRule.targetControlId, true); return this.DEFAULTVISIBILITY;
//this.updateValueAndVisibility(visibilityRule.targetControlId, null);
} }
private resetVisibilityRules() { private resetVisibilityRules() {
this.elementVisibilityMap.clear(); this.elementVisibilityMap.clear();
this.elementVisibilityMap = new Map<String, boolean>(); 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(); 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) { parseValue(value: any) {
if (typeof value === 'string') { if (typeof value === 'string') {
if (isNumeric(value)) { return value; } if (isNumeric(value)) { return value; }
@ -195,24 +263,26 @@ export class VisibilityRulesService {
}).map(x => x.targetControlId); }).map(x => x.targetControlId);
} }
public appendVisibilityRule(rule: VisibilityRule): void{ // public appendVisibilityRule(rule: VisibilityRule): void{
const existingTargetRule = this.visibilityRuleContext.rules.find( r => r.targetControlId === rule.targetControlId); // const existingTargetRule = this.visibilityRuleContext.rules.find( r => r.targetControlId === rule.targetControlId);
if(existingTargetRule){ // if(existingTargetRule){
rule.sourceVisibilityRules.forEach(svr =>{ // rule.sourceVisibilityRules.forEach(svr =>{
existingTargetRule.sourceVisibilityRules.push(svr); // existingTargetRule.sourceVisibilityRules.push(svr);
}); // });
}else{ // }else{
this.visibilityRuleContext.rules.push(rule); // this.visibilityRuleContext.rules.push(rule);
} // }
} // }
//removes rule that has the specific id either as a source either as a target //removes rule that has the specific id either as a source either as a target
public annihilateId(id: string) : void{ public removeAllIdReferences(id: string) : void{
// * Remove from visibility rues and visibility rules context
//remove as a target //remove as a target
const temp = this.visibilityRuleContext.rules.map((x,i) => (x.targetControlId === id )? i : null ); const temp = this.visibilityRuleContext.rules.map((x,i) => (x.targetControlId === id )? i : null );
@ -238,10 +308,42 @@ export class VisibilityRulesService {
tbd.reverse().forEach(index =>{ tbd.reverse().forEach(index =>{
this.visibilityRuleContext.rules.splice(index,1); 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{ public addNewRule(rule: Rule): void{
const targetId = rule.targetField;
const sourceId = rule.sourceField;
this.visibilityRuleContext.addToVisibilityRulesContext(rule); 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);
} }
} }