This is a migrated thread and some comments may be shown as answers.

Dynamically adding a node

3 Answers 1060 Views
TreeView
This is a migrated thread and some comments may be shown as answers.
Jong
Top achievements
Rank 1
Veteran
Jong asked on 22 Mar 2021, 07:54 PM

Hello,

I'm trying to dynamically add a child node to a treeview, but adding a child node to the selected dataitem does not refresh the treeview.

When I inspect the dataitem that I add a child node to, it has that child node.

Interestingly remove works fine.

 

 

I created a stackblitz, so that you can see the problem I'm having.

https://stackblitz.com/edit/angular-vqmryk?file=app%2Fapp.component.ts

Click the buttons I highlighted in the screenshot, and this will call onAddGroupClick() or onAddClick() where I'm adding a child node.

How can I make it so that the treeview understands that a node is added?

Below is the code.

import { Component } from "@angular/core";
 
@Component({
  selector: "my-app",
  template: `
    <kendo-treeview
      [nodes]="instructionJson.items"
      textField="text"
      kendoTreeViewExpandable
      kendoTreeViewHierarchyBinding
      childrenField="items"
      [isExpanded]="isExpanded"
    >
      <ng-template kendoTreeViewNodeTemplate let-dataItem>
        <ng-container *ngIf="dataItem.isHeader">
          <kendo-buttongroup selection="single">
            <button
              kendoButton
              [toggleable]="true"
              (selectedChange)="
                onOperatorSelectionChange($event, 'And', dataItem)
              "
              [primary]="dataItem.operator == 'And'"
            >
              And
            </button>
            <button
              kendoButton
              [toggleable]="true"
              (selectedChange)="
                onOperatorSelectionChange($event, 'Or', dataItem)
              "
              [primary]="dataItem.operator == 'Or'"
            >
              Or
            </button>
          </kendo-buttongroup>
          <kendo-buttongroup>
            <button
              kendoButton
              icon="k-icon k-i-filter-add-expression"
              (click)="onAddGroupClick(dataItem)"
            ></button>
            <button
              kendoButton
              icon="k-icon k-i-filter-add-group"
              (click)="onAddClick(dataItem)"
            ></button>
          </kendo-buttongroup>
          <kendo-buttongroup>
            <button
              kendoButton
              icon="k-icon k-i-close"
              (click)="onDeleteClick(dataItem)"
            ></button>
          </kendo-buttongroup>
        </ng-container>
        <ng-container *ngIf="!dataItem.isHeader">
          <div>
            <button
              kendoButton
              icon="k-icon k-i-close"
              (click)="onDeleteClick(dataItem)"
            ></button>
          </div>
        </ng-container>
      </ng-template>
    </kendo-treeview>
  `
})
export class AppComponent {
  public instructionJson: any = {
    triggerType: null,
    url: null,
    items: [
      {
        isHeader: true,
        operator: "And",
        items: [
          {
            isHeader: true,
            operator: "Or",
            items: []
          },
          {
            isHeader: false,
            items: []
          },
          {
            isHeader: false,
            items: []
          }
        ]
      }
    ]
  };
  public isExpanded = (dataItem: any, index: string) => {
    return true;
  };
  public onOperatorSelectionChange(event, operator: string, dataItem: any) {
    dataItem.operator = operator;
  }
  public onDeleteClick(dataItem: any) {
    let parentDataItem = this.getParentDataItem(this.instructionJson, dataItem);
    if (parentDataItem) {
      if (dataItem.isHeader == true) parentDataItem.items = [];
      else this.remove(parentDataItem, dataItem);
    }
  }
 
  private getParentDataItem(currentParentDataItem, dataItemToFind) {
    if (
      currentParentDataItem &&
      currentParentDataItem.items &&
      currentParentDataItem.items.length > 0
    ) {
      for (let childItem of currentParentDataItem.items) {
        if (childItem == dataItemToFind) {
          return currentParentDataItem;
        } else {
          let parentDataItem = this.getParentDataItem(
            childItem,
            dataItemToFind
          );
          if (parentDataItem != null) return parentDataItem;
        }
      }
    }
    return null;
  }
  private remove(parentDataItem, dataItemToDelete) {
    const index = parentDataItem.items.indexOf(dataItemToDelete);
    if (index > -1) {
      parentDataItem.items.splice(index, 1);
    }
  }
  public onAddGroupClick(dataItem: any) {
    dataItem.items.push({
      isHeader: true,
      operator: "And",
      items: []
    });
  }
  public onAddClick(dataItem: any) {
    dataItem.items.push({
      isHeader: false,
      items: []
    });
  }
}

3 Answers, 1 is accepted

Sort by
0
Svet
Telerik team
answered on 24 Mar 2021, 10:20 AM

Hi Jong,

Thank you for the provided details.

The undesired behavior is caused due to the instructionJson object not being updated by reference when trying to add new items. When removing an item the instructionJson object is modified:

  public onDeleteClick(dataItem: any) {
    let parentDataItem = this.getParentDataItem(this.instructionJson, dataItem);
    if (parentDataItem) {
      if (dataItem.isHeader == true) parentDataItem.items = [];
      else this.remove(parentDataItem, dataItem); //the remove method doesn't seem to be called
    }
  }

When adding a new item, the instructionJson object doesn't seem to be updated:

  public onAddGroupClick(dataItem: any) {
    dataItem.items.push({
      isHeader: true,
      operator: "And",
      items: []
    });
  }
  public onAddClick(dataItem: any) {
    dataItem.items.push({
      isHeader: false,
      items: []
    });
  }

Instead the dataItem that is the same (by value but not by reference) as the object represented in the instructionJson.items array is modified which won't update the actual instructionJson.items array.

What could be done is to update the instructionJson.items array in a similar way as follows:

  public onAddGroupClick(dataItem: any) {
     this.instructionJson.items = [
      ...this.instructionJson.items,
      { triggerType: null, url: null, items: [] }
    ];
  }
 Here is an updated example demonstrating that suggestion:

https://stackblitz.com/edit/angular-vqmryk-h9a54n?file=app%2Fapp.component.ts

I hope the provided suggestions help you to move forward. Feel free to let me know in case any further information or assistance is required for this case.

Regards,
Svetlin
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

0
Jong
Top achievements
Rank 1
Veteran
answered on 24 Mar 2021, 02:17 PM

Thanks for you reply.

But your solution does not quite work, because your code only adds a node to the root node, and I need to add a child node to the currently clicked node.

But you pointed me to the right direction.

I guess the kendo-treeview only watches for the root node's children changes and only refreshes the tree if the root node's children changes.

So, here is a workaround if anyone is interested.

public onAddGroupClick(dataItem: any) {
  dataItem.items.push({
    isHeader: true,
    operator: "And",
    items: []
  });
  // After adding a child node to the current node, create and re-assign a copy of the root's children nodes to notify the treeview that the nodes are changed.
  this.instructionJson.items = [...this.instructionJson.items];
}
public onAddClick(dataItem: any) {
  dataItem.items.push({
    isHeader: false,
    items: []
  });
  // After adding a child node to the current node, create and re-assign a copy of the root' children nodes to notify the treeview that the nodes are changed.
  this.instructionJson.items = [...this.instructionJson.items];
}
0
Accepted
Svet
Telerik team
answered on 26 Mar 2021, 09:27 AM

Hi Jong,

Thank you for sharing the update!

Indeed you are right, updating by reference the root data array will trigger Angular's change detection which will re-render the TreeView accordingly.


Svetlin
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

Tags
TreeView
Asked by
Jong
Top achievements
Rank 1
Veteran
Answers by
Svet
Telerik team
Jong
Top achievements
Rank 1
Veteran
Share this question
or