Proper usage of vaadin-grid-tree-toggle without a data provider?

I’ve been struggling with the vaadin-grid-tree-toggle component a bit in Polymer2. I’m curious if it’s required that a data provider is used when using this component or if it’s possible to bind directly to an array of data? It seems that when I bind to data that is in the form of:

[
  {hasChildren: true, uuid: 'foo.bar.baz', data: {...some data to display in grid columns here...}},
  {hasChildren: false, uuid: 'abc-def-ghi@foo', parrentUuid: 'foo.bar.baz', data: {...some data to display in grid columns here...}}
]

By using row data similar to what I have above, the tree displays correctly. The item abc-def-ghi@foo is a child of foo.bar.baz however when I click the toggle to collapse/expand, the entire grid begins repeating rows with the level cascading for each item and completely breaks.

My template code looks like the following:

    <vaadin-grid id="taskProducts" items="[[_data]
]" active-item="{{activeItem}}">
      <vaadin-grid-column>
        <template class="header">Name</template>
        <template>
          <vaadin-grid-tree-toggle
              expanded="{{expanded}}"
              leaf="[[!item.hasChildren]
]"
              level="[[level]
]">
            [[item.uuid]
]
          </vaadin-grid-tree-toggle>
        </template>
      </vaadin-grid-column>
    </vaadin-grid>

I’m using a vaadin-grid-tree-toggle template similar to what is shown at https://vaadin.com/components/vaadin-grid/html-examples/grid-tree-demos.

Looking at that example, I’m also a bit confused how the expanded and level property bindings are handled. It’s not very clear.

For my use case, I have a method on my component that makes a series of asynchronous calls to populate the _data property. I then want to display this data with specific rows of data grouped together. For my use, it makes more sense to populate the data all at once instead of lazy loading data. It all works as intended if I disable the expand/collapse functionality on the vaadin-grid-tree-toggle component, but seems to fall apart if enabled using your example of expanded="{{expanded}}". I could technically store the expand/collapse booleans and level values in the rows of data, however I’m unsure how to update this value on a row when expanded/collapsed or if that is even a valid workflow.

Any advice would be greatly appreciated.

Thank you!

Did you ever find a solution for this? Is there a way to load the tree with one ajax call?

I would like to search a database and then return the results in a hierarchical tree view.

I haven’t found a solution for this unfortunately.

We’ve abandoned using the vaadin-grid-tree-toggle component until we have more time to devote to figuring it out or until we get some guidance from someone else with experience using the component. I’m fairly confident that the issue involves the expanded and level properties. I couldn’t find anything in the documentation that covered those properties and how they’re managed per item in the data.

I’m running into this exact issue within my Polymer app. Is there anyway to contact vaadin support to get help with using the vaadin-grid-tree-toggle component if you’re not using a data provider?

I believe direct contact for support is only available to those who pay for their products. Since opening this ticket, I spent a bit of time going over the API docs and the dataProvider makes more sense now. I was not able to get the expand/collapse bindings to work without one.

When not providing a dataProvider, vaadin uses it’s own internal array data provider mixin to handle the items property binding. This is good to look at for reference: https://github.com/vaadin/vaadin-grid/blob/master/src/vaadin-grid-array-data-provider-mixin.html#L54. One thing to note about the ArrayDataProviderMixin is that it provides it’s own multisort. If you write your own grouping dataProvider, you’ll need to implement your own sorting functions. You can also use theirs for reference: https://github.com/vaadin/vaadin-grid/blob/master/src/vaadin-grid-array-data-provider-mixin.html#L104. It’s a shame they don’t include a standard multisort method to call. I wound up duplicating their sort code shown above and instead of calling this._filters, this._checkPaths or this._multiSort, I replaced the calls with this.$.myGrid._filters, this.$.myGrid._checkPaths and this.$.myGrid._multiSort so the code duplication was minimal.

When using the vaadin-grid-tree-toggle, dataProvider is executed for each level of items. For level 0 params.parentItem will be null, expanding a level 0 item will execute the dataProvider with params.parentItem being the item you just expanded, etc… Since I already have the full array of items I want to display, I just keep them stored in a private property like this._arrayOfItemsToDisplay to reference and build a custom array inside of the dataProvider for each level. My custom grouping data provider works like the following:

_groupingDataProvider(params, callback) {
  let items = []
  if (!params.parentItem) {
    // We're at level 0, create first level groups
    if (this._arrayOfItemsToDisplay.length) {
      // Logic to add items to the internal "items" array
	  // with an additional property of hasChildren: true
	  // (to bind to vaadin-grid-tree-toggle's leaf 
	  // property like leaf="[[item.hasChildren]
]")
	  // and some sort of identifier for the children
	  // Something like:
	  //   let topLevel = this._arrayOfItemsToDisplay.filter((item) => {
	  //     // Some filter to return top level items
	  //   })
	  //   topLevel.forEach((item) => {
	  //     // the ID parameter could be anything. Just 
	  //     // something to help you filter children for this item.
	  //     items.push(Object.assign({hasChildren: true, id: 'somethingToLinkChildToParent'}, item))
	  //   })
    }
  } else {
    // We're at level 1 or above, create array based on 
	// params.parentItem (look up based on identifier 
	// described above). 
    // let childItems = this._arrayOfItemsToDisplay.filter((item) => {
	//    // Again, this could be anything that ties the child to parent. 
	//    // I'm only using this as an example to satisfy the example above.
	//    return item.parentId === params.parentItem.id
	// })
	// childItems.forEach((item) => {
	//   // Like above, include an additional
	//   // property hasChildren: true if this item level 1 
	//   // item also has children 
    //   items.push(Object.assign({hasChildren: true, id: 'somethingToLinkChildToParent'}, item))
	// })
  }
  
  // Any custom multisort code here to apply to "items", 
  // sorts will be described in params.sortOrders. You can
  // use the ArrayDataProviderMixin multisort as an example
  
  // Send final array back to vaadin's callback
  // that is in charge of displaying the items
  callback(items, items.length)
}

The HTML for the vaadin-grid-tree-toggle looks like:

            <template is="dom-if" if="[[grouping]
]">
              <vaadin-grid-tree-toggle
                  expanded="{{expanded}}"
                  leaf="[[!item.hasChildren]
]"
                  level="[[level]
]">
                <div class="truncate" title$="[[item.name]
]">
                  [[item.name]
]
                </div>
              </vaadin-grid-tree-toggle>
            </template>

Hopefully this helps and makes the use of a dataProvider less daunting.

@brettbronson Thanks for taking the time to break down how the dataProvider works with the tree-toggle component. I was able to get it working.
I’m running into the issue that filtering within columns only filters on the root level items. Been wracking my brain trying to figure how to enable filtering on child nodes such that any leafs would be removed if they do not match the column filter.
Did you also run into this situation in your use case?

I actually haven’t tried filtering just yet… Looking over https://vaadin.com/components/vaadin-grid/html-examples/grid-sorting-filtering-demos#filtering-with-data-provider, my initial assumption is that the dataProvider will also execute each time the filter is updated (in this case, as _filterLastName changes in the text field. That should provide the dataProvider with an additional entry to params like: // 'params.filters' format: [{path: 'lastName', direction: 'asc'}, ...] ;.

My guess is that you could also do a check for params.filters when building an internal array inside of the dataProvider and use that to further filter down the items you’re displaying. Perhaps you’re already doing this, in which case, are you saying that the dataProvider only runs when params.parentItem is null?

That is correct, I am seeing that the _groupingDataProvider method is triggered whenever I input any text within the filter field, but when it is triggered, the params.parentItem is null. Thus the filtering is only done on the root level items.

If I completely clear the text from the filter field, then the _groupingDataProvider method is triggered for both top level items and their associated leafs, so the complete set of data is again populated within the table. I am assuming that there is a listener embedded somewhere that is calling the clearCache method from the Vaadin.Grid.DataProviderMixin API.

Hmm… I wonder if it would work if you recursively called the _groupingDataProvider function if you’re at a parent node… Maybe something like:

if (params.filters) {
	items.forEach((item) => {
		if (item.hasChildren) {
			let newParams = Object.assign(params, {parentItem: item})
			this._groupingDataProvider(newParams, callback)
		}
	})
}

?

I tried using your code above but I am running into a scoping issue with the line:
this._groupingDataProvider(newParams, callback)

Since the dataProvider is passed into the vaadin-grid, any call to the dataProvider will originate from the vaadin-grid.
Therefore, when we try to recursively call this._groupingDataProvider(newParams, callback), the scoping for this belongs to the vaadin-grid instead of the custom-element where I am defining the dataProvider.

I tried to fix the issue by first finding the parent and then calling the method (i.e: this.parentElement.parentElement…_groupingDataProvider(newParams, callback))

But then I run into the issue of the scoping being off once the function is recursively called since ‘this’ now belongs to the parent custom-component.
When recursively called, the references to the items variable is no longer for the vaadin-grid, but for the custom-component, so I run into an undefined exception.

Have you tried running it with a .bind(this) ?

this._groupingDataProvider(newParams, callback).bind(this)