How to Implement a TreeWidget with Granular Control

I am trying to figure out exactly how to implement a custom widget extending TreeWidget with a bit more granular control than including a whole tree rendering in a bigger view.

What I’m trying to do is render name, description and input components for each leaf node in the tree, while rendering names only for their parents. The input components should differ based on input type and they should obtain focus if the enter key is pressed when the corresponding leaf node is selected.

My main problems I have are the following:

What is needed for the tree to keep keyboard navigation functionality?

What shape should the data presented to the render functions have?

How do I stop the stock search functionality from capturing keyboard inputs within the tree?

[original thread by NicholasStenbeck]

cc: @vince-fugnitto @paul-marechal

@anton-kosyakov knows more about trees than Vince and me combined here.

That may be true, but he also may be more busy than the two of you combined :slight_smile:

Ok sorry, but I genuinely don’t know how to minimally use trees in Theia. I know one needs some kind of model and then feed that into some tree widget? Maybe looking at the outline view implementation might help: https://github.com/eclipse-theia/theia/tree/05f2e07bb77637723e6720231342b9acce01a159/packages/outline-view/src/browser

But I wouldn’t be able to tell if the requirements mentioned by Nicholas are completely met.

[Hanksha]

You might be interested in this topic https://spectrum.chat/theia/general/simple-treewidget-implementation-example~1b2e2852-0536-4876-9f59-de46acb58a5ehttps://spectrum.chat/theia/general/simple-treewidget-implementation-example~1b2e2852-0536-4876-9f59-de46acb58a5e

[Hanksha]

What is needed for the tree to keep keyboard navigation functionality?

I’m guessing you want to keep the up/down arrow navigation even when a text input is focused? I never tired it but I think you’ll have to capture the events of each input text and forward it to the TreeWidget handleUp and handleDown methods.

What shape should the data presented to the render functions have?

The TreeWidget maintain the tree data in the form of TreeNode which have other extending interfaces to provide some behavior to each node like SelectableTreeNode, ExpandableTreeNode. I find it a bit cumbersome to work with tree node and prefer to use my domain models so what I did is have a generic tree node interface like this:

export interface DataTreeNode
    extends CompositeTreeNode,
        SelectableTreeNode,
        ExpandableTreeNode {
    data: any;
    resolved: boolean;
}

Then I can set the tree root by building DataTreeNodes based on my domain model where I set data as my model.

You can then override the TreeWidget methods like this:

// controlling expandable state
isExpandable(node: TreeNode): node is ExpandableTreeNode {
    if (DataTreeNode.is(node)) {
      if (node.resolved) return node.children.length > 0;

      const data = node.data
      if (MyModel.is(data)) return true;
      if(OtherModel.is(data) && data.myBoolean) return true;
      return false;
    }
    return super.isExpandable(node);
  }
// overriding caption
renderCaption(node: TreeNode, props: NodeProps): React.ReactNode {
    if (DataTreeNode.is(node)) {
      const base = super.renderCaption(node, props) as React.ReactElement;
      const children: React.ReactNode[] = [base.props.children];

      const data = node.data
      if (MyModel.is(data)) children.push(<input type="text" />)

      return <div {...base.props}>{children}</div>;
    }

    return super.renderCaption(node, props);
  }

etc. just check which TreeWidget method you need to override.

How do I stop the stock search functionality from capturing keyboard inputs within the tree?

You have two options AFAIK, you can disable the search feature when you bind your tree like this:

createTreeContainer(parent, {
        search: false
})

The other option is to rebind the SearchBoxFactory and return your own SearchBox implementation which doesn’t listen to key event when an input text is focused.

If there is a way to enable/disable the current SearchBox impl, I’m not aware of it.

Something like @hanksha mentioned.

Each tree widgets has tree model on which a root can be set. Tree Model is using Tree as data source. One can implement Tree.resolveChildren to provide child nodes. TreeWidget can be extended to adjust how some now looks like

There is also SourceTreeWidget API, it’s build on the top of Tree, the different is that you don’t provide elements as pure json, but instances which can render itself.

We don’t have docs about it. The best would be to read code to see how it is used.

[NicholasStenbeck]

Thank you for the responses. I’ll start with checking out SourceTreeWidget, since it seems to fit nicely with what I want to do.

Nicholas is it related to the preferences PR? I will be able to concentrate on reviewing it more in April probably. For March we need to get the VSX Registry integration done and move Monaco PR forward. That’s my primary focus and eats up a lot of time already, sorry.

[NicholasStenbeck]

@anton-kosyakov Yes, it is related to the Preferences PR. There’s a lot to unpack, but I’m working on it.

[NicholasStenbeck]

I currently have a class extending SourceTreeWidget that contains a source with a model that has a list of all TreeElements I want to render, but I have a hard time figuring out how to feed this all properly to the render function. Is this something that should happen in the background if I set up everything correctly or should I override the render function?

My current hierarchy is
BoxPanel -> SourceTreeWidget -> TreeSource -> TreeModelmpl -> SourceTree

I have been staring holes at the code in the examples I find from looking for anything that extends SourceTreeWidget, but have yet to find the answers I am looking for.

A widget listens to source and whenever it changes, elements gets fetched from source and rerendered if they are visible.

[NicholasStenbeck]

Does this mean that the widget will call source.getElements whenever source.fireDidChange has been called? In that case, is firing the event something that needs to be done manually?

Thank you for taking the time to help.

Yes, a source should now better when it is changed.

look for instance at debug watch expressions source: https://github.com/eclipse-theia/theia/blob/9457f8087bb421b7aa1c6cf124c6dfe57659df5f/packages/debug/src/browser/view/debug-watch-source.ts#L35-L39