argos/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description.compone...

416 lines
12 KiB
TypeScript

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<any> = new EventEmitter();
@Output() fieldsetFocusChange: EventEmitter<string> = new EventEmitter<string>();
tocentries: ToCEntry[];
@Input() form: FormGroup;
@Input() TOCENTRY_ID_PREFIX="";
@Output() visibilityRulesInstance = new EventEmitter<VisibilityRulesService>();
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 &&sections.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 &&sections.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
}