Skip to content

Add documentation for the TreeWidget #736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

xpomul
Copy link

@xpomul xpomul commented Mar 6, 2025

Contributes a documentation page for the TreeWidget, describing how a custom Tree View can be implemented.
Highlights several features and customization possibilities.

Note: This PR references example code in the Theia Extension Generator repository which is not yet merged.
The respective PR is eclipse-theia/generator-theia-extension#241
That PR should be merged first before merging this one.

xpomul added 2 commits March 6, 2025 11:30
Adds a documentation page describing how a custom TreeWidget can be implemented based on the
Theia TreeWidget base class and collaborating classes. Highlights various features and how
to use them.

Signed-off-by: Stefan Winkler <stefan@winklerweb.net>
@xpomul xpomul temporarily deployed to pull-request-preview March 6, 2025 10:37 — with GitHub Actions Inactive
Copy link

github-actions bot commented Mar 6, 2025

PR Preview Action v1.4.7
🚀 Deployed preview to https://eclipse-theia.github.io/theia-website-previews/pr-previews/pr-736/
on branch previews at 2025-04-30 13:51 UTC

@xpomul xpomul temporarily deployed to pull-request-preview March 13, 2025 13:15 — with GitHub Actions Inactive
@xpomul xpomul marked this pull request as draft April 23, 2025 13:31
@xpomul
Copy link
Author

xpomul commented Apr 23, 2025

converted to draft - needs to be updated once eclipse-theia/generator-theia-extension#241 has been finalized.

@xpomul
Copy link
Author

xpomul commented Apr 30, 2025

@martin-fleck-at I have adapted the docs to the final example code. So it is ready for review now.
Thanks and have a nice long weekend ;-)

@martin-fleck-at
Copy link
Contributor

@xpomul Fantastic, thank you, I'll have a look at it today!

Copy link
Contributor

@martin-fleck-at martin-fleck-at left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for writing up this documentation @xpomul!

I think we already have a lot of information on this page but I do worry a bit about a clear distinction between the generic tree UI part vs the example that you use. I think we need to add a bit more generic information in the beginning about the tree, the services and the properties as well that for each of those things we do have default implementations.

Afterwards we need to make it very clear, that we will explain customization based on one particular example but that use cases can vary a lot and not all parts that are mentioned in the example need to be implemented by every adopter, e.g., checkboxes. I think readers may otherwise get overwhelmed and confused about what it is they actually need to do to get their own tree in the most basic version.

What do you think?


All of these are explained below in more detail. For demonstration purposes, we will create a `TreeviewExampleWidget` with some demo contents.

### The `TreeWidget` widget
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd avoid the code formatting in headers, it looks a bit off imho.


## Basic Building Blocks

To create a basic `TreeWidget`, we need to implement:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
To create a basic `TreeWidget`, we need to implement:
To create a basic `TreeWidget`, we the following parts:

(cause we do not actually need to implement all of them, for some of them we could just take the default implementation and the label provider may not be needed at all)

];
```

This business model needs to be mapped to a hierarchy of tree nodes. These are objects that satisfy the `TreeNode` interface, which primarily provides the `id` and `parent` properties. Each node in the tree is required to have an `id` that is unique within the tree _(Note: expect strange effects if your `id`s are not unique!)._ The `parent` property can be `undefined` initially, and will be managed by utiltiy functions within the `CompoiteTreeNode` namespace, which are described below.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This business model needs to be mapped to a hierarchy of tree nodes. These are objects that satisfy the `TreeNode` interface, which primarily provides the `id` and `parent` properties. Each node in the tree is required to have an `id` that is unique within the tree _(Note: expect strange effects if your `id`s are not unique!)._ The `parent` property can be `undefined` initially, and will be managed by utiltiy functions within the `CompoiteTreeNode` namespace, which are described below.
This business model needs to be mapped to a hierarchy of tree nodes. These are objects that satisfy the `TreeNode` interface, which primarily provides the `id` and `parent` properties. Each node in the tree is required to have an `id` that is unique within the tree _(Note: expect strange effects if your `id`s are not unique!)._ The `parent` property can be `undefined` initially, and will be managed by utility functions within the `CompositeTreeNode` namespace, which are described below.

}
```

