org.dominokit.domino.ui.tree.Tree Maven / Gradle / Ivy
/*
* Copyright © 2019 Dominokit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dominokit.domino.ui.tree;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.dominokit.domino.ui.utils.Domino.*;
import elemental2.dom.HTMLDivElement;
import elemental2.dom.HTMLElement;
import java.util.*;
import org.dominokit.domino.ui.IsElement;
import org.dominokit.domino.ui.collapsible.CollapseStrategy;
import org.dominokit.domino.ui.elements.DivElement;
import org.dominokit.domino.ui.elements.SpanElement;
import org.dominokit.domino.ui.elements.UListElement;
import org.dominokit.domino.ui.icons.Icon;
import org.dominokit.domino.ui.icons.ToggleMdiIcon;
import org.dominokit.domino.ui.icons.lib.Icons;
import org.dominokit.domino.ui.search.Search;
import org.dominokit.domino.ui.utils.*;
/**
* The Tree class provides a tree structure for displaying hierarchical data.
*
* Example usage:
*
*
* Tree tree = Tree.create("Root");
* TreeItem item1 = TreeItem.create("Item 1", "Value 1");
* TreeItem item2 = TreeItem.create("Item 2", "Value 2");
* tree.appendChild(item1);
* tree.appendChild(item2);
*
*
* @param The type of data associated with each tree item.
* @see BaseDominoElement
*/
public class Tree extends BaseDominoElement>
implements TreeParent,
IsElement,
TreeStyles,
HasSelectionListeners, TreeItem, TreeItem> {
private ToggleTarget toggleTarget = ToggleTarget.ANY;
private TreeItemFilter> filter =
(treeItem, searchToken) ->
treeItem.getTitle().toLowerCase().contains(searchToken.toLowerCase());
private TreeItem activeTreeItem;
private boolean autoCollapse = true;
private final List> subItems = new ArrayList<>();
private boolean autoExpandFound;
private LazyChild search;
private LazyChild> collapseExpandAllIcon;
private T value;
private CollapseStrategy collapseStrategy;
private DivElement element;
private DivElement bodyElement;
private UListElement subTree;
private LazyChild headerElement;
private LazyChild> searchIcon;
private TreeItemIconSupplier iconSupplier;
private boolean selectionListenersPaused;
private final Set, ? super TreeItem>>
selectionListeners = new HashSet<>();
private final Set, ? super TreeItem>>
deselectionListeners = new HashSet<>();
/** Creates a new empty tree. */
public Tree() {
element =
div()
.addCss(dui_tree)
.appendChild(
bodyElement =
div().addCss(dui_tree_body).appendChild(subTree = ul().addCss(dui_tree_nav)));
headerElement = LazyChild.of(TreeHeader.create(), element);
init(this);
}
/**
* Creates a new tree with the given title.
*
* @param treeTitle The title of the tree.
*/
public Tree(String treeTitle) {
this();
headerElement.get().setTitle(treeTitle);
}
/**
* Creates a new tree with the given title and associated data value.
*
* @param treeTitle The title of the tree.
* @param value The data value associated with the tree.
*/
public Tree(String treeTitle, T value) {
this(treeTitle);
this.value = value;
}
/**
* Creates a new instance of Tree with the specified title and associated data value.
*
* @param title The title of the tree.
* @param value The data value associated with the tree.
* @param The type of data associated with each tree item.
* @return A new Tree instance.
*/
public static Tree create(String title, T value) {
return new Tree<>(title, value);
}
/**
* Creates a new instance of Tree with the specified title.
*
* @param title The title of the tree.
* @param The type of data associated with each tree item.
* @return A new Tree instance.
*/
public static Tree create(String title) {
return new Tree<>(title);
}
/**
* Creates a new empty instance of Tree.
*
* @param The type of data associated with each tree item.
* @return A new Tree instance.
*/
public static Tree create() {
return new Tree<>();
}
@Override
public HTMLElement getAppendTarget() {
return subTree.element();
}
/**
* Appends a child tree item to this tree.
*
* @param treeItem The tree item to append.
* @return This Tree instance for method chaining.
*/
public Tree appendChild(TreeItem treeItem) {
super.appendChild(treeItem.element());
treeItem.setParent(this);
treeItem.setToggleTarget(this.toggleTarget);
if (nonNull(collapseStrategy)) {
treeItem.setCollapseStrategy(collapseStrategy);
}
this.subItems.add(treeItem);
if (nonNull(iconSupplier)) {
treeItem.onSuppliedIconChanged(iconSupplier);
}
return this;
}
/**
* Sets a custom icon supplier for tree items in this tree. The icon supplier provides icons for
* each tree item based on its content.
*
* @param iconSupplier The custom icon supplier to set.
* @return This {@code Tree} instance for method chaining.
*/
public Tree setTreeItemIconSupplier(TreeItemIconSupplier iconSupplier) {
this.iconSupplier = iconSupplier;
if (nonNull(this.iconSupplier)) {
subItems.forEach(
item -> {
item.onSuppliedIconChanged(iconSupplier);
});
}
return this;
}
/**
* Gets the custom icon supplier set for this tree. The icon supplier provides icons for each tree
* item based on its content.
*
* @return The custom icon supplier for tree items, or {@code null} if not set.
*/
TreeItemIconSupplier getIconSupplier() {
return iconSupplier;
}
/**
* Appends a separator to this tree.
*
* @return This Tree instance for method chaining.
*/
public Tree addSeparator() {
appendChild(Separator.create());
return this;
}
/**
* Sets the toggle target for this tree.
*
* @param toggleTarget The toggle target to set.
* @return This `Tree` instance for method chaining.
*/
public Tree setToggleTarget(ToggleTarget toggleTarget) {
if (nonNull(toggleTarget)) {
subItems.forEach(item -> item.setToggleTarget(toggleTarget));
this.toggleTarget = toggleTarget;
}
return this;
}
/**
* Gets the currently active tree item in this tree.
*
* @return The currently active tree item, or {@code null} if none is active.
*/
@Override
public TreeItem getActiveItem() {
return activeTreeItem;
}
/**
* Sets the currently active tree item in this tree. The tree item will be activated, and any
* previously active item will be deactivated.
*
* @param activeItem The tree item to set as active.
*/
@Override
public void setActiveItem(TreeItem activeItem) {
setActiveItem(activeItem, false);
}
/**
* Sets the currently active tree item in this tree with an option to suppress selection events.
* The tree item will be activated, and any previously active item will be deactivated.
*
* @param activeItem The tree item to set as active.
* @param silent {@code true} to suppress selection events, {@code false} otherwise.
*/
@Override
public void setActiveItem(TreeItem activeItem, boolean silent) {
TreeItem source = null;
if (nonNull(this.activeTreeItem) && !this.activeTreeItem.equals(activeItem)) {
source = this.activeTreeItem;
this.activeTreeItem.deactivate();
}
this.activeTreeItem = activeItem;
this.activeTreeItem.activate();
if (!silent) {
triggerSelectionListeners(activeItem, activeItem);
activeItem.triggerSelectionListeners(activeItem, activeItem);
Optional.ofNullable(source)
.ifPresent(
item -> {
triggerDeselectionListeners(item, activeItem);
item.triggerDeselectionListeners(item, activeItem);
});
}
}
/**
* Gets the header of this tree.
*
* @return The `TreeHeader` of this tree.
*/
public TreeHeader getHeader() {
return headerElement.get();
}
/**
* Gets the sub-tree element of this tree.
*
* @return The sub-tree element.
*/
public UListElement getSubTree() {
return subTree;
}
/**
* Gets the title element of this tree's header.
*
* @return The title element.
*/
public SpanElement getTitle() {
return headerElement.get().getTitle();
}
/**
* Sets whether this tree is searchable.
*
* @param searchable `true` to enable search functionality, `false` otherwise.
* @return This `Tree` instance for method chaining.
*/
public Tree setSearchable(boolean searchable) {
if (searchable) {
if (isNull(search)) {
search =
LazyChild.of(
Search.create(true).onSearch(Tree.this::filter).onClose(this::clearFilter),
headerElement);
search.whenInitialized(
() -> {
search
.element()
.getInputElement()
.onKeyDown(
keyEvents -> {
keyEvents.onArrowDown(
evt -> {
subItems.stream()
.filter(item -> !dui_hidden.isAppliedTo(item))
.findFirst()
.ifPresent(item -> item.getClickableElement().focus());
});
});
});
}
if (isNull(searchIcon)) {
searchIcon =
LazyChild.of(
PostfixAddOn.of(
Icons.magnify()
.clickable()
.addClickListener(
evt -> {
evt.stopPropagation();
search.get().open();
}))
.addCss(dui_tree_header_item),
headerElement.get().getContent());
}
searchIcon.get();
} else {
if (nonNull(searchIcon)) {
searchIcon.remove();
}
if (nonNull(search)) {
search.remove();
}
}
return this;
}
/**
* Sets whether this tree is foldable.
*
* @param foldingEnabled `true` to enable folding functionality, `false` otherwise.
* @return This `Tree` instance for method chaining.
*/
public Tree setFoldable(boolean foldingEnabled) {
if (foldingEnabled) {
if (isNull(collapseExpandAllIcon)) {
collapseExpandAllIcon =
LazyChild.of(
PostfixAddOn.of(
ToggleMdiIcon.create(Icons.fullscreen(), Icons.fullscreen_exit())
.clickable()
.apply(
self ->
self.addClickListener(
evt -> {
evt.stopPropagation();
if (self.isToggled()) {
collapseAll();
} else {
expandAll();
}
self.toggle();
})))
.addCss(dui_tree_header_item),
headerElement.get().getContent());
}
collapseExpandAllIcon.get();
} else {
if (nonNull(collapseExpandAllIcon)) {
collapseExpandAllIcon.remove();
}
}
return this;
}
/** Expands all tree items in this tree. */
public void expandAll() {
getSubItems().forEach(TreeItem::expandAll);
}
/** Collapses all tree items in this tree. */
public void collapseAll() {
getSubItems().forEach(TreeItem::collapseAll);
}
/** Deactivates all tree items in this tree. */
public void deactivateAll() {
getSubItems().forEach(TreeItem::deactivate);
}
/**
* Enables automatic expansion of found tree items when using the search feature.
*
* @return This tree instance to allow method chaining.
*/
public Tree autoExpandFound() {
this.autoExpandFound = true;
return this;
}
/**
* Checks if automatic expansion of found tree items is enabled when using the search feature.
*
* @return {@code true} if automatic expansion is enabled, {@code false} otherwise.
*/
@Override
public boolean isAutoExpandFound() {
return autoExpandFound;
}
/**
* Sets whether to auto-expand found items in the tree.
*
* @param autoExpandFound `true` to auto-expand found items, `false` otherwise.
* @return This `Tree` instance for method chaining.
*/
public Tree setAutoExpandFound(boolean autoExpandFound) {
this.autoExpandFound = autoExpandFound;
return this;
}
/** Clears the search filter applied to tree items in this tree. */
public void clearFilter() {
subItems.forEach(TreeItem::clearFilter);
}
/**
* Filters tree items in this tree based on the given search token.
*
* @param searchToken The search token to filter tree items.
*/
public void filter(String searchToken) {
subItems.forEach(treeItem -> treeItem.filter(searchToken));
}
/**
* Returns a reference to the root tree within which this tree is contained. Since a tree is
* self-contained and typically not nested within other trees, this method returns a reference to
* the current tree instance.
*
* @return A reference to the current tree instance.
*/
@Override
public Tree getTreeRoot() {
return this;
}
/**
* Sets whether this tree should automatically collapse when a new item is selected.
*
* @param autoCollapse `true` to automatically collapse the tree, `false` otherwise.
* @return This `Tree` instance for method chaining.
*/
public Tree setAutoCollapse(boolean autoCollapse) {
this.autoCollapse = autoCollapse;
return this;
}
/**
* Sets the title of this tree.
*
* @param title The title to set.
* @return This `Tree` instance for method chaining.
*/
public Tree setTitle(String title) {
headerElement.get().setTitle(title);
return this;
}
/**
* Sets the icon for this tree.
*
* @param icon The icon to set.
* @return This `Tree` instance for method chaining.
*/
public Tree setIcon(Icon> icon) {
headerElement.get().setIcon(icon);
return this;
}
/**
* Checks whether this tree is set to auto-collapse when a new item is selected.
*
* @return `true` if auto-collapse is enabled, `false` otherwise.
*/
public boolean isAutoCollapse() {
return autoCollapse;
}
/**
* Returns a list of sub-items contained within this tree. The sub-items are represented as
* instances of {@link TreeItem}.
*
* @return A list of sub-items contained within this tree.
*/
@Override
public List> getSubItems() {
return new ArrayList<>(subItems);
}
/**
* Expands the node within the tree, indicating that it should be expanded or collapsed. This
* method has no effect on the root tree.
*
* @param expandParent {@code true} to expand the parent node, {@code false} to collapse it.
* @return A reference to this tree.
*/
@Override
public TreeParent expandNode(boolean expandParent) {
return this;
}
/**
* Expands the node within the tree. This method has no effect on the root tree.
*
* @return A reference to this tree.
*/
@Override
public TreeParent expandNode() {
return this;
}
/**
* Returns an empty optional, as this tree does not have a parent tree.
*
* @return An empty optional.
*/
@Override
public Optional> getParent() {
return Optional.empty();
}
/** Activates this tree, making it the active tree item. */
@Override
public void activate() {}
/**
* Activates this tree, making it the active tree item. This method has no effect on the parent
* tree.
*
* @param activateParent {@code true} to activate the parent tree, {@code false} to deactivate it.
*/
@Override
public void activate(boolean activateParent) {}
/**
* Gets the search input field associated with this tree if it is searchable.
*
* @return An `Optional` containing the search input field, or empty if not searchable.
*/
public Optional getSearch() {
if (nonNull(search) && search.isInitialized()) {
return Optional.ofNullable(search.get());
}
return Optional.empty();
}
/**
* Gets the search icon associated with this tree if it is searchable.
*
* @return An `Optional` containing the search icon, or empty if not searchable.
*/
public Optional> getSearchIcon() {
if (nonNull(searchIcon) && search.isInitialized()) {
return Optional.of(searchIcon.get());
}
return Optional.empty();
}
/**
* Gets the collapse/expand all icon associated with this tree if it is foldable.
*
* @return An `Optional` containing the collapse/expand all icon, or empty if not foldable.
*/
public Optional> getCollapseExpandAllIcon() {
if (nonNull(collapseExpandAllIcon) && collapseExpandAllIcon.isInitialized()) {
return Optional.of(collapseExpandAllIcon.get());
}
return Optional.empty();
}
/**
* Gets the value associated with this tree.
*
* @return The value associated with this tree.
*/
public T getValue() {
return value;
}
/**
* Sets the value associated with this tree.
*
* @param value The value to set.
*/
public void setValue(T value) {
this.value = value;
}
/**
* Gets a list of active tree items in the path from the root to the currently active item.
*
* @return A list of active tree items.
*/
public List> getActivePath() {
List> activeItems = new ArrayList<>();
TreeItem activeItem = getActiveItem();
while (nonNull(activeItem)) {
activeItems.add(activeItem);
activeItem = activeItem.getActiveItem();
}
return activeItems;
}
/**
* Gets a list of values associated with active tree items in the path from the root to the
* currently active item.
*
* @return A list of values associated with active tree items.
*/
public List getActivePathValues() {
List activeValues = new ArrayList<>();
TreeItem activeItem = getActiveItem();
while (nonNull(activeItem)) {
activeValues.add(activeItem.getValue());
activeItem = activeItem.getActiveItem();
}
return activeValues;
}
/**
* Removes the specified tree item from this tree. This method removes the tree item from the list
* of sub-items and calls the {@link TreeItem#remove()} method on the item to detach it from the
* DOM.
*
* @param item The tree item to be removed.
*/
@Override
public void removeItem(TreeItem item) {
subItems.remove(item);
item.remove();
}
/**
* Clears all child items from this tree.
*
* @return This `Tree` instance for method chaining.
*/
public Tree clear() {
subItems.forEach(TreeItem::remove);
return this;
}
/**
* Sets a filter for tree items in this tree.
*
* @param filter The filter to set.
* @return This `Tree` instance for method chaining.
*/
public Tree setFilter(TreeItemFilter> filter) {
this.filter = filter;
return this;
}
/**
* Gets the current filter used for searching within the tree.
*
* @return The current filter applied to the tree items for searching.
*/
@Override
public TreeItemFilter> getFilter() {
return this.filter;
}
/**
* Sets the collapse strategy for all tree items in this tree.
*
* @param collapseStrategy The collapse strategy to set.
* @return This `Tree` instance for method chaining.
*/
public Tree setCollapseStrategy(CollapseStrategy collapseStrategy) {
getSubItems().forEach(tTreeItem -> setCollapseStrategy(collapseStrategy));
this.collapseStrategy = collapseStrategy;
return this;
}
/**
* Gets the collapse strategy set for this tree.
*
* @return The collapse strategy.
*/
public CollapseStrategy getCollapseStrategy() {
return collapseStrategy;
}
/**
* Configures the header of this tree using a `ChildHandler`.
*
* @param handler The `ChildHandler` to configure the header.
* @return This `Tree` instance for method chaining.
*/
public Tree withHeader(ChildHandler, TreeHeader> handler) {
handler.apply(this, headerElement.get());
return this;
}
/**
* Pauses the selection listeners of the tree, preventing them from reacting to selection events.
*
* @return The current tree instance with selection listeners paused or resumed based on the
* toggle value.
*/
@Override
public Tree pauseSelectionListeners() {
this.selectionListenersPaused = true;
return this;
}
/**
* Resumes the paused selection listeners of the tree, allowing them to react to selection events.
*
* @return The current tree instance with selection listeners resumed.
*/
@Override
public Tree resumeSelectionListeners() {
this.selectionListenersPaused = false;
return this;
}
/**
* Toggles the pause state of selection listeners of the tree.
*
* @param toggle {@code true} to pause the listeners, {@code false} to resume them.
* @return The current tree instance with selection listeners paused or resumed based on the
* toggle value.
*/
@Override
public Tree togglePauseSelectionListeners(boolean toggle) {
this.selectionListenersPaused = toggle;
return this;
}
/**
* Gets the set of selection listeners registered with the tree.
*
* @return A set containing selection listeners.
*/
@Override
public Set, ? super TreeItem>> getSelectionListeners() {
return this.selectionListeners;
}
/**
* Gets the set of deselection listeners registered with the tree.
*
* @return A set containing deselection listeners.
*/
@Override
public Set, ? super TreeItem>>
getDeselectionListeners() {
return this.deselectionListeners;
}
/**
* Checks if the selection listeners of the tree are currently paused.
*
* @return {@code true} if selection listeners are paused, {@code false} otherwise.
*/
@Override
public boolean isSelectionListenersPaused() {
return this.selectionListenersPaused;
}
/**
* Triggers selection listeners with the provided source and selection tree items.
*
* @param source The source tree item that triggered the selection.
* @param selection The selected tree item.
* @return The current tree instance with selection listeners triggered.
*/
@Override
public Tree triggerSelectionListeners(TreeItem source, TreeItem selection) {
if (!this.selectionListenersPaused) {
this.selectionListeners.forEach(
listener -> listener.onSelectionChanged(Optional.ofNullable(source), selection));
}
return this;
}
/**
* Triggers deselection listeners with the provided source and deselected tree items.
*
* @param source The source tree item that triggered the deselection.
* @param selection The deselected tree item.
* @return The current tree instance with deselection listeners triggered.
*/
@Override
public Tree triggerDeselectionListeners(TreeItem source, TreeItem selection) {
if (!this.selectionListenersPaused) {
this.deselectionListeners.forEach(
listener -> listener.onSelectionChanged(Optional.ofNullable(source), selection));
}
return this;
}
/**
* Gets the currently selected tree item in the tree.
*
* @return The currently selected tree item, or {@code null} if none is selected.
*/
@Override
public TreeItem getSelection() {
return this.activeTreeItem;
}
/**
* Gets the HTMLDivElement representing the tree element.
*
* @return The HTMLDivElement representing the tree.
*/
@Override
public HTMLDivElement element() {
return element.element();
}
/**
* An interface to handle item click events in the tree.
*
* @param The type of data associated with each tree item.
*/
public interface ItemClickListener {
/**
* Called when a tree item is clicked.
*
* @param treeItem The tree item that was clicked.
*/
void onTreeItemClicked(TreeItem treeItem);
}
/**
* An interface to provide custom icons for tree items.
*
* @param The type of data associated with each tree item.
*/
public interface TreeItemIconSupplier {
/**
* Creates an icon for the given tree item.
*
* @param item The tree item for which to create the icon.
* @return The created icon.
*/
Icon> createIcon(TreeItem item);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy