diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/components/form-composite-field/form-composite-field.component.ts b/dmp-frontend/src/app/ui/misc/dataset-description-form/components/form-composite-field/form-composite-field.component.ts index 341749ef1..c3f45585e 100644 --- a/dmp-frontend/src/app/ui/misc/dataset-description-form/components/form-composite-field/form-composite-field.component.ts +++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/components/form-composite-field/form-composite-field.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnInit } from '@angular/core'; -import { FormArray, FormGroup, AbstractControl } from '@angular/forms'; +import { FormArray, FormGroup, AbstractControl, FormArrayName } from '@angular/forms'; import { DatasetDescriptionCompositeFieldEditorModel, DatasetDescriptionFieldEditorModel } from '../../dataset-description-form.model'; import { ToCEntry } from '../../dataset-description.component'; import { VisibilityRulesService } from '../../visibility-rules/visibility-rules.service'; @@ -49,17 +49,33 @@ export class FormCompositeFieldComponent { } deleteCompositeFieldFormGroup() { + + const compositeFieldId = ((this.form.get('multiplicityItems') as FormArray).get(''+0) as FormGroup).getRawValue().id; + const fieldIds = (this.form.get('fields') as FormArray).controls.map(control => control.get('id').value) as string[]; + const numberOfItems = this.form.get('multiplicityItems').get('' + 0).get('fields').value.length; for (let i = 0; i < numberOfItems; i++) { const multiplicityItem = this.form.get('multiplicityItems').get('' + 0).get('fields').get('' + i).value; this.form.get('fields').get('' + i).patchValue(multiplicityItem); } ((this.form.get('multiplicityItems'))).removeAt(0); + + + this.visibilityRulesService.annihilateId(compositeFieldId); + fieldIds.forEach( x => this.visibilityRulesService.annihilateId(x)); } deleteMultipeFieldFromCompositeFormGroup() { const parent = this.form.parent; const index = (parent as FormArray).controls.indexOf(this.form); + + const currentId = this.form.get('id').value; + const fieldIds = (this.form.get('fields') as FormArray).controls.map(control => control.get('id').value) as string[]; + + + this.visibilityRulesService.annihilateId(currentId); + fieldIds.forEach(x => this.visibilityRulesService.annihilateId(x)); + (parent as FormArray).removeAt(index); (parent as FormArray).controls.forEach((control, i)=>{ try{ diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/components/form-section/form-section.component.ts b/dmp-frontend/src/app/ui/misc/dataset-description-form/components/form-section/form-section.component.ts index 66f67db4f..248e2712e 100644 --- a/dmp-frontend/src/app/ui/misc/dataset-description-form/components/form-section/form-section.component.ts +++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/components/form-section/form-section.component.ts @@ -6,6 +6,9 @@ import { DatasetDescriptionSectionEditorModel, DatasetDescriptionCompositeFieldE import { FormCompositeFieldComponent } from '../form-composite-field/form-composite-field.component'; import { LinkToScroll } from '../../tableOfContentsMaterial/table-of-contents'; import { ToCEntry, ToCEntryType } from '../../dataset-description.component'; +import { VisibilityRuleSource } from '../../visibility-rules/models/visibility-rule-source'; +import { VisibilityRule } from '../../visibility-rules/models/visibility-rule'; +import { Rule } from '@app/core/model/dataset-profile-definition/rule'; @Component({ @@ -70,8 +73,118 @@ export class FormSectionComponent implements OnInit, OnChanges { addMultipleField(fieldsetIndex: number) { const compositeFieldToBeCloned = (this.form.get('compositeFields').get('' + fieldsetIndex) as FormGroup).getRawValue(); - const multiplicityItemsArray = ((this.form.get('compositeFields').get('' + fieldsetIndex).get('multiplicityItems')));; - const compositeField: DatasetDescriptionCompositeFieldEditorModel = new DatasetDescriptionCompositeFieldEditorModel().cloneForMultiplicity(compositeFieldToBeCloned, multiplicityItemsArray.length); + const multiplicityItemsArray = ((this.form.get('compositeFields').get('' + fieldsetIndex).get('multiplicityItems'))); + const idMappings = [] as {old: string, new: string}[]; + const compositeField: DatasetDescriptionCompositeFieldEditorModel = new DatasetDescriptionCompositeFieldEditorModel().cloneForMultiplicity(compositeFieldToBeCloned, multiplicityItemsArray.length,idMappings); + + + + + // ** COMPOSITE FIELD IS SHOWN OR HIDDEN FROM ANOTHER ELEMENT + const compositeFieldVisibilityDependencies = this.visibilityRulesService.getVisibilityDependency(compositeFieldToBeCloned); + if(compositeFieldVisibilityDependencies && compositeFieldVisibilityDependencies.length){ + + compositeFieldVisibilityDependencies.forEach(x =>{ + const visRule: Rule = { + targetField: compositeField.id, + sourceField: x.sourceControlId, + requiredValue: x.sourceControlValue, + type: '' + } + this.visibilityRulesService.addNewRule(visRule); + }); + } + + // console.log('idMappings ', idMappings); + compositeFieldToBeCloned.fields.forEach(element => { + // console.log(this.visibilityRulesService.getVisibilityDependency(element.id)); + const dependency = this.visibilityRulesService.getVisibilityDependency(element.id); + + + + try{ + + if(dependency && dependency.length){ + + // * INNER VISIBILITY DEPENDENCIES + // * IF INNER INPUT HIDES ANOTHER INNER INPUT + + const innerDependency = compositeFieldToBeCloned.fields.reduce((innerD, currentElement )=>{ + + const innerDependecies = dependency.filter(d => d.sourceControlId === currentElement.id); + return[...innerD, ...innerDependecies]; + },[]) as VisibilityRuleSource[]; + + if(innerDependency.length){ + //Build visibility source + const updatedRules = innerDependency.map(x => { + const newId = idMappings.find(y=> y.old === x.sourceControlId); + return {...x, sourceControlId: newId.new}; + }); + const visRule: VisibilityRule = { + targetControlId: idMappings.find(x => x.old === element.id).new, + sourceVisibilityRules: updatedRules + } + + this.visibilityRulesService.appendVisibilityRule(visRule); + } + + } + + + // * OUTER DEPENDENCIES + + // * IF INNER INPUT HIDES OUTER INPUTS + const innerIds = idMappings.map(x => x.old) as string[]; + + const outerTargets = this.visibilityRulesService.getVisibilityTargets(element.id).filter( x=> !innerIds.includes(x)); + + outerTargets.forEach(target =>{ + + const outerRules = (this.visibilityRulesService.getVisibilityDependency(target) as VisibilityRuleSource[]).filter(x => x.sourceControlId === element.id); + const updatedRules = outerRules.map(x => { + return {...x ,sourceControlId: idMappings.find(y => y.old === element.id).new}; + }); + + const visRule: VisibilityRule = { + targetControlId: target, + sourceVisibilityRules: updatedRules + } + this.visibilityRulesService.appendVisibilityRule(visRule); + + }); + + + + + + // * IF INNER INPUT IS HIDDEN BY OUTER INPUT + + if(dependency && dependency.length){ + const fieldsThatHideInnerElement = dependency.filter(x => !innerIds.includes(x.sourceControlId)); + + if( fieldsThatHideInnerElement.length){ + fieldsThatHideInnerElement.forEach(x =>{ + const visRule: Rule = { + targetField: idMappings.find(l => l.old === element.id).new, + sourceField: x.sourceControlId, + requiredValue: x.sourceControlValue, + type: '' + } + this.visibilityRulesService.addNewRule(visRule); + }); + } + } + + + // console.log(`for ${element.id} targets are`, outerTargets); + } catch { + console.log('error'); + } + + }); + // console.log(this.visibilityRulesService); + // console.log(this.visibilityRulesService.getVisibilityDependency()); multiplicityItemsArray.push(compositeField.buildForm()); } diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.model.ts b/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.model.ts index 0b7e6713e..de0e2e870 100644 --- a/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.model.ts +++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.model.ts @@ -194,11 +194,17 @@ export class DatasetDescriptionCompositeFieldEditorModel extends BaseFormModel { // return newItem; // } - cloneForMultiplicity(item: CompositeField, ordinal: number): DatasetDescriptionCompositeFieldEditorModel { + cloneForMultiplicity(item: CompositeField, ordinal: number, idMappings:{old:string, new:string}[] = []): DatasetDescriptionCompositeFieldEditorModel { const newItem: DatasetDescriptionCompositeFieldEditorModel = new DatasetDescriptionCompositeFieldEditorModel(); newItem.id = 'multiple_' + item.id + '_' + Guid.create(); + + idMappings.push({old: item.id, new: newItem.id }); item.fields.forEach((field, index) => { - newItem.fields.push(new DatasetDescriptionFieldEditorModel().cloneForMultiplicity(field, newItem.id)); + + const clonedItem = new DatasetDescriptionFieldEditorModel().cloneForMultiplicity(field, newItem.id) + newItem.fields.push(clonedItem); + + idMappings.push({old: field.id, new: clonedItem.id}); }); newItem.ordinal = ordinal; return newItem; @@ -238,7 +244,7 @@ export class DatasetDescriptionFieldEditorModel extends BaseFormModel { if (item.multiplicity) this.multiplicity = new DatasetDescriptionMultiplicityEditorModel().fromModel(item.multiplicity); if (item.defaultValue) this.defaultValue = new DatasetDescriptionDefaultValueEditorModel().fromModel(item.defaultValue); this.value = item.value ? item.value : (this.defaultValue.value ? this.defaultValue.value : undefined); - if (this.viewStyle.renderStyle === 'checkBox') { this.value = this.value === 'true'; } + if (this.viewStyle.renderStyle === 'checkBox' && (item.value !== true)) { this.value = this.value === 'true'; } //Cover both posibilites of boolean true or false and string 'true' or 'false' if (item.multiplicityItems) { item.multiplicityItems.map(x => this.multiplicityItems.push(new DatasetDescriptionFieldEditorModel().fromModel(x))); } this.data = item.data; return this; diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description.component.ts b/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description.component.ts index bcf78bf99..d18a6300d 100644 --- a/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description.component.ts +++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description.component.ts @@ -2,6 +2,8 @@ import { AfterViewInit, Component, Input, OnChanges, OnInit, SimpleChanges, View import { AbstractControl, AbstractControlOptions, FormArray, FormGroup } from '@angular/forms'; import { MatExpansionPanel } from '@angular/material'; import { MatHorizontalStepper } from '@angular/material/stepper'; +import { CompositeField } from '@app/core/model/dataset-profile-definition/composite-field'; +import { Field } from '@app/core/model/dataset-profile-definition/field'; import { Rule } from '@app/core/model/dataset-profile-definition/rule'; import { DatasetProfileTableOfContentsInternalSection } from '@app/ui/admin/dataset-profile/table-of-contents/table-of-contents-internal-section/table-of-contents-internal-section'; import { LinkToScroll } from '@app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents'; @@ -53,6 +55,10 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit this.visibilityRulesInstance.emit(this.visibilityRulesService); this.tocentries = this.getTocEntries(); + + this._enrichWithMultiplicityRules(this.tocentries); + + this.hiddenEntriesIds = this._findHiddenEntries(this.tocentries); this.visibilityRulesService.visibilityChange @@ -90,6 +96,156 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit + private _enrichWithMultiplicityRules(tocentries: ToCEntry[]) : void { + if (tocentries){ + tocentries.forEach(entry => { + if(entry.type === ToCEntryType.Field) return; // * TODO Me to tora implementation den tha ftasei pote edo + + + if(entry.type === ToCEntryType.FieldSet){ + // if(multiplicity: ) + try { + // TODO OTAN KANW HIDE MULTIPLE PEDIO TOTE STO ON SHOW HANO TA VALUES (AUTO MPOREI NA EINAI KAI LEGIT) ('NA DOUME AN ONTOS DIAGRAFONTAI I APLA DEN TA DEIXNOUME') + // * 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; + if( (multiplicity.max > 1 ) && (multiplicity.min> 0) && (multiplicity.max >= multiplicity.min)){ // has valid multiplicity + this._createAndAppendVisibilityRule(entry); + } + } catch { + + } + + + return + } + + if(entry.subEntries){ + this._enrichWithMultiplicityRules(entry.subEntries); + } + }) + } + } + + private _createAndAppendVisibilityRule(entry: ToCEntry): void{ + + if(entry && (entry.type === ToCEntryType.FieldSet)){ + + //childs that are either target or source + const childIdsWithVisRules = (entry.form.get('fields') as FormArray).controls.reduce((all, s) => + { + const sval = s.value as Field; + return this.visibilityRules.find(x => (x.targetField === sval.id) || (x.sourceField === sval.id)) ? [...all, sval] : all; + },[]) as Field[]; + + + const innerCompositeFieldOriginalIds = (entry.form.get('fields') as FormArray).controls.map( x=> x.get('id').value) as string[]; + + //multiplicity items + const multiplicityItemsValue = entry.form.get('multiplicityItems').value as CompositeField[]; + + + // ********* FIELDS OF FIELDSET ARE EITHER TARGETS OR SOURCES ***** + + + if( childIdsWithVisRules.length && multiplicityItemsValue && multiplicityItemsValue.length ){ + //check each multiplicity item composite field + multiplicityItemsValue.forEach( mi =>{ + + const multiplicityCompositeFieldIds = mi.fields.map(x => x.id); + const idMappings = multiplicityCompositeFieldIds.map(x => { + return { + original: innerCompositeFieldOriginalIds.find( l => x.includes(l)), + multiplicityIdValue: x + } + }) as {original: string, multiplicityIdValue: string}[]; + + //each field of mutliplicity item + mi.fields.forEach(field =>{ + + + //get original visibility rules (original field) + + //original id + const original_id = childIdsWithVisRules.find(x=> field.id.includes(x.id)).id; + + + //get vis rules + + //as source + const original_as_source = this.visibilityRules.filter( x => x.sourceField === original_id); + const original_as_target = this.visibilityRules.filter( x => x.targetField === original_id); + + + + if(original_as_source.length){ + + //inner dependencies + const innerDep = original_as_source.filter(x => innerCompositeFieldOriginalIds.includes(x.targetField)); + innerDep.forEach(x =>{ + const newRule = {...x, sourceField: field.id, targetField: idMappings.find(l => l.original === x.targetField).multiplicityIdValue} as Rule; + this.visibilityRulesService.addNewRule(newRule); + }) + + + //outer dependencies + const outerDep = original_as_source.filter(x => !innerCompositeFieldOriginalIds.includes(x.targetField)); + outerDep.forEach(x =>{ + const newRule = {...x, sourceField: field.id}; + this.visibilityRulesService.addNewRule(newRule); + }) + } + + + + if( original_as_target.length){ + + //inner dependencies + const innerDep = original_as_target.filter( x=> innerCompositeFieldOriginalIds.includes(x.sourceField)); + innerDep.forEach(x =>{ + const newRule = {...x, targetField: field.id, sourceField: idMappings.find(l => l.original === x.sourceField).multiplicityIdValue} as Rule; + this.visibilityRulesService.addNewRule(newRule); + }) + + //outer dependencies + const outerDep = original_as_target.filter( x=> !innerCompositeFieldOriginalIds.includes(x.sourceField)); + outerDep.forEach(x=>{ + const newRule = {...x, targetField: field.id} as Rule; + this.visibilityRulesService.addNewRule(newRule); + }) + } + + }) + }); + } + + + + + // ** FIELDSET ITSELF IS TARGET + // ** source it can never be + + const compositeFieldAsTargetRules = this.visibilityRules.filter(x => x.targetField === entry.id); + const idCompositeFieldMappings = multiplicityItemsValue.map(x =>{ + return { + originalValue: entry.id, + newValue:x.id + } + }) as {originalValue: string, newValue: string}[]; + + + if(compositeFieldAsTargetRules.length){ + + compositeFieldAsTargetRules.forEach(x =>{ + idCompositeFieldMappings.forEach(l=>{ + const newRule = {...x, targetField: l.newValue}; + this.visibilityRulesService.addNewRule(newRule); + }); + }); + } + + + } + } private _buildRecursively(form: FormGroup,whatAmI:ToCEntryType):ToCEntry{ if(!form) return null; diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/visibility-rules/models/visibility-rules-context.ts b/dmp-frontend/src/app/ui/misc/dataset-description-form/visibility-rules/models/visibility-rules-context.ts index 3df90cf19..2eba9ff10 100644 --- a/dmp-frontend/src/app/ui/misc/dataset-description-form/visibility-rules/models/visibility-rules-context.ts +++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/visibility-rules/models/visibility-rules-context.ts @@ -21,10 +21,22 @@ export class VisibilityRulesContext { }); } - private addToVisibilityRulesContext(item: Rule): void { + public addToVisibilityRulesContext(item: Rule): void { for (let i = 0; i < this.rules.length; i++) { if (this.rules[i].targetControlId === item.targetField) { - this.rules[i].sourceVisibilityRules.push({ sourceControlId: item.sourceField, sourceControlValue: item.requiredValue }); + + const newRule = { sourceControlId: item.sourceField, sourceControlValue: item.requiredValue }; + const ruleExists = this.rules[i].sourceVisibilityRules.find(x =>{ + return Object.keys(x).reduce((exact, key)=>{ + if(!exact) return false; + return x[key] === newRule[key]; + },true); + }) + + if(!ruleExists){ + this.rules[i].sourceVisibilityRules.push(newRule); + } + return; } } diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service.ts b/dmp-frontend/src/app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service.ts index e730193c7..02b4cfd6f 100644 --- a/dmp-frontend/src/app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service.ts +++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service.ts @@ -5,17 +5,20 @@ import { Subject } from 'rxjs'; import { isNumeric } from 'rxjs/internal/util/isNumeric'; 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 visibilityRuleContext: VisibilityRulesContext; private form: AbstractControl; private elementVisibilityMap = new Map(); private _changeMade$ = new Subject(); + constructor( public applicationReference: ApplicationRef, public ngZone: NgZone @@ -173,4 +176,72 @@ export class VisibilityRulesService { 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 annihilateId(id: string) : void{ + + //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); + }); + } + + + public addNewRule(rule: Rule): void{ + this.visibilityRuleContext.addToVisibilityRulesContext(rule); + } }