In this example, we initialize the tree in the `init()` method when the class is instantiated. This is not necessarily required, but it is the simplest way for our static model. In more complex scenarios, we could also call a concrete `initModel()` method from the Tree Widget implementation, for example, in the `onAfterAttach()` event handler, or we could implement the initialization asynchonously.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In this example, we initialize the tree in the `init()` method when the class is instantiated. This is not necessarily required, but it is the simplest way for our static model. In more complex scenarios, we could also call a concrete `initModel()` method from the Tree Widget implementation, for example, in the `onAfterAttach()` event handler, or we could implement the initialization asynchonously.
In this example, we initialize the tree in the `init()` method when the class is instantiated. This is not necessarily required, but it is the simplest way for our static model. In more complex scenarios, we could also call a concrete `initModel()` method from the Tree Widget implementation, for example, in the `onAfterAttach()` event handler, or we could implement the initialization asynchronously.

* a label provider contribution - a subclass of `LabelProviderContribution`
* the view contribution - a subclass of `AbstractViewContribution`, as is usual for all widgets/views
* the widget factory, which is realized using an inversify child container

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are not mentioning the Tree with the default TreeImpl implementation.

To create a basic `TreeWidget`, we need to implement:

* the actual widget - a subclass of `TreeWidget`
* a model - a subclass of `TreeModelImpl`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd almost suggest to focus a bit more on the interfaces and mention that there are default implementations for each part already but that it can be customized.

}
```

> **Note:** At the moment (Theia 1.60.x), there is an issue with the UI in which the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd remove the Theia 1.60.x version here because it might have already been present in previous versions, just referencing the issue below should be enough.


Note: The code snippets shown below are part of the full example available via the [Theia Extension Generator](https://github.com/eclipse-theia/generator-theia-extension/blob/master/README.md). To get started and play with the example code, generate the `TreeWidget View` example using the generator.

## Basic Building Blocks
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would like to see some more general explanation here (or in an Overview section) about the tree UI in general, because at its core the tree widget is not really different than any widget that is created through widget factories. The main difference is, that due to the complexity of tree UI behavior, Theia provides a utility function createTreeContainer to tie a set of services (selection, expansion, navigation, search, etc.) and behavioral properties (multi select, virtualization, etc.) together to create a pretty good default UI. So writing a tree UI consists mainly of making sure that the widget factory uses a tree container with all the necessary services and properties that define the behavior of the tree. So it is more in line with what you explain the section Putting it all together. All that is left is for the user to plug in their own model.

Of course, the parts that you mention here are all valid and important but not all of them are strictly necessary ("basic"). I think having a more general picture of the tree first and then diving into a use case for customization (i.e., the Example tree) may give a clearer picture here, what do you think?


### The `TreeWidget` widget

The `TreeWidget` is a specialized `ReactWidget` that already implements all of the logic required to render trees and let the user interact with them. So in its simplest form, we need to just implement the constructor to initialize the id, title, and caption
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if we "need" to do it but we definitely want to do it. I'd be a bit more careful in what we describe as necessary in this article vs a common approach/recommendation.

}
```

Under the hood, the tree expansion is handled by the `TreeExpansionServiceImpl` implementation which maintains the `expanded` state of `ExpandableTreeNode`, sends events related to expanding and collapsing subtrees, and also calls `Tree.refresh()` for the expanded node. The `refresh()` method in turn calls `resolveChildren()` which can be implemented to asyncronously provide the child nodes for the given parent. As a bonus, `Tree.refresh()` marks the node as `busy` until the promise is resolved, leading to a nice busy marker in the form of a spinning circle if the promise is not resolved within a certain amount of time (800ms). This can be observed in this demo code due to the `wait(2000)` call.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TreeExpansionServiceImpl implementation -> TreeExpansionService implementation (could be anything that is bound to that service)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants