Vaadin Tree Grid throws a recursion error

Viewed 61

I am working with Vaadin 14.7.5, so vaadin-grid 5.9.0.

A single vaadin-grid element containting a single vaadin-grid-tree-toggle element is inside a Lit Element.

import {css, html, LitElement} from 'lit-element';
import {render} from "lit-html";

import '@vaadin/vaadin-grid';
import '@vaadin/vaadin-grid/vaadin-grid-column';
import '@vaadin/vaadin-grid/vaadin-grid-tree-column';
import '@vaadin/vaadin-grid/vaadin-grid-tree-toggle';

class ProjectTaskTreeElement extends LitElement {

  static get properties() {
    return {
      project: {
        attribute: false
      }
    };
  }

  constructor() {
    super();
  }

  static get styles() {
    return css`
      :host {
        display: block;

        width: 100%;
      }

      :host([hidden]) {
        display: none !important;
      }
    `;
  }

  render() {
    return html`
        <vaadin-grid id="tree"
                     .items="${this.project ? this.project.top_level_tasks : []}">
            <vaadin-grid-tree-column header="Name" path="name" item-has-children-path="subtasks"></vaadin-grid-tree-column>
        </vaadin-grid>
    `;
  }

}

customElements.define('project-task-tree', ProjectTaskTreeElement);

export {
  ProjectTaskTreeElement
};

Here's an example of projects:

{
  "id": 30,
  "name": "Test Project",
  "archived": false,
  "top_level_tasks": [
    {
      "id": 18,
      "name": "1",
      "complete": false,
      "subtasks": [
        {
          "id": 19,
          "name": "1.1",
          "complete": false,
          "subtasks": [],
          "teammates": []
        }
      ],
      "teammates": []
    }
  ],
  "teammates": []
}

I expected this to work fine. ArrayDataProviderMixin#_arrayDataProvider should handle items as a data provider. The grid renders fine, with the expected root row collapsed and expandable. But when I click to expand, an error is thrown:

Uncaught InternalError: too much recursion
    set http://localhost:8080/node_modules/@polymer/polymer/lib/mixins/properties-changed.js:167
    _arrayDataProvider http://localhost:8080/node_modules/@vaadin/vaadin-grid/src/vaadin-grid-array-data-provider-mixin.js:66
    _loadPage http://localhost:8080/node_modules/@vaadin/vaadin-grid/src/vaadin-grid-data-provider-mixin.js:347
    ensureSubCacheForScaledIndex http://localhost:8080/node_modules/@vaadin/vaadin-grid/src/vaadin-grid-data-provider-mixin.js:70
    _loadPage http://localhost:8080/node_modules/@vaadin/vaadin-grid/src/vaadin-grid-data-provider-mixin.js:364
    _loadPage http://localhost:8080/node_modules/@vaadin/vaadin-grid/src/vaadin-grid-data-provider-mixin.js:359
    _arrayDataProvider http://localhost:8080/node_modules/@vaadin/vaadin-grid/src/vaadin-grid-array-data-provider-mixin.js:75
properties-changed.js:167:51

I have tried templating a vaadin-grid-column element instead, but the same error occurs:

<vaadin-grid-column>
    <template class="header">Name</template>
    <template>
        <vaadin-grid-tree-toggle leaf="[[!item.subtasks.length]]"
                                 expanded="{{expanded}}"
                                 level="[[level]]">
            [[item.name]]
        </vaadin-grid-tree-toggle>
    </template>
</vaadin-grid-column>
1 Answers

Figured it out. After careful review of ArrayDataProviderMixin, I found it would not support my data's format.

First, I transformed my data:

_flattenTasks(tasks) {
  function traveler(task, parent, accumulator) {
    accumulator.push({
      id: task.id,
      name: task.name,
      hasSubtasks: task.subtasks.length > 0,
      parentTaskId: parent ? parent.id : null
    });

    for(const subtask of task.subtasks) {
      accumulator = traveler(subtask, task, accumulator);
    }

    return accumulator;
  }

  let accumulator = [];

  for(const task of tasks) {
    accumulator = traveler(task, null, accumulator);
  }

  return accumulator;
}

Then, I wrote a data provider:

_treeDataProvider(parameters, callback) {
  const getSubtasks = (parentTaskId, callback) => {
    callback(this._tasks.filter((task) => {
      if(parentTaskId) {
        return (task.parentTaskId === parentTaskId);
      } else {
        return !task.parentTaskId;
      }
    }));
  };

  const parentTaskId = parameters.parentItem ? parameters.parentItem.id : null;

  getSubtasks(parentTaskId, (subtasks) => {
    const startIndex = parameters.page * parameters.pageSize;
    const pageItems = subtasks.slice(startIndex, startIndex + parameters.pageSize);
    const treeLevelSize = subtasks.length;

    callback(pageItems, treeLevelSize);
  });
}
Related