Get ordered tree items after drag and drop operation

1 Answer 107 Views
TreeView
Missing User
Missing User asked on 05 Apr 2022, 09:13 AM | edited on 05 Apr 2022, 01:28 PM

Hello everyone, I'm currently working on a complex TreeView, with a flat data binding, and I have the necessity to pass to the backend the whole tree ordered "as is" after a drag and drop operation is performed:

I prepared a starting StackBlitz at: https://stackblitz.com/edit/angular-w8t3pg?file=app/app.component.ts

Here's what I'd like to happen:

1) user drags C above A, create an array (or modify the existing one (nodes)) like this:

[
    { id: 3, name: 'C', parent: null, isfather: true },
    { id: 5, name: 'D', parent: 3, isfather: true },
    { id: 6, name: 'E', parent: 5, isfather: false},
    { id: 4, name: 'F', parent: 5, isfather: false },
    { id: 2, name: 'A', parent: null, isfather: true },
    { id: 1, name: 'B', parent: 2, isfather: false },
    { id: 7, name: 'G', parent: null, isfather: true },
    { id: 8, name: 'H', parent: 7, isfather: false },
    { id: 9, name: 'J', parent: 7, isfather: false },
  ];

2) then, the user moves B inside E (E sees its flag isfather changed to true, whereas A to false since it doesn't have any child anymore)

[
    { id: 3, name: 'C', parent: null, isfather: true },
    { id: 5, name: 'D', parent: 3, isfather: true },
    { id: 6, name: 'E', parent: 5, isfather: true },
    { id: 1, name: 'B', parent: 5, isfather: false },
    { id: 4, name: 'F', parent: 5, isfather: false },
    { id: 2, name: 'A', parent: null, isfather: false },
    { id: 7, name: 'G', parent: null, isfather: true },
    { id: 8, name: 'H', parent: 7, isfather: false },
    { id: 9, name: 'J', parent: 7, isfather: false },
];
And so on. The only operation not permitted is for a parent to be dragged inside one of his children.

Is it possible to do so? At the moment I'm trying with this solution (using my code and not the one in the example):
 <kendo-treeview
      #tree
      gdArea="checklisttree"
      [navigable]="false"

      [nodes]="nodes"
      kendoTreeViewFlatDataBinding
      textField="id"
      idField="id"
      parentIdField="idlink"

      kendoTreeViewExpandable
      [expandedKeys]="expandedKeys"
      expandBy="id"

      kendoTreeViewDragAndDrop
      kendoTreeViewDragAndDropEditing
      (nodeDrop)="getNodesInOrder($event)"
      (addItem)="log('addItem', $event)"
      (removeItem)="log('removeItem', $event)"

      kendoTreeViewSelectable
      [(selectedKeys)]="selectedKeys"
      (selectionChange)="nodeSelectionChangeHandler($event)"
    >
      <ng-template kendoTreeViewNodeTemplate let-dataItem>
        {{ '(id = ' + dataItem.id + '), (title = ' + dataItem.title + ' )' }}
      </ng-template>
      <ng-template
        kendoTreeViewDragClueTemplate
        let-action="action"
        let-sourceItem="sourceItem"
        let-destinationItem="destinationItem"
        let-text="text"
      >
        <span class="k-drag-status k-icon" [ngClass]="getDragStatus(action, sourceItem, destinationItem)"></span>
        <span>{{ text }}</span>
      </ng-template>
    </kendo-treeview>

With the idea of using the arrays inside the editService like this:
  getNodesInOrder($ev? TreeitemDragEvent): void {
    const rootNodes: any[] = (this.tree as any).editService.flatBinding.filterData; //list of the root elements
    const nodes = []; // I'd like to use this to push the various children in order I miss how to do this with recursive function
    rootNodes.forEach(node => {
      node.dataItem.isfather = node.children.length > 0;
      nodes.push(node.dataItem);
      // recursive call here?
    })
    console.log(nodes);
  }
I fear that this could break in future versions of the TreeView since it's not part of the API (editService only has add and remove if I don't cast it to any)

Here's how the editService.flatBinding.filterData looks like:



Feel free to edit the stackblitz to make me understand how to do this... Thanks in advance
Missing User
commented on 05 Apr 2022, 01:23 PM

I was able to find a way that seems to work, I'm still looking for better solutions


interface FlatBindingData {
  children: FlatBindingData[];
  dataItem: I.CheckListItems;
  visible: boolean;
  parent: FlatBindingData;
  index: string;
};


  getNodesInOrder(ev?: TreeItemDragEvent): void {
    const source = ev.sourceItem.item;
    const sourceIndex = source.index;

    const dest = ev.destinationItem.item;
    const destIndex = dest.index;


    if (ev) {
      // Prevent droppping node on itself
      if (sourceIndex === destIndex) {
        return;
      }

      // Prevent dropping node on one of his chldren (they'll share the initial portion of their index)
      if (destIndex.substring(0, sourceIndex.length) === sourceIndex) {
        return;
      }
    }



    this.tmpArr = [];
    const rootNodes: FlatBindingData[] = (this.tree as any).editService.flatBinding.filterData;
    this.recursiveFunc(rootNodes);
  }

  recursiveFunc(nodes: FlatBindingData[]) {
    nodes.forEach(node => {
      const hasChildren = node.children.length > 0;
      node.dataItem.isfather = hasChildren;
      this.tmpArr.push(node.dataItem);
      if (!hasChildren) return
      this.recursiveFunc(node.children);
    })
  }

1 Answer, 1 is accepted

Sort by
1
Accepted
Georgi
Telerik team
answered on 08 Apr 2022, 08:50 AM

Hi Marco,

Thank you for providing a sample Stackblitz demo!

May I ask for some clarification about the end result which you are after? You already have the TreeView working correctly from the user perspective - Drag & Drop works as expected. Correct?

If I understand correctly, the main issue was updating the isfather metadata on each nodeDrop event so that it remains correct at the end before you send it to the backend. Please correct me if I'm wrong here.

Assuming the above is correct, the solution you have already devised, looks good, including the recursive algorithm - however as you already mentioned, you are accessing the edited data through non-public API, which may break in the future.

I found this page in the TreeView documentation.

In this page it is stated that this is "A directive which enables the update of the initially provided data array during drag-and-drop."

This means that when the user drags a node, the original nodes array that the programmer supplied, actually gets updated with the latest changes. This means what instead of accessing the updated data through treeview.editService.flatBinding.filterData, we can just refer to this.nodes and obtain it.

All that being said, at the end, right before sending your data to the backend, you can perform a single run through the data and add the necessary metadata to it.

Also, the fact that in this example we are using flat data, allows us to even skip the recursive function and just go through the nodes linearly and set the isFather field accordingly.

Here is an example which demonstrates the described approach.

I hope that helps - please let me know if you are happy with either of the solutions.

I am happy to provide further assistance if necessary.

Looking forward to hearing from you.

Regards,
Georgi
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.

Missing User
commented on 11 Apr 2022, 09:06 AM | edited

Hello, thanks for your answer.

You already have the TreeView working correctly from the user perspective - Drag & Drop works as expected. Correct?

Yes


If I understand correctly, the main issue was updating the isfather metadata on each nodeDrop event so that it remains correct at the end before you send it to the backend. Please correct me if I'm wrong here.

Yes, but I want the data to be sent "as is": so first root, then all its children (and their children too, and their children's children too etc.), then second root and so on


I'll try the suggested method ASAP and will reply in another comment if I had success!
Missing User
commented on 11 Apr 2022, 09:55 AM

Ok, so I tried the suggested method but I think I chose the wrong starting example in my Stackblitz:

I receive an array which is ordered by ID but I'd like that when a change is made, the original nodes would reflect the status of the processed tree. So if I receive this: 


public nodes: any[] = [
    { id: 1, name: 'B', parent: 2 },
    { id: 2, name: 'A', parent: null },
    { id: 3, name: 'C', parent: null },
    { id: 4, name: 'F', parent: 5 },
    { id: 5, name: 'D', parent: 3 },
    { id: 6, name: 'E', parent: 5 },
    { id: 7, name: 'G', parent: null },
    { id: 8, name: 'H', parent: 7 },
    { id: 9, name: 'J', parent: 7 },
  ];


And then move E inside A:


I'd like nodes to be:


[
    { id: 2, name: 'A', parent: null },
    { id: 1, name: 'B', parent: 2 },
    { id: 6, name: 'E', parent: 5 },
    { id: 3, name: 'C', parent: null },
    { id: 5, name: 'D', parent: 3 },
    { id: 4, name: 'F', parent: 5 },
    { id: 7, name: 'G', parent: null },
    { id: 8, name: 'H', parent: 7 },
    { id: 9, name: 'J', parent: 7 },
  ];

Georgi
Telerik team
commented on 13 Apr 2022, 08:40 AM | edited

Hi Marco,

I think I now understand the end goal.

I have put together this updated example where I believe the outcome is achieved successfully.

Please do let me know if this helps - looking forward to hearing from you.

 

Regards,

Georgi
Progress Telerik

Missing User
commented on 13 Apr 2022, 09:02 AM

Hello and thanks for your answer; from the example I can say that's the correct behaviour. I'm gonna implement it in my project to see how it behaves and I'll report back ASAP
Missing User
commented on 13 Apr 2022, 09:44 AM

I did some tests in my project and everything seems to work fine. I'll mark the answer as accepted.

Thanks again for your help!
Tags
TreeView
Asked by
Missing User
Answers by
Georgi
Telerik team
Share this question
or