Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Material Flat Tree Parent Children Graphical Representaion

I want to show Parent Children graphical representation on Angular Material Flat Tree.

This is the design :

enter image description here

Here is the DEMO what i done so far.

like image 236
Anzil khaN Avatar asked Oct 01 '19 12:10

Anzil khaN


1 Answers

FIRST SOLUTION

here is the stackBlitz Demo

here is the stackBlitz editable link

I've made a recursive call on every visible node reffering to the node index using treeControl.dataNodes.indexOf(node)

here is the recursive function:

  recNode( arr:any[], data:any[], index:number, maxIndex:number ):any[] {

    if ( arr === undefined )
      arr = [];

    for ( let i = 0; i < data.length; i++ ) {

          index++

          if ( index === maxIndex ) return ( [ true, index, arr ] );
          if ( data[i].children.length ) {

            let res = this.recNode( arr, data[i].children, index, maxIndex )
            index = res[1];

            if ( res[0] === true ) {

              arr.splice( 0, 0, ( i !== ( data.length - 1 ) ) )         
              return ( [ true, index, arr ] );

            }

        }

    }

    return ( [ false, index, arr ] );

  }

this returns an array of true false values for your [ngClass]

then in html I call that function as so

<li *ngFor="let r of recNode( undefined, dataSource.data, -1, treeControl.dataNodes.indexOf( node ) )[2]; [ngClass]="{'node-arrow': r, 'node-arrow-empty': !r}" ></li>

and finally I've created another class in the css called node-arrow-empty without borders

   .node-arrow-nox {
      position: relative;
      // min-width: 48px;
      // min-height: 48px;
      .node-arrow {
        position: relative;
        min-width: 48px;
        min-height: 48px;
        &:before {
          content: "";
          position: absolute;
          top: -20px;
          left: 20px;
          border-left: 1px solid $copounsCount;
          bottom: 0;
        }
      }
      .node-arrow-empty {
        position: relative;
        min-width: 48px;
        min-height: 48px;
        &:before {
          content: "";
          position: absolute;
          top: -20px;
          left: 20px;
          bottom: 0;
        }
      }
    }

depending on weither the value in the array is true or false it switches in the html between the two classes.

SECOND SOLUTION

stackBlitz DEMO

stackBlitz editable link

the other solution would be to keep track of last element in array by adding for example a isLast:boolean property. Of course if the datas you are dealing with are dynamic you need to find a way to dinamically change that value.

Once the isLast is filled you make a recursive call which will find the previous sibling with current node level - 1. That being said you will check 2 things. First thing is, is the node classes contains n-last (which is the class name I gave to Html representing last element of parent array) and second thing retrieve the class n-{{node-level}}.

By recursively (you can make a non recursive method if you wan't to) retrieving the previousElementSibling which has class n-3 or n-2 ... to n-0 you can check if each of this element contains a n-last class. You push the true or false into an array and just as the first solution switch between class node-arrow and node-arrow-empty.

here is the recursive function

  dummy( el:any, arr:any[], level:number ) {

    let classes = el.className;

    if ( arr === undefined )
     arr = [];
    else {

     arr.splice( 0, 0, classes.includes( 'n-last' ) );

    }

    let np = /(?!n-)\d+/g;

    let m = classes.match( np );
    let nLevel = parseInt( m[0] );
    nLevel--;

    if ( nLevel < 0 ) return ( arr );

    while ( parseInt( el.className.match( np )[0] ) !== nLevel ) {

      el = el.previousElementSibling;

    }

    arr = this.dummy(el, arr, nLevel);

    return (arr);

  }

the HTML (here is only parent node but same thing for child)

<mat-tree-node #refP   class="parent-node {{'n-' + node.level}} " *matTreeNodeDef="let node; when: grpNode;" matTreeNodePadding matTreeNodePaddingIndent="0" cdkDrag [cdkDragData]="node" [ngClass]="{'no-child': !node.children.length, 'n-last': node.isLast}">
  <span class="node-arrow-nox d-flex">
    <li [ngClass]="{'node-arrow': !r , 'node-arrow-empty': r}" *ngFor="let r of dummy(refP, undefined, node.level)"></li>
  </span>
  <button class="node-toggle" mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.filename" [disabled]="!node.children.length">
    <mat-icon class="mat-icon-rtl-mirror toggle-arrow">
      {{treeControl.isExpanded(node) ? 'play_arrow' : 'play_arrow'}}
    </mat-icon>
  </button>
  <div class="node-icon icon-h-arw"></div>
  <div class="node-name">{{node.isLast}}-{{node.accntCode}} <span>: :</span> {{node.name}} </div>
</mat-tree-node>

as you can see I'm passing element to the dummy fucntion with a template variable called #refP

and here are the PARENTNODE and CHILDNODE declaration:

export class PARENTNODE {
  id: string;
  isLast: boolean;
  ...
  actAsSupplier: boolean;
}

/* Flat node with expandable and level information */
export class CHILDNODE {
  constructor(
    public id: string,
    public isLast: boolean,
    ...
    public actAsSupplier: boolean,
  ){}
}

THIRD SOLUTION

I won't code it but I'll try to explain it.

This is kind of similar to the previous solution except your could parse when tree is changed (swapping, deletion, add) the data and add a property like divType populated with true or false and call it in your html as so

 <li *ngFor="let r of node.divType" [ngClass]="{'node-arrow': r, 'node-arrow-empty': !r}" ></li>
like image 186
JSmith Avatar answered Nov 14 '22 09:11

JSmith