Merge pull request #83 in TEA/mina-sidor-fa-web from feature/TV-396 to develop

Squashed commit of the following:

commit f5029b04d2117df86eaf6692c88bdc692059d8d6
Merge: 3d776b1 45f2fb5
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Sat Sep 11 06:17:07 2021 +0200

    Merge branch 'develop-remote' into feature/TV-396

commit 3d776b10824be6b54e186104a5bcd351e5b2fb42
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Fri Sep 10 23:26:47 2021 +0200

    TV-396 fixed some tests and so on

commit bd57fce383ba409ae8de1869c242b5a8f51071d2
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Fri Sep 10 23:20:26 2021 +0200

    TV-396 made some adjustments to the validation logic after feedback in PR

commit 942cddb263d0965e772f7f34305e85737da76df4
Merge: 174dfe9 ceee702
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Fri Sep 10 14:10:23 2021 +0200

    Merge branch 'develop-remote' into feature/TV-396

commit 174dfe924f2eac979992275ddd55ed0758144efb
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Fri Sep 10 14:05:39 2021 +0200

    TV-396 fixed issue with general info after restructuring..

commit 7e0d4bdf9e76e0fb58fe30358c3e729cce1f9260
Merge: da02f6c 5b00453
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Fri Sep 10 12:00:29 2021 +0200

    Merge branch 'develop-remote' into feature/TV-396

commit da02f6cc7f4a9405ad1d8167ef18729b18973d61
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Fri Sep 10 10:43:42 2021 +0200

    TV-396 added aria-expanded attribute

commit 48eb24ca6e354b44ae4d4b62ce2ffa496743d0b5
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Fri Sep 10 09:46:39 2021 +0200

    TV-396 moved some logic into seperate template

commit 0ef787d6c3700677ae793c486930b07748365412
Merge: 6dfd7b0 5f81d6f
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Thu Sep 9 17:34:40 2021 +0200

    Merge branch 'develop-remote' into feature/TV-396

commit 6dfd7b00caa45d335f3fe8619b92c282038ac5cb
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Thu Sep 9 17:33:54 2021 +0200

    TV-396 used digi internal link instead of basic a-tag

commit 46c17011b7f6b1f628b14ccf020c06cdc95627c8
Merge: 4338e15 1938b94
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Thu Sep 9 15:07:16 2021 +0200

    Merge branch 'develop-remote' into feature/TV-396

commit 4338e153f7e4ebdf8ab65a64a6194dbd4d9fa9c7
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Thu Sep 9 14:56:26 2021 +0200

    TV-396 added error summary an validation handling for the edit form etc.

commit ebb2e76993b99756d5a641ab8ca7d137be8a982f
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Wed Sep 8 22:58:44 2021 +0200

    TV-396 making sure utforande verksamheter and addresses are populated as they should be when editing an employee.

commit 01f4c9bf7ad8fc4ad44b0e8945182492b864d0cf
Merge: 1c2aa92 b06436a
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Wed Sep 8 22:07:25 2021 +0200

    Merge branch 'develop-remote' into feature/TV-396

commit 1c2aa92f21aac57036ed05d5ebeab0f0e6a45c2c
Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se>
Date:   Wed Sep 8 22:07:00 2021 +0200

    Merge branch 'develop-remote' into bugs/TV-520

    # Conflicts:
    #	apps/mina-sidor-fa/src/app/pages/administration/pages/employee-card/employee-card.component.html
This commit is contained in:
Christian Gårdebrink
2021-09-11 06:19:16 +02:00
parent 45f2fb577a
commit 723ad02092
26 changed files with 724 additions and 723 deletions

View File

@@ -0,0 +1,34 @@
<div class="error-list-wrapper" [hidden]="!validationErrorLinks || validationErrorLinks.length === 0">
<digi-notification-alert
class="error-list"
af-variation="danger"
[attr.af-heading]="headingText"
[af-heading-level]="headingLevel"
[afCloseable]="false"
>
<ul class="error-list__validation-error-links">
<li *ngFor="let validationErrorLink of validationErrorLinks;">
<digi-ng-link-internal
msfaAnchorLink
class="error-list__validation-error-link"
[afHref]="'#' + validationErrorLink.elementId"
[afText]="validationErrorLink.text"
></digi-ng-link-internal>
</li>
</ul>
</digi-notification-alert>
</div>
<!-- <digi-form-error-list
class="edit-employee-form__validation-error-summary"
af-heading="Åtgärda följande fel för att spara dina ändringar:"
*ngIf="validationErrorLinks && validationErrorLinks.length !== 0"
>
Behöver hantera ankarlänkar kopplat till den här komponenten om det ska fungera att använda den...
<a
[attr.href]="'#' + validationErrorLink.elementId"
[attr.id]="first ? firstValidationErrorLinkId : 'validation-error-link-' + index"
*ngFor="let validationErrorLink of validationErrorLinks; let first = first; let index = index"
>
{{ validationErrorLink.text }}
</a>
</digi-form-error-list> -->

View File

