import { AfterViewInit, Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild, Output, EventEmitter } from '@angular/core'; import { AbstractControl, AbstractControlOptions, FormArray, FormGroup } from '@angular/forms'; import { MatExpansionPanel } from '@angular/material/expansion'; 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'; import { VisibilityRulesService } from '@app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service'; import { BaseComponent } from '@common/base/base.component'; import { debounceTime, takeUntil } from 'rxjs/operators'; import { VisibilityRuleSource } from './visibility-rules/models/visibility-rule-source'; @Component({ selector: 'app-dataset-description', templateUrl: './dataset-description.component.html', styleUrls: ['./dataset-description.component.scss'] }) export class DatasetDescriptionComponent extends BaseComponent implements OnInit, AfterViewInit, OnChanges { // @ViewChild('stepper', { static: false }) stepper: MatHorizontalStepper; @Input() path: string; @Input() visibilityRules: Rule[] = []; @Input() datasetProfileId: String; @Input() linkToScroll: LinkToScroll; @Output() formChanged: EventEmitter = new EventEmitter(); @Output() fieldsetFocusChange: EventEmitter = new EventEmitter(); tocentries: ToCEntry[]; @Input() form: FormGroup; @Input() TOCENTRY_ID_PREFIX=""; @Output() visibilityRulesInstance = new EventEmitter(); public hiddenEntriesIds:string[] = []; constructor( private visibilityRulesService: VisibilityRulesService, ) { super(); } ngOnInit() { 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) { // this.form.valueChanges // .pipe(takeUntil(this._destroyed)) // .subscribe(val => { // this.formChanged.emit(val); // }); // } this.visibilityRulesInstance.emit(this.visibilityRulesService); this.hiddenEntriesIds = this._findHiddenEntries(this.tocentries); this.visibilityRulesService.visibilityChange .pipe( takeUntil(this._destroyed), debounceTime(100) ) .subscribe(_=>{ this.hiddenEntriesIds = this._findHiddenEntries(this.tocentries); }) } ngOnChanges(changes: SimpleChanges) { // When the form is changed set stepper index to 0. // if (this.stepper && changes['form'] && !changes['form'].isFirstChange()) { // this.stepper.selectedIndex = 0; // } else if (this.stepper && changes['linkToScroll'] && changes['linkToScroll'].currentValue) { // if (changes['linkToScroll'].currentValue.page >= 0) { // this.stepper.selectedIndex = changes['linkToScroll'].currentValue.page; // } // } } ngAfterViewInit() { } onAskedToScroll(panel: MatExpansionPanel, id?:string){ panel.open(); this.fieldsetFocusChange.emit(id); } private _enrichWithMultiplicityRules(tocentries: ToCEntry[]) : Rule[] { if (tocentries){ return tocentries.map(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 return this._createAndAppendVisibilityRule(entry); } } catch { } return []; } if(entry.subEntries){ return this._enrichWithMultiplicityRules(entry.subEntries); } }) .reduce((r,c)=>{ return [...c, ...r]},[]); } return []; } private _createAndAppendVisibilityRule(entry: ToCEntry): Rule[]{ const rules_to_append = []; 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; rules_to_append.push(newRule); }) //outer dependencies const outerDep = original_as_source.filter(x => !innerCompositeFieldOriginalIds.includes(x.targetField)); outerDep.forEach(x =>{ const newRule = {...x, sourceField: field.id}; rules_to_append.push(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; rules_to_append.push(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; rules_to_append.push(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}; rules_to_append.push(newRule); }); }); } } return rules_to_append; } private _buildRecursively(form: FormGroup,whatAmI:ToCEntryType):ToCEntry{ if(!form) return null; switch(whatAmI){ case ToCEntryType.Section: const sections = form.get('sections') as FormArray; const fieldsets = form.get('compositeFields') as FormArray; const tempResult:ToCEntry[] = []; if(sections &§ions.length){ sections.controls.forEach(section=>{ tempResult.push(this._buildRecursively(section as FormGroup, ToCEntryType.Section)); }); }else if(fieldsets && fieldsets.length){ fieldsets.controls.forEach(fieldset=>{ tempResult.push(this._buildRecursively(fieldset as FormGroup, ToCEntryType.FieldSet)); }); } return { form: form, id: form.get('id').value, label: form.get('title').value, numbering: '', subEntries:tempResult, subEntriesType: sections &§ions.length? ToCEntryType.Section: ToCEntryType.FieldSet, type: ToCEntryType.Section, ordinal: form.get('ordinal').value } case ToCEntryType.FieldSet: return { form: form, label:form.get('title').value, id: form.get('id').value, numbering:'s', subEntries:[], subEntriesType: ToCEntryType.Field, type: ToCEntryType.FieldSet, ordinal: form.get('ordinal').value } } } private _sortByOrdinal(tocentries: ToCEntry[]){ if(!tocentries || !tocentries.length) return; tocentries.sort(this._customCompare); tocentries.forEach(entry=>{ this._sortByOrdinal(entry.subEntries); }); } private _customCompare(a,b){ return a.ordinal - b.ordinal; } private _calculateNumbering(tocentries: ToCEntry[], depth:number[] = []){ if(!tocentries || !tocentries.length){ return; } let prefixNumbering = depth.length? depth.join('.'): ''; if(depth.length) prefixNumbering = prefixNumbering+"."; tocentries.forEach((entry,i)=>{ entry.numbering = prefixNumbering + (i+1); this._calculateNumbering(entry.subEntries, [...depth, i+1]) }); } getTocEntries(): ToCEntry[] { if (!this.form) { return []; } const result: ToCEntry[] = []; //build parent pages (this.form.get('pages') as FormArray).controls.forEach((pageElement, i) => { result.push({ id: i+'id', label: pageElement.get('title').value, type: ToCEntryType.Page, form: pageElement, numbering: (i + 1).toString(), subEntriesType: ToCEntryType.Section, subEntries:[], ordinal: pageElement.get('ordinal').value } as ToCEntry) }); result.forEach((entry,i)=>{ const sections = entry.form.get('sections') as FormArray; sections.controls.forEach(section=>{ const tempResults = this._buildRecursively(section as FormGroup,ToCEntryType.Section); entry.subEntries.push(tempResults); }); }); this._sortByOrdinal(result); //calculate numbering this._calculateNumbering(result); return result; } private _findHiddenEntries(tocentries:ToCEntry[]):string[]{ if(!tocentries) return []; const invisibleEntries:string[] = [] tocentries.forEach(entry=>{ if(entry.type === ToCEntryType.FieldSet){ const isVisible = this.visibilityRulesService.checkElementVisibility(entry.id); if(!isVisible){ invisibleEntries.push(entry.id); }else{ //check field inputs const fields = entry.form.get('fields') as FormArray; const oneFieldAtLeastIsVisible = fields.controls.some(field=> this.visibilityRulesService.checkElementVisibility(field.get('id').value)); if(!oneFieldAtLeastIsVisible){ invisibleEntries.push(entry.id); } } }else{ const hiddenEntries = this._findHiddenEntries(entry.subEntries); if(entry.subEntries&& (entry.subEntries.every(e=> hiddenEntries.includes(e.id)))){ //all children all hidden then hide parent node; invisibleEntries.push(entry.id); }else{ invisibleEntries.push(...hiddenEntries); } } }) return invisibleEntries; } } export interface ToCEntry { id: string; label: string; subEntriesType: ToCEntryType; subEntries: ToCEntry[]; type: ToCEntryType; form: AbstractControl; numbering: string; ordinal: number; } export enum ToCEntryType { Page = 0, Section = 1, FieldSet = 2, Field = 3 }