@@ -0,0 +1,15 @@
@import 'mixins/list';
@import 'variables/gutters';
.error-list {
display: block;
margin: 1.5rem 0;
&__validation-error-links {
@include msfa__reset-list;
display: flex;
flex-direction: column;
gap: $digi--layout--gutter;
margin: 1.5rem 0;
}
}

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ErrorListComponent } from './error-list.component';
describe('ErrorListComponent', () => {
let component: ErrorListComponent;
let fixture: ComponentFixture<ErrorListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ErrorListComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ErrorListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,19 @@
import { TypographyDynamicHeadingLevel } from '@af/digi-ng/_typography/typography-dynamic-heading';
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
export interface ValidationErrorLink {
elementId: string;
text: string;
}
@Component({
selector: 'msfa-error-list',
templateUrl: './error-list.component.html',
styleUrls: ['./error-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ErrorListComponent {
@Input() validationErrorLinks: ValidationErrorLink[] = [];
@Input() headingText: string;
@Input() headingLevel: TypographyDynamicHeadingLevel = TypographyDynamicHeadingLevel.H3;
}

View File

@@ -0,0 +1,13 @@
import { DigiNgLinkInternalModule } from '@af/digi-ng/_link/link-internal';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AnchorLinkModule } from '@msfa-directives/anchor-link.module';
import { ErrorListComponent } from './error-list.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [ErrorListComponent],
imports: [CommonModule, AnchorLinkModule, DigiNgLinkInternalModule],
exports: [ErrorListComponent],
})
export class ErrorListModule {}

View File

@@ -1,88 +1,115 @@
<div class="expanded-tree-node" *ngIf="treeNodeModel">
<digi-ng-typography-dynamic-heading
class="expanded-tree-node__heading"
[afText]="getTreeNodeHeadingText(treeNodeModel)"
[afLevel]="headingLevel"
></digi-ng-typography-dynamic-heading>
<p class="expanded-tree-node__info" *ngIf="treeNodeModel.showGeneralInfoAboutGrandChildren">
{{treeNodeModel.grandChildrenInfo}}
</p>
<div
*ngIf="treeNodeModel.children && !treeNodeModel.showGeneralInfoAboutGrandChildren"
class="expanded-tree-node__filter"
>
<digi-form-input-search
[attr.af-variation]="FormInputSearchVariation.S"
[attr.af-button-text]="getFilterButtonAriaLabelText(treeNodeModel)"
[attr.af-button-type]="ButtonType.BUTTON"
[attr.af-label]="' '"
[attr.af-aria-labelledby]="getFilterDescriptionId(treeNodeModel)"
(afOnFocusOutside)="onFocusOutsideFilter($event)"
(afOnChange)="onFilterTextChanged($event, treeNodeModel)"
(afOnKeyup)="onFilterTextChanged($event, treeNodeModel)"
></digi-form-input-search>
<div class="msfa__a11y-sr-only" [attr.id]="getFilterDescriptionId(treeNodeModel)">
Filtrera valbara alternativ där deras namn måste innehålla den angivna texten.
</div>
<div
class="expanded-tree-node"
[ngClass]="{
'expanded-tree-node--is-root-node' : treeNodeModel?.isRoot
}"
>
<div class="expanded-tree-node__content">
<digi-ng-typography-dynamic-heading
class="expanded-tree-node__heading"
[afText]="headingText"
[afLevel]="headingLevel"
></digi-ng-typography-dynamic-heading>
<ng-container *ngIf="showGeneralInfo else treeNodeChildrenTemplate">
<p class="expanded-tree-node__info">{{generalInfo}}</p>
</ng-container>
</div>
<div
*ngIf="hasChildLeafNodes(treeNodeModel)"
class="expanded-tree-node__node-checkbox-presentation expanded-tree-node__node-checkbox-presentation--toggle-all"
[ngClass]="{
'expanded-tree-node__node-checkbox-presentation--focus': treeNodeModel.toggleAllHasFocus,
'expanded-tree-node__node-checkbox-presentation--checked': allChildLeafNodesAreSelected(treeNodeModel)
}"
[attr.id]="getPresentationToggleAllId(treeNodeModel)"
(click)="nodePresentationToggleAllClicked(treeNodeModel)"
>
<span class="expanded-tree-node__node-checkbox-presentation__box"></span>
<span class="expanded-tree-node__node-checkbox-presentation__text">{{treeNodeModel.toggleAllChildrenLabel}}</span>
</div>
<ng-container *ngIf="visibleChildren">
<ul *ngIf="!treeNodeModel.showGeneralInfoAboutGrandChildren" class="expanded-tree-node__nodes">
<li
*ngFor="let childNode of visibleChildren"
[attr.id]="getPresentationItemId(childNode)"
[ngClass]="{'expanded-tree-node__node--leaf' : isLeafNode(childNode)}"
class="expanded-tree-node__node"
>
<ng-container
[ngTemplateOutlet]="childNode.children ? nodeExpansionPresentationTemplate : nodeCheckboxPresentationTemplate"
[ngTemplateOutletContext]="{node: childNode, parentNode: treeNodeModel}"
></ng-container>
</li>
</ul>
</ng-container>
<ng-template #nodeCheckboxPresentationTemplate let-node="node">
<div
class="expanded-tree-node__node-checkbox-presentation"
[ngClass]="{
'expanded-tree-node__node-checkbox-presentation--focus': node.hasFocus,
'expanded-tree-node__node-checkbox-presentation--checked': node.isSelected
}"
(click)="nodePresentationItemClicked(node)"
>
<span class="expanded-tree-node__node-checkbox-presentation__box"></span>
<span class="expanded-tree-node__node-checkbox-presentation__text">{{node.label}}</span>
</div>
<ng-template #treeNodeChildrenTemplate>
<ng-container *ngIf="treeNodeModel">
<div *ngIf="treeNodeModel.children" class="expanded-tree-node__filter">
<digi-form-input-search
[attr.af-variation]="FormInputSearchVariation.S"
[attr.af-button-text]="getFilterButtonAriaLabelText(treeNodeModel)"
[attr.af-button-type]="ButtonType.BUTTON"
[attr.af-label]="' '"
[attr.af-aria-labelledby]="'filter-description-'+treeNodeModel.uuid"
[attr.af-value]="treeNodeModel.filterText"
(afOnFocusOutside)="onFocusOutsideFilter($event)"
(afOnChange)="onFilterTextChanged($event, treeNodeModel)"
(afOnKeyup)="onFilterTextChanged($event, treeNodeModel)"
></digi-form-input-search>
<div class="msfa__a11y-sr-only" [attr.id]="'filter-description-'+treeNodeModel.uuid">
Filtrera valbara alternativ där deras namn måste innehålla den angivna texten.
</div>
</div>
<digi-form-checkbox
*ngIf="hasChildLeafNodes(treeNodeModel)"
class="expanded-tree-node__child-node-checkbox expanded-tree-node__child-node-checkbox--toggle-all"
[attr.af-label]="treeNodeModel.toggleAllChildrenLabel"
[attr.af-aria-labelledby]="'toggle-all-description-'+treeNodeModel.uuid"
[attr.af-checked]="allChildLeafNodesAreSelected(treeNodeModel)"
(afOnChange)="onToggleAllChildLeafNodes(treeNodeModel)"
></digi-form-checkbox>
<div class="msfa__a11y-sr-only" [attr.af-id]="'toggle-all-description-'+treeNodeModel.uuid">
{{getAriaLabelForToggleAllButton(treeNodeModel)}}
</div>
<ul *ngIf="visibleChildren" class="expanded-tree-node__nodes">
<li *ngFor="let childNode of visibleChildren" class="expanded-tree-node__node">
<ng-container
[ngTemplateOutlet]="childNode.children ? nodeExpansionTemplate : nodeCheckboxTemplate"
[ngTemplateOutletContext]="{node: childNode, parentNode: treeNodeModel, isExpanded: isExpandedNode(treeNodeModel, childNode)}"
>
</ng-container>
</li>
</ul>
<ng-container
*ngIf="showGeneralInfoTemplate(treeNodeModel)"
[ngTemplateOutlet]="generalInfoPanelTemplate"
></ng-container>
</ng-container>
</ng-template>
<ng-template #nodeExpansionPresentationTemplate let-node="node" let-parentNode="parentNode">
<div
class="expanded-tree-node__node-expansion-presentation"
<ng-template #nodeCheckboxTemplate let-node="node">
<digi-form-checkbox
class="expanded-tree-node__child-node-checkbox"
[attr.af-id]="'item-'+node.uuid"
[attr.af-label]="node.label"
[attr.af-checked]="node.isSelected"
[attr.af-value]="node.value"
(afOnChange)="onToggleSelected(node, $event)"
></digi-form-checkbox>
</ng-template>
<ng-template #nodeExpansionTemplate let-node="node" let-parentNode="parentNode" let-isExpanded="isExpanded">
<button
type="button"
class="expanded-tree-node__child-node-expansion-btn"
[ngClass]="{
'expanded-tree-node__node-expansion-presentation--focus': node.hasFocus,
'expanded-tree-node__node-expansion-presentation--active' : isExpandedNode(parentNode, node)
}"
(click)="nodePresentationItemClicked(node)"
'expanded-tree-node__child-node-expansion-btn--active' : isExpanded
}"
[attr.aria-expanded]="isExpanded"
[attr.aria-label]="getAriaLabelTextForExpansionButton(parentNode, node)"
[attr.aria-controls]="'expansion-panel-'+node.uuid"
(click)="onSetExpandedChild(parentNode, node)"
>
<span class="expanded-tree-node__node-expansion-presentation__text">{{node.label}}</span>
<span class="expanded-tree-node__child-node-expansion-btn__text">{{node.label}}</span>
<span
class="expanded-tree-node__node-expansion-presentation__has-selection-dot"
class="expanded-tree-node__child-node-expansion-btn__has-selection-dot"
*ngIf="hasSelectedDescendant(node)"
aria-hidden="true"
></span>
<digi-icon-arrow-right class="expanded-tree-node__node-expansion-presentation__icon"></digi-icon-arrow-right>
<digi-icon-arrow-right class="expanded-tree-node__child-node-expansion-btn__icon"></digi-icon-arrow-right>
</button>
<div
[attr.id]="'expansion-panel-'+node.uuid"
class="expanded-tree-node__child-node-expansion-panel"
[ngClass]="{'expanded-tree-node__child-node-expansion-panel--active': isExpanded}"
>
<msfa-expanded-tree-node
*ngIf="isExpanded"
[treeNodeModel]="node"
[headingText]="node.label"
[visibleChildren]="childNodeVisibleChildren"
(filterVisibleChildrenRequested)="updateChildNodeVisibleChildren($event)"
(selectedNodesChanged)="updateExpandedChildPanel($event)"
></msfa-expanded-tree-node>
</div>
</ng-template>
<ng-template #generalInfoPanelTemplate>
<div class="expanded-tree-node__child-node-expansion-panel expanded-tree-node__child-node-expansion-panel--active">
<msfa-expanded-tree-node
[headingText]="treeNodeModel.grandChildrenItemType"
[showGeneralInfo]="true"
[generalInfo]="treeNodeModel.grandChildrenInfo"
></msfa-expanded-tree-node>
</div>
</ng-template>
</div>

View File

@@ -4,11 +4,24 @@
@import 'variables/gutters';
.expanded-tree-node {
display: flex;
flex-direction: column;
min-height: 100%;
max-height: 300px;
overflow: auto;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
padding-top: $digi--layout--gutter--m;
&--is-root-node {
position: static;
}
&__content {
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
}
&__filter {
padding: 0 0.9375rem;
@@ -60,53 +73,7 @@
}
}
&__node-checkbox-presentation {
font-size: var(--digi--typography--font-size--s);
font-weight: 400;
display: block;
position: relative;
padding-left: 1.875rem;
cursor: pointer;
line-height: normal;
&__box {
position: absolute;
top: 0;
left: 0;
height: 1.25rem;
width: 1.25rem;
border-radius: 0.1875rem;
background-color: var(--digi--ui--color--background);
border: 1px solid var(--digi--ui--color--background--dark);
}
&--toggle-all {
margin: 1.25rem $digi--layout--gutter;
}
&--checked &__box {
background-color: var(--digi--ui--color--success);
border-color: var(--digi--ui--color--success);
&::after {
position: absolute;
left: 0.375rem;
top: 0.0625rem;
width: 0.375rem;
height: 0.6875rem;
border: solid #fff;
border-width: 0 0.125rem 0.125rem 0;
transform: rotate(35deg);
content: '';
}
}
&--focus &__box {
box-shadow: var(--digi--ui--outline--focus--m);
}
}
&__node-expansion-presentation {
&__child-node-expansion-btn {
display: inline-flex;
padding: 0.3125rem 0.9375rem;
justify-content: space-between;
@@ -122,7 +89,7 @@
position: relative;
&:hover,
&--focus {
&:focus {
background-color: var(--digi--ui--color--background--secondary);
}
@@ -131,7 +98,7 @@
color: var(--digi--ui--color--background);
&:hover,
&--focus {
&:focus {
background-color: lighten($digi--ui--color--primary, 10%);
}
}
@@ -139,7 +106,6 @@
&__text {
text-align: left;
flex-grow: 1;
max-width: rem(450);
margin-right: $digi--layout--gutter--s;
display: block;
overflow: hidden;
@@ -163,4 +129,33 @@
max-height: 1em;
}
}
&__child-node-checkbox {
display: block;
margin: 0.3125rem $digi--layout--gutter;
&--toggle-all {
margin: 1.25rem $digi--layout--gutter;
}
::ng-deep {
.digi-form-checkbox__label {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
&__child-node-expansion-panel {
&--active {
position: absolute;
top: 0;
left: 100%;
border-left: 1px solid var(--digi--typography--color--text--disabled);
height: 100%;
width: 100%;
}
}
}

View File

@@ -1,13 +1,12 @@
import { TypographyDynamicHeadingLevel } from '@af/digi-ng/_typography/typography-dynamic-heading';
import {
Component,
OnInit,
ChangeDetectionStrategy,
Input,
OnChanges,
SimpleChanges,
Output,
EventEmitter,
OnChanges,
SimpleChanges,
} from '@angular/core';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { ButtonType } from '../../../../../pages/avrop/enums/button-type.enum';
@@ -26,42 +25,44 @@ import {
styleUrls: ['./expanded-tree-node.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements OnInit, OnChanges {
@Input() treeNodeModel: TreeNodeModel | null = null;
export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements OnChanges {
@Input() treeNodeModel: TreeNodeModel;
@Input() headingLevel: TypographyDynamicHeadingLevel = TypographyDynamicHeadingLevel.H3;
@Input() latestParentActionKey: string;
@Output() filterTreeNodeRequested = new EventEmitter<FilterTreeNodeData>();
@Output() clickAndFocusElementRequested = new EventEmitter<string>();
@Input() headingText: string;
@Input() showGeneralInfo: boolean;
@Input() generalInfo: string;
@Input() visibleChildren: Array<TreeNodeModel>;
@Output() filterVisibleChildrenRequested = new EventEmitter<FilterTreeNodeData>();
@Output() selectedNodesChanged = new EventEmitter<TreeNodeModel>();
readonly ButtonType = ButtonType;
readonly FormInputSearchVariation = FormInputSearchVariation;
visibleChildren: Array<TreeNodeModel> | null = null;
childNodeVisibleChildren: Array<TreeNodeModel>;
private readonly filterTreeNodeDebouncer: Subject<FilterTreeNodeData> = new Subject<FilterTreeNodeData>();
private readonly filterTreeNodeDebouncer: Subject<string> = new Subject<string>();
constructor(private treeNodesSelectorService: TreeNodesSelectorService) {
super();
super.unsubscribeOnDestroy(
this.filterTreeNodeDebouncer.pipe(debounceTime(200)).subscribe(filterTreeNodeData => {
this.filterTreeNodeRequested.emit(filterTreeNodeData);
this.filterTreeNodeDebouncer.pipe(debounceTime(200)).subscribe(text => {
this.filterVisibleChildrenRequested.emit({ text, treeNode: this.treeNodeModel });
})
);
}
ngOnInit(): void {
this.refreshComponentData();
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.latestParentActionKey || changes.treeNodeModel) {
this.refreshComponentData();
}
}
let expandedChildNode: TreeNodeModel | null = null;
private refreshComponentData(): void {
this.visibleChildren = this.treeNodesSelectorService.getVisibleChildren(this.treeNodeModel);
if (changes.visibleChildren) {
expandedChildNode = this.visibleChildren?.find(childNode => this.isExpandedNode(this.treeNodeModel, childNode));
this.updateChildNodeVisibleChildren({
text: expandedChildNode?.filterText,
treeNode: expandedChildNode,
});
}
}
onFilterTextChanged(event: CustomEvent<{ target: { value: string } }>, treeNode: TreeNodeModel): void {
@@ -69,10 +70,7 @@ export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements O
return;
}
this.filterTreeNodeDebouncer.next({
text: event.detail.target.value,
treeNode,
});
this.filterTreeNodeDebouncer.next(event.detail.target.value);
}
onFocusOutsideFilter(event: Event): void {
@@ -80,42 +78,68 @@ export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements O
event.stopPropagation();
}
nodePresentationToggleAllClicked(treeNode: TreeNodeModel): void {
if (!treeNode) {
onToggleSelected(treeNode: TreeNodeModel, event: CustomEvent<{ target: { checked: boolean } }>): void {
if (!treeNode || !event?.detail?.target) {
return;
}
this.clickAndFocusElementRequested.emit(`#${this.treeNodesSelectorService.getToggleAllButtonId(treeNode)}`);
treeNode.isSelected = event?.detail?.target?.checked;
this.selectedNodesChanged.emit(this.treeNodeModel);
}
nodePresentationItemClicked(treeNode: TreeNodeModel): void {
if (!treeNode) {
onToggleAllChildLeafNodes(treeNode: TreeNodeModel): void {
let allChildLeafNodesAreSelected = false;
if (!treeNode || !treeNode.children) {
return;
}
this.clickAndFocusElementRequested.emit(`#${this.treeNodesSelectorService.getItemButtonId(treeNode)}`);
allChildLeafNodesAreSelected = this.allChildLeafNodesAreSelected(treeNode);
treeNode.children = treeNode.children.map(child => {
return { ...child, isSelected: !allChildLeafNodesAreSelected };
});
this.selectedNodesChanged.emit(this.treeNodeModel);
}
getPresentationItemId(treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getPresentationItemId(treeNode);
onSetExpandedChild(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): void {
if (!parentTreeNode || !treeNode) {
return;
}
parentTreeNode.expandedChildUuid = parentTreeNode.expandedChildUuid === treeNode.uuid ? null : treeNode.uuid;
this.updateChildNodeVisibleChildren({ text: treeNode.filterText, treeNode });
}
getPresentationToggleAllId(treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getPresentationToggleAllId(treeNode);
updateExpandedChildPanel(treeNode: TreeNodeModel): void {
if (!this.childNodeVisibleChildren) {
return;
}
this.childNodeVisibleChildren = this.treeNodesSelectorService.getVisibleChildren(treeNode);
}
getFilterDescriptionId(treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getFilterDescriptionId(treeNode);
updateChildNodeVisibleChildren(filterTreeNodeData: FilterTreeNodeData): void {
if (!filterTreeNodeData || !filterTreeNodeData.treeNode) {
return;
}
filterTreeNodeData.treeNode.children = this.treeNodesSelectorService.getFilteredTreeNodeChildren(
filterTreeNodeData
);
filterTreeNodeData.treeNode.filterText = filterTreeNodeData.text;
this.childNodeVisibleChildren = this.treeNodesSelectorService.getVisibleChildren(filterTreeNodeData.treeNode);
}
getFilterButtonAriaLabelText(treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getFilterButtonAriaLabelText(treeNode);
}
getTreeNodeHeadingText(treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getTreeNodeHeadingText(treeNode);
}
hasChildLeafNodes(node: TreeNodeModel): boolean {
return this.treeNodesSelectorService.hasChildLeafNodes(node);
}
@@ -135,4 +159,16 @@ export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements O
hasSelectedDescendant(treeNode: TreeNodeModel): boolean {
return this.treeNodesSelectorService.hasSelectedLeafNodeDescendant(treeNode);
}
getAriaLabelTextForExpansionButton(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getAriaLabelTextForExpansionButton(parentTreeNode, treeNode);
}
getAriaLabelForToggleAllButton(treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getAriaLabelForToggleAllButton(treeNode);
}
showGeneralInfoTemplate(treeNode: TreeNodeModel): boolean {
return this.treeNodesSelectorService.showGeneralInfoTemplate(treeNode);
}
}

View File

@@ -1,6 +1,6 @@
<digi-util-detect-focus-outside (afOnFocusOutside)="closePanel()">
<digi-util-keydown-handler (afOnEsc)="closePanel()">
<section class="tree-nodes-selector-panel" #treeNodesSelectorPanel>
<section class="tree-nodes-selector-panel">
<header>
<h2 class="tree-nodes-selector-panel__heading">{{headingText}}</h2>
<p class="msfa__a11y-sr-only">
@@ -18,21 +18,13 @@
</button>
</header>
<div class="tree-nodes-selector-panel__content" *ngIf="pendingRootNode">
<ul class="tree-nodes-selector-panel__expanded-nodes" aria-hidden="true" *ngIf="expandedTreeNodes">
<li class="tree-nodes-selector-panel__expanded-node" *ngFor="let node of expandedTreeNodes">
<msfa-expanded-tree-node
[treeNodeModel]="node"
[latestParentActionKey]="latestParentActionKey"
(filterTreeNodeRequested)="filterTreeNode($event)"
(clickAndFocusElementRequested)="clickAndFocusElementByQuerySelector($event)"
></msfa-expanded-tree-node>
</li>
</ul>
<div class="msfa__a11y-sr-only">
<ng-container
[ngTemplateOutlet]="nodeTemplate"
[ngTemplateOutletContext]="{node: pendingRootNode}"
></ng-container>
<div class="tree-nodes-selector-panel__tree">
<msfa-expanded-tree-node
[treeNodeModel]="pendingRootNode"
[visibleChildren]="visibleChildren"
(selectedNodesChanged)="updateExpandedChildPanel($event)"
(filterVisibleChildrenRequested)="updateVisibleChildren($event)"
></msfa-expanded-tree-node>
</div>
</div>
<footer>
@@ -45,64 +37,6 @@
{{confirmationButtonText}}
</digi-button>
</footer>
<ng-template #nodeTemplate let-node="node">
<div class="tree-nodes-selector-panel__level">
<h3 class="tree-nodes-selector-panel__heading">{{node.label}}</h3>
<digi-form-checkbox
*ngIf="hasChildLeafNodes(node)"
[attr.af-id]="getToggleAllButtonId(node)"
[attr.af-label]="node.selectAllChildrenLabel"
[attr.af-aria-labelledby]="'toggle-all-description-'+node.uuid"
[attr.af-checked]="allChildLeafNodesAreSelected(node)"
(afOnChange)="toggleAllChildLeafNodes(node)"
(afOnFocus)="setFocusOnToggleAll(node, true)"
(afOnBlur)="setFocusOnToggleAll(node, false)"
></digi-form-checkbox>
<div [attr.af-id]="'toggle-all-description-'+node.uuid">{{getAriaLabelForToggleAllButton(node)}}</div>
<ul *ngIf="getVisibleChildren(node) as visibleChildren" class="tree-nodes-selector-panel__nodes">
<li
*ngFor="let childNode of visibleChildren"
class="tree-nodes-selector-panel__node"
[ngClass]="{'tree-nodes-selector-panel__node--expanded': isExpandedNode(node, childNode)}"
>
<ng-container
[ngTemplateOutlet]="childNode.children ? nodeExpansionTemplate : nodeCheckboxTemplate"
[ngTemplateOutletContext]="{node: childNode, parentNode: node}"
>
</ng-container>
</li>
</ul>
</div>
</ng-template>
<ng-template #nodeCheckboxTemplate let-node="node">
<digi-form-checkbox
[attr.af-id]="'item-'+node.uuid"
[attr.af-label]="node.label"
[attr.af-checked]="node.isSelected"
[attr.af-value]="node.value"
(afOnChange)="onToggleSelected(node, $event)"
(afOnFocus)="setFocusOnNodeItem(node, true)"
(afOnBlur)="setFocusOnNodeItem(node, false)"
></digi-form-checkbox>
</ng-template>
<ng-template #nodeExpansionTemplate let-node="node" let-parentNode="parentNode">
<button
type="button"
[attr.id]="getItemButtonId(node)"
[attr.aria-label]="getAriaLabelTextForExpansionButton(parentNode, node)"
(click)="setExpandedChild(parentNode, node)"
(focus)="setFocusOnNodeItem(node, true)"
(blur)="setFocusOnNodeItem(node, false)"
>
{{node.label}}
</button>
<ng-container
*ngIf="isExpandedNode(parentNode, node)"
[ngTemplateOutlet]="nodeTemplate"
[ngTemplateOutletContext]="{node: node}"
>
</ng-container>
</ng-template>
</section>
</digi-util-keydown-handler>
</digi-util-detect-focus-outside>

View File

@@ -41,28 +41,15 @@
font-size: var(--digi--typography--font-size--xs);
}
&__expanded-nodes {
@include msfa__reset-list;
display: flex;
flex-direction: row;
&__content {
overflow: auto;
max-width: 100%;
}
&__expanded-node {
position: relative;
display: flex;
flex-direction: column;
flex: 0 0 50%;
&__tree {
max-width: 50%;
overflow: auto;
padding-top: $digi--layout--gutter--m;
padding-bottom: $digi--layout--gutter--l;
border-left: 1px solid var(--digi--typography--color--text--disabled);
&:first-child {
border-left-width: 0;
}
height: 300px;
position: relative;
}
footer {

View File

@@ -1,14 +1,5 @@
import { ButtonSize } from '@af/digi-ng/_button/button';
import {
ChangeDetectionStrategy,
Component,
ElementRef,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import {
FilterTreeNodeData,
TreeNodeModel,
@@ -28,13 +19,10 @@ export class TreeNodesSelectorPanelComponent implements OnInit {
@Output() selectedChangesConfirmed = new EventEmitter<TreeNodeModel>();
@Output() closePanelRequested = new EventEmitter<void>();
@ViewChild('treeNodesSelectorPanel') treeNodesSelector: ElementRef;
readonly ButtonSize = ButtonSize;
pendingRootNode: TreeNodeModel | null = null;
expandedTreeNodes: Array<TreeNodeModel> | null = null;
latestParentActionKey: string = null;
visibleChildren: Array<TreeNodeModel> | null = null;
constructor(private treeNodesSelectorService: TreeNodesSelectorService) {}
@@ -44,123 +32,18 @@ export class TreeNodesSelectorPanelComponent implements OnInit {
private setupPendingRootNode(): void {
this.pendingRootNode = this.treeNodesSelectorService.getClonedTreeNode(this.rootNode);
this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode);
}
onToggleSelected(treeNode: TreeNodeModel, event: CustomEvent<{ target: { checked: boolean } }>): void {
if (!treeNode || !event?.detail?.target) {
if (!this.pendingRootNode) {
return;
}
treeNode.isSelected = event?.detail?.target?.checked;
// eslint-disable-next-line
this.latestParentActionKey = `${treeNode.uuid}-selected-${treeNode.isSelected}`;
}
setExpandedChild(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): void {
if (!parentTreeNode || !treeNode) {
return;
}
parentTreeNode.expandedChildUuid = parentTreeNode.expandedChildUuid === treeNode.uuid ? null : treeNode.uuid;
this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode);
this.latestParentActionKey = `${parentTreeNode.uuid}-expanded-child-${parentTreeNode.expandedChildUuid}`;
}
setFocusOnToggleAll(treeNode: TreeNodeModel, hasFocus: boolean): void {
let presentationToggleAllElement: HTMLElement = null;
let expansionColumnElement: HTMLElement = null;
if (!treeNode) {
return;
}
treeNode.toggleAllHasFocus = hasFocus;
if (!hasFocus) {
return;
}
presentationToggleAllElement = this.treeNodesSelectorService.getPresentationToggleAllElement(
this.treeNodesSelector,
treeNode
);
expansionColumnElement = this.treeNodesSelectorService.getExpansionColumnElement(presentationToggleAllElement);
this.scrollElementInContainer(expansionColumnElement);
}
private scrollElementInContainer(element: HTMLElement): void {
let elementRect: DOMRect = null;
let parentElementRect: DOMRect = null;
if (!element || !element.parentElement) {
return;
}
if (this.treeNodesSelectorService.isFullyVisibleElement(element, element.parentElement)) {
return;
}
elementRect = element.getBoundingClientRect();
parentElementRect = element.parentElement.getBoundingClientRect();
element.parentElement.scrollTop = elementRect.top + element.parentElement.scrollTop - parentElementRect.top;
element.parentElement.scrollLeft = elementRect.left + element.parentElement.scrollLeft - parentElementRect.left;
}
setFocusOnNodeItem(treeNode: TreeNodeModel, hasFocus: boolean): void {
let presentationItemElement: HTMLElement = null;
let expansionColumnElement: HTMLElement = null;
if (!treeNode) {
return;
}
treeNode.hasFocus = hasFocus;
if (!hasFocus) {
return;
}
this.latestParentActionKey = `${treeNode.uuid}-focused-item`;
presentationItemElement = this.treeNodesSelectorService.getPresentationItemElement(
this.treeNodesSelector,
treeNode
);
expansionColumnElement = this.treeNodesSelectorService.getExpansionColumnElement(presentationItemElement);
this.scrollElementInContainer(presentationItemElement);
this.scrollElementInContainer(expansionColumnElement);
}
toggleAllChildLeafNodes(treeNode: TreeNodeModel): void {
let allChildLeafNodesAreSelected = false;
if (!treeNode || !treeNode.children) {
return;
}
allChildLeafNodesAreSelected = this.allChildLeafNodesAreSelected(treeNode);
treeNode.children = treeNode.children.map(child => {
return { ...child, isSelected: !allChildLeafNodesAreSelected };
});
this.latestParentActionKey = `${treeNode.uuid}-selected-${allChildLeafNodesAreSelected.toString()}`;
this.pendingRootNode.isRoot = true;
this.updateExpandedChildPanel(this.pendingRootNode);
}
clearSelections(): void {
this.pendingRootNode = this.treeNodesSelectorService.getTreeNodeWithNoNodesSelected(this.pendingRootNode);
this.latestParentActionKey = `cleared-selections`;
this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode);
this.updateExpandedChildPanel(this.pendingRootNode);
}
closePanel(): void {
@@ -168,65 +51,21 @@ export class TreeNodesSelectorPanelComponent implements OnInit {
this.closePanelRequested.emit();
}
filterTreeNode(filterTreeNodeData: FilterTreeNodeData): void {
if (!filterTreeNodeData || !filterTreeNodeData.treeNode) {
return;
}
filterTreeNodeData.treeNode.children = this.treeNodesSelectorService.getFilteredTreeNodeChildren(
filterTreeNodeData
);
this.latestParentActionKey = `${filterTreeNodeData.treeNode.uuid}-filtered-${filterTreeNodeData.text}`;
this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode);
}
clickAndFocusElementByQuerySelector(selector: string): void {
const selectedItem = this.treeNodesSelector
? this.treeNodesSelectorService.getElementByQuerySelector(this.treeNodesSelector, selector)
: null;
if (!selectedItem) {
return;
}
selectedItem.focus();
selectedItem.click();
}
getVisibleChildren(treeNode: TreeNodeModel): Array<TreeNodeModel> | null {
return this.treeNodesSelectorService.getVisibleChildren(treeNode);
}
hasChildLeafNodes(node: TreeNodeModel): boolean {
return this.treeNodesSelectorService.hasChildLeafNodes(node);
}
allChildLeafNodesAreSelected(node: TreeNodeModel): boolean {
return this.treeNodesSelectorService.allChildLeafNodesAreSelected(node);
}
hasSelectedDescendant(treeNode: TreeNodeModel): boolean {
return this.treeNodesSelectorService.hasSelectedLeafNodeDescendant(treeNode);
}
getAriaLabelTextForExpansionButton(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getAriaLabelTextForExpansionButton(parentTreeNode, treeNode);
updateExpandedChildPanel(treeNode: TreeNodeModel): void {
this.visibleChildren = this.treeNodesSelectorService.getVisibleChildren(treeNode);
}
getAriaLabelForToggleAllButton(treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getAriaLabelForToggleAllButton(treeNode);
}
updateVisibleChildren(filterTreeNodeData: FilterTreeNodeData): void {
filterTreeNodeData.treeNode.children = this.treeNodesSelectorService.getFilteredTreeNodeChildren(
filterTreeNodeData
);
isExpandedNode(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): boolean {
return this.treeNodesSelectorService.isExpandedNode(parentTreeNode, treeNode);
}
filterTreeNodeData.treeNode.filterText = filterTreeNodeData.text;
getToggleAllButtonId(treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getToggleAllButtonId(treeNode);
}
getItemButtonId(treeNode: TreeNodeModel): string {
return this.treeNodesSelectorService.getItemButtonId(treeNode);
this.visibleChildren = this.treeNodesSelectorService.getVisibleChildren(filterTreeNodeData.treeNode);
}
}

View File

@@ -3,6 +3,7 @@
<button
#togglePanelBtn
(click)="togglePanel()"
[attr.id]="buttonElementId"
[attr.aria-controls]="panelId"
[ngClass]="{'tree-nodes-selector__toggle-panel-btn--invalid': isInvalid && showValidation}"
type="button"

View File

@@ -36,6 +36,7 @@ interface PropagateTouchedFn {
],
})
export class TreeNodesSelectorComponent implements ControlValueAccessor {
@Input() buttonElementId: string = UUID.UUID();
@Input() headingText: string;
@Input() confirmationButtonText = 'Spara';
@Input() isInvalid = false;

View File

@@ -59,71 +59,6 @@ describe('TreeNodesSelectorService', () => {
});
});
describe('getExpandedTreeNodes', () => {
it('should return a list of all tree nodes that are in an expanded chain starting with the provided tree node.', () => {
const treeNode: TreeNodeModel = {
label: 'root',
isSelected: false,
value: null,
uuid: '1',
expandedChildUuid: '1_1',
children: [
{
label: 'level 1',
isSelected: false,
value: null,
uuid: '1_1',
expandedChildUuid: '1_1_2',
children: [
{
label: 'level 2',
isSelected: false,
value: null,
uuid: '1_1_1',
},
{
label: 'level 2',
isSelected: true,
value: null,
uuid: '1_1_2',
},
],
},
{
label: 'level 1',
isSelected: true,
value: null,
uuid: '1_2',
expandedChildUuid: '1_2_2',
children: [
{
label: 'level 2',
isSelected: false,
value: null,
uuid: '1_2_1',
},
{
label: 'level 2',
isSelected: true,
value: null,
uuid: '1_2_2',
},
],
},
],
};
const result = service.getExpandedTreeNodes(treeNode);
expect(result).not.toBeUndefined();
expect(result).not.toBeNull();
expect(result.length).toEqual(3);
expect(result[0].uuid).toEqual('1');
expect(result[1].uuid).toEqual('1_1');
expect(result[2].uuid).toEqual('1_1_2');
});
});
describe('getFilteredTreeNodeChildren', () => {
it('should set the propert hideTreeNode to true for all children with a label that contains the provided text when compared case insensitive, trimming white space and return them.', () => {
const treeNode: TreeNodeModel = {

View File

@@ -1,4 +1,4 @@
import { ElementRef, Injectable } from '@angular/core';
import { Injectable } from '@angular/core';
import { UUID } from 'angular2-uuid';
export interface TreeNode {
@@ -18,8 +18,9 @@ export interface TreeNodeModel extends TreeNode {
children?: Array<TreeNodeModel>;
hasFocus?: boolean;
toggleAllHasFocus?: boolean;
showGeneralInfoAboutGrandChildren?: boolean;
hideTreeNode?: boolean;
isRoot?: boolean;
filterText?: string;
}
export interface FilterTreeNodeData {
@@ -82,30 +83,7 @@ export class TreeNodesSelectorService {
return `${isActiveNode ? 'Döljer' : 'Visar'} ${treeNode.grandChildrenItemType} för ${treeNode.label}`;
}
isFullyVisibleElement(element: HTMLElement, container: HTMLElement): boolean {
const { bottom, top, left, right } = element.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
if (top <= containerRect.top && containerRect.top - top > 0) {
return false;
}
if (bottom - containerRect.bottom > 0) {
return false;
}
if (left <= containerRect.left && containerRect.left - left > 0) {
return false;
}
if (right - containerRect.right > 0) {
return false;
}
return true;
}
hasChildLeafNodes(node: TreeNodeModel): boolean {
hasChildLeafNodes(node: TreeNode): boolean {
if (!node || !node.children || node.children.length === 0) {
return false;
}
@@ -125,42 +103,6 @@ export class TreeNodesSelectorService {
return !node.children.some(child => this.isLeafNode(child) && !child.isSelected);
}
getElementByQuerySelector(elementRef: ElementRef, selector: string): HTMLElement {
const element = elementRef?.nativeElement as HTMLElement;
return element?.querySelector(selector);
}
getPresentationItemElement(elementRef: ElementRef, treeNode: TreeNodeModel): HTMLElement {
return this.getElementByQuerySelector(elementRef, `#${this.getPresentationItemId(treeNode)}`);
}
getPresentationToggleAllElement(elementRef: ElementRef, treeNode: TreeNodeModel): HTMLElement {
return this.getElementByQuerySelector(elementRef, `#${this.getPresentationToggleAllId(treeNode)}`);
}
getExpansionColumnElement(element: HTMLElement): HTMLElement {
return element?.closest('.tree-nodes-selector-panel__expanded-node');
}
getExpandedTreeNodes(treeNode: TreeNodeModel): Array<TreeNodeModel> {
let expandedChildNode: TreeNodeModel | null = null;
if (!treeNode) {
return [];
}
expandedChildNode = treeNode.children?.find(childNode => this.isExpandedNode(treeNode, childNode));
if (expandedChildNode) {
return [treeNode].concat(this.getExpandedTreeNodes(expandedChildNode));
}
return treeNode.children && treeNode.children.some(child => child.children && child.children.length > 0)
? [treeNode, { ...treeNode, children: undefined, showGeneralInfoAboutGrandChildren: true }]
: [treeNode];
}
isExpandedNode(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): boolean {
if (!parentTreeNode || !treeNode) {
return false;
@@ -210,35 +152,7 @@ export class TreeNodesSelectorService {
return '';
}
return `Filtrera ${treeNode.showGeneralInfoAboutGrandChildren ? treeNode.grandChildrenItemType : treeNode.label}`;
}
getTreeNodeHeadingText(treeNode: TreeNodeModel): string {
if (!treeNode) {
return '';
}
return treeNode.showGeneralInfoAboutGrandChildren ? treeNode.grandChildrenItemType : treeNode.label;
}
getToggleAllButtonId(treeNode: TreeNodeModel): string {
return `toggle-all-${treeNode.uuid}`;
}
getItemButtonId(treeNode: TreeNodeModel): string {
return `item-${treeNode.uuid}`;
}
getPresentationItemId(treeNode: TreeNodeModel): string {
return `presentation-item-${treeNode?.uuid}`;
}
getPresentationToggleAllId(treeNode: TreeNodeModel): string {
return `presentation-toggle-all-${treeNode?.uuid}`;
}
getFilterDescriptionId(treeNode: TreeNodeModel): string {
return `filter-description-${treeNode?.uuid}`;
return `Filtrera ${treeNode.label}`;
}
getCleanedText(text: string): string {
@@ -360,4 +274,20 @@ export class TreeNodesSelectorService {
? treeNode.isSelected
: !treeNode.children.some(childNode => !this.hasSelectedAllLeafNodes(childNode));
}
hasExpandedChild(treeNode: TreeNode): boolean {
if (!treeNode || !treeNode.children) {
return false;
}
return treeNode.children.some(childNode => this.isExpandedNode(treeNode, childNode));
}
showGeneralInfoTemplate(treeNode: TreeNode): boolean {
if (!treeNode || !treeNode.grandChildrenInfo || this.hasExpandedChild(treeNode)) {
return false;
}
return this.getCleanedText(treeNode.grandChildrenInfo).length > 0;
}
}