diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/collapsible/TreeHeightCollapseStrategy.java b/domino-ui/src/main/java/org/dominokit/domino/ui/collapsible/TreeHeightCollapseStrategy.java index e9ced3112..9ef0b9c64 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/collapsible/TreeHeightCollapseStrategy.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/collapsible/TreeHeightCollapseStrategy.java @@ -17,20 +17,19 @@ import static org.dominokit.domino.ui.collapsible.Collapsible.DUI_COLLAPSED; import static org.dominokit.domino.ui.style.GenericCss.dui_transition_none; -import static org.dominokit.domino.ui.utils.Domino.*; import static org.dominokit.domino.ui.utils.ElementsFactory.elements; import elemental2.dom.AddEventListenerOptions; import elemental2.dom.DomGlobal; import elemental2.dom.Element; import elemental2.dom.EventListener; -import org.dominokit.domino.ui.tree.TreeItem; +import org.dominokit.domino.ui.tree.TreeNode; import org.dominokit.domino.ui.utils.DominoId; import org.dominokit.domino.ui.utils.IsCollapsible; /** - * An implementation of {@link org.dominokit.domino.ui.collapsible.CollapseStrategy} that is meant - * to be used with the {@link org.dominokit.domino.ui.tree.Tree} component + * An implementation of {@link CollapseStrategy} that is meant to be used with the {@link + * org.dominokit.domino.ui.tree.Tree} component */ public class TreeHeightCollapseStrategy implements CollapseStrategy, CollapsibleStyles { @@ -43,42 +42,42 @@ public class TreeHeightCollapseStrategy implements CollapseStrategy, Collapsible private final CollapsibleDuration transition; private final String heightVar; private CollapsibleHandlers handlers; - private final TreeItem treeItem; + private final TreeNode node; private boolean expanding = false; private boolean collapsing = false; /** * Constructor for TreeHeightCollapseStrategy. * - * @param treeItem a {@link org.dominokit.domino.ui.tree.TreeItem} object + * @param node a {@link org.dominokit.domino.ui.tree.TreeItem} object */ - public TreeHeightCollapseStrategy(TreeItem treeItem) { - this(treeItem, CollapsibleDuration._300ms); + public TreeHeightCollapseStrategy(TreeNode node) { + this(node, CollapsibleDuration._300ms); } /** * Constructor for TreeHeightCollapseStrategy. * - * @param treeItem a {@link org.dominokit.domino.ui.tree.TreeItem} object + * @param node a {@link org.dominokit.domino.ui.tree.TreeItem} object * @param transition a {@link CollapsibleDuration} object */ - public TreeHeightCollapseStrategy(TreeItem treeItem, CollapsibleDuration transition) { - this.treeItem = treeItem; + public TreeHeightCollapseStrategy(TreeNode node, CollapsibleDuration transition) { + this.node = node; this.transition = transition; this.heightVar = DominoId.unique(EXPAND_COLLAPSE_HEIGHT_VAR); - this.treeItem.setAttribute(DUI_EXPAND_COLLAPSE_VAR, this.heightVar); - this.treeItem.setCssProperty("height", "var(" + this.heightVar + ", auto)"); + this.node.setAttribute(DUI_EXPAND_COLLAPSE_VAR, this.heightVar); + this.node.setCssProperty("height", "var(" + this.heightVar + ", auto)"); } /** @dominokit-site-ignore {@inheritDoc} */ @Override public void init(Element element, CollapsibleHandlers handlers) { this.handlers = handlers; - this.treeItem.addCss(dui_height_collapsed_overflow).addCss(transition.getStyle()); - this.treeItem.nowOrWhenAttached( + this.node.addCss(dui_height_collapsed_overflow).addCss(transition.getStyle()); + this.node.nowOrWhenAttached( () -> { - double height = treeItem.getBoundingClientRect().height; - treeItem.setAttribute(DUI_COLLAPSED_HEIGHT, height); + double height = node.getBoundingClientRect().height; + node.setAttribute(DUI_COLLAPSED_HEIGHT, height); }); element.setAttribute(DUI_COLLAPSED, true); @@ -94,17 +93,17 @@ public void cleanup(Element element) { /** @dominokit-site-ignore {@inheritDoc} */ @Override public void expand(Element element) { - treeItem.nowOrWhenAttached( + node.nowOrWhenAttached( () -> { if (!collapsing) { this.expanding = true; this.handlers.onBeforeExpand().run(); - double height = treeItem.getBoundingClientRect().height; - treeItem.setAttribute(DUI_COLLAPSED_HEIGHT, height); - treeItem.getSubTree().show(); - treeItem.setCssProperty(this.heightVar, height + "px"); + double height = node.getBoundingClientRect().height; + node.setAttribute(DUI_COLLAPSED_HEIGHT, height); + node.getSubTree().show(); + node.setCssProperty(this.heightVar, height + "px"); double expandedHeight = getActualHeight(); - treeItem.setAttribute(DUI_EXPANDED_HEIGHT, expandedHeight); + node.setAttribute(DUI_EXPANDED_HEIGHT, expandedHeight); expandElement(element); } }); @@ -112,7 +111,7 @@ public void expand(Element element) { private double getActualHeight() { double expandedHeight = - treeItem.childElements().stream() + node.childElements().stream() .filter(IsCollapsible::isExpanded) .mapToDouble(e -> e.getBoundingClientRect().height) .sum(); @@ -120,38 +119,36 @@ private double getActualHeight() { } private void expandElement(Element element) { - if (dui_transition_none.isAppliedTo(treeItem)) { - treeItem.setCssProperty(this.heightVar, "auto"); - treeItem.removeAttribute(DUI_COLLAPSED); + if (dui_transition_none.isAppliedTo(node)) { + node.setCssProperty(this.heightVar, "auto"); + node.removeAttribute(DUI_COLLAPSED); handlers.onExpandCompleted().run(); expanding = false; } else { EventListener stopListener = evt -> { - resetParentHeight(treeItem); - treeItem.setCssProperty(this.heightVar, "auto"); + resetParentHeight(node); + node.setCssProperty(this.heightVar, "auto"); handlers.onExpandCompleted().run(); expanding = false; }; createAnimationEndListeners(stopListener); - String expandedHeight = treeItem.getAttribute(DUI_EXPANDED_HEIGHT); - treeItem.setCssProperty(this.heightVar, expandedHeight + "px"); - treeItem.removeAttribute(DUI_COLLAPSED); + String expandedHeight = node.getAttribute(DUI_EXPANDED_HEIGHT); + node.setCssProperty(this.heightVar, expandedHeight + "px"); + node.removeAttribute(DUI_COLLAPSED); } } private void createAnimationEndListeners(EventListener stopListener) { AddEventListenerOptions addEventListenerOptions = AddEventListenerOptions.create(); addEventListenerOptions.setOnce(true); - treeItem - .element() - .addEventListener("webkitTransitionEnd", stopListener, addEventListenerOptions); - treeItem.element().addEventListener("MSTransitionEnd", stopListener, addEventListenerOptions); - treeItem.element().addEventListener("mozTransitionEnd", stopListener, addEventListenerOptions); - treeItem.element().addEventListener("oanimationend", stopListener, addEventListenerOptions); - treeItem.element().addEventListener("animationend", stopListener, addEventListenerOptions); + node.element().addEventListener("webkitTransitionEnd", stopListener, addEventListenerOptions); + node.element().addEventListener("MSTransitionEnd", stopListener, addEventListenerOptions); + node.element().addEventListener("mozTransitionEnd", stopListener, addEventListenerOptions); + node.element().addEventListener("oanimationend", stopListener, addEventListenerOptions); + node.element().addEventListener("animationend", stopListener, addEventListenerOptions); } /** @dominokit-site-ignore {@inheritDoc} */ @@ -159,9 +156,9 @@ private void createAnimationEndListeners(EventListener stopListener) { public void collapse(Element element) { if (!expanding) { collapsing = true; - treeItem.setCssProperty(this.heightVar, getActualHeight() + "px"); - boolean disableAnimation = dui_transition_none.isAppliedTo(treeItem); - treeItem.apply( + node.setCssProperty(this.heightVar, getActualHeight() + "px"); + boolean disableAnimation = dui_transition_none.isAppliedTo(node); + node.apply( self -> { if (self.isAttached()) { this.handlers.onBeforeCollapse().run(); @@ -176,11 +173,11 @@ public void collapse(Element element) { self.onAttached( (e, mutationRecord) -> { this.handlers.onBeforeCollapse().run(); - treeItem.addCss(dui_transition_none); + node.addCss(dui_transition_none); EventListener stopListener = evt -> { if (!disableAnimation) { - dui_transition_none.remove(treeItem); + dui_transition_none.remove(node); } handlers.onCollapseCompleted().run(); collapsing = false; @@ -193,13 +190,13 @@ public void collapse(Element element) { } } - private void resetParentHeight(TreeItem treeItem) { - treeItem + private void resetParentHeight(TreeNode treeNode) { + treeNode .getParent() .ifPresent( parent -> { - if (parent instanceof TreeItem) { - TreeItem parentItem = (TreeItem) parent; + if (parent instanceof TreeNode) { + TreeNode parentItem = (TreeNode) parent; parentItem.removeCssProperty(parentItem.getAttribute(DUI_EXPAND_COLLAPSE_VAR)); parent.getParent().ifPresent(treeItem1 -> resetParentHeight(parentItem)); } @@ -207,15 +204,14 @@ private void resetParentHeight(TreeItem treeItem) { } private void collapseElement(Element element) { - if (dui_transition_none.isAppliedTo(treeItem)) { - treeItem.setAttribute(DUI_COLLAPSED, "true"); - treeItem.setCssProperty(this.heightVar, treeItem.getAttribute(DUI_COLLAPSED_HEIGHT) + "px"); + if (dui_transition_none.isAppliedTo(node)) { + node.setAttribute(DUI_COLLAPSED, "true"); + node.setCssProperty(this.heightVar, node.getAttribute(DUI_COLLAPSED_HEIGHT) + "px"); } else { DomGlobal.requestAnimationFrame( timestamp -> { - treeItem.setAttribute(DUI_COLLAPSED, "true"); - treeItem.setCssProperty( - this.heightVar, treeItem.getAttribute(DUI_COLLAPSED_HEIGHT) + "px"); + node.setAttribute(DUI_COLLAPSED, "true"); + node.setCssProperty(this.heightVar, node.getAttribute(DUI_COLLAPSED_HEIGHT) + "px"); }); } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/config/TreeConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/config/TreeConfig.java index 3c90865dd..d95aa9e71 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/config/TreeConfig.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/config/TreeConfig.java @@ -18,7 +18,7 @@ import java.util.function.Supplier; import org.dominokit.domino.ui.collapsible.CollapseStrategy; import org.dominokit.domino.ui.collapsible.TreeHeightCollapseStrategy; -import org.dominokit.domino.ui.tree.TreeItem; +import org.dominokit.domino.ui.tree.TreeNode; /** * Implementations of this interface can be used to configure defaults for {@link @@ -31,10 +31,10 @@ public interface TreeConfig extends ComponentConfig { * *

Defaults to : {@code TreeHeightCollapseStrategy} * - * @param treeItem The TreeItem we are creating the strategy for. + * @param node The TreeItem we are creating the strategy for. * @return a {@code Supplier} */ - default Supplier getTreeDefaultCollapseStrategy(TreeItem treeItem) { - return () -> new TreeHeightCollapseStrategy(treeItem); + default Supplier getTreeDefaultCollapseStrategy(TreeNode node) { + return () -> new TreeHeightCollapseStrategy(node); } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasActiveNode.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasActiveNode.java new file mode 100644 index 000000000..d0c99ae14 --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasActiveNode.java @@ -0,0 +1,24 @@ +/* + * 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; + +public interface HasActiveNode, S> { + void setActiveNode(N node); + + void setActiveNode(N node, boolean silent); + + N getActiveNode(); +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasTreeRoot.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasTreeRoot.java new file mode 100644 index 000000000..5a88f2399 --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasTreeRoot.java @@ -0,0 +1,18 @@ +/* + * 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; + +public interface HasTreeRoot {} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/IsParentNode.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/IsParentNode.java new file mode 100644 index 000000000..f7156f97f --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/IsParentNode.java @@ -0,0 +1,36 @@ +/* + * 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 java.util.Optional; + +public interface IsParentNode, S> extends HasActiveNode { + RootNode getRootNode(); + + IsParentNode expandNode(); + + IsParentNode expandNode(boolean expandParent); + + TreeItemFilter getFilter(); + + Optional> getParent(); + + void removeNode(N node); + + V getValue(); + + void setValue(V value); +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/NodeIconSupplier.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/NodeIconSupplier.java new file mode 100644 index 000000000..e94c33616 --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/NodeIconSupplier.java @@ -0,0 +1,33 @@ +/* + * 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 org.dominokit.domino.ui.icons.Icon; + +/** + * An interface to provide custom icons for tree items. + * + * @param The type of data associated with each tree item. + */ +public interface NodeIconSupplier, S> { + /** + * 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(N item); +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/RootNode.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/RootNode.java new file mode 100644 index 000000000..f35f30d04 --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/RootNode.java @@ -0,0 +1,28 @@ +/* + * 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; + +public interface RootNode, S> extends HasActiveNode { + boolean isAutoCollapse(); + + boolean isAutoExpandFound(); + + NodeIconSupplier getIconSupplier(); + + default void onActiveNodeChanged(N node, S selection, boolean silent) {} + + void onDeselectionChanged(N source, S selection); +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/Tree.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/Tree.java index 46d04091f..2d79fc6d6 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/Tree.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/Tree.java @@ -15,117 +15,17 @@ */ 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; - } +public class Tree extends TreeRoot, Tree, TreeItem> { /** * 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. + * @param The type of data associated with each tree item. * @return A new Tree instance. */ - public static Tree create(String title, T value) { + public static Tree create(String title, V value) { return new Tree<>(title, value); } @@ -133,709 +33,45 @@ public static Tree create(String title, T 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. + * @param The type of data associated with each tree item. * @return A new Tree instance. */ - public static Tree create(String title) { + 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. + * @param The type of data associated with each tree item. * @return A new Tree instance. */ - public static Tree create() { + 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; - } - - public Tree appendChild(TreeItem... treeItems) { - Arrays.stream(treeItems).forEach(this::appendChild); - 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; + /** Creates a new empty tree. */ + public Tree() { + super(); } /** - * Triggers selection listeners with the provided source and selection tree items. + * Creates a new tree with the given title. * - * @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. + * @param treeTitle The title of the tree. */ - @Override - public Tree triggerSelectionListeners(TreeItem source, TreeItem selection) { - if (!this.selectionListenersPaused) { - this.selectionListeners.forEach( - listener -> listener.onSelectionChanged(Optional.ofNullable(source), selection)); - } - return this; + public Tree(String treeTitle) { + super(treeTitle); } /** - * Triggers deselection listeners with the provided source and deselected tree items. + * Creates a new tree with the given title and associated data value. * - * @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. + * @param treeTitle The title of the tree. + * @param value The data value associated with the tree. */ - @Override - public Tree triggerDeselectionListeners(TreeItem source, TreeItem selection) { - if (!this.selectionListenersPaused) { - this.deselectionListeners.forEach( - listener -> listener.onSelectionChanged(Optional.ofNullable(source), selection)); - } - return this; + public Tree(String treeTitle, V value) { + super(treeTitle, value); } /** @@ -844,46 +80,16 @@ public Tree triggerDeselectionListeners(TreeItem source, TreeItem selec * @return The currently selected tree item, or {@code null} if none is selected. */ @Override - public TreeItem getSelection() { - return this.activeTreeItem; + public TreeItem getSelection() { + return this.getActiveNode(); } - /** - * 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); + public void onActiveNodeChanged(TreeItem source, TreeItem selection, boolean silent) { + withPauseSelectionListenersToggle( + silent, + field -> { + triggerSelectionListeners(source, selection); + }); } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeItem.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeItem.java index c35919c1a..9c9277e1c 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeItem.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeItem.java @@ -15,185 +15,12 @@ */ 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.Element; -import elemental2.dom.EventListener; -import elemental2.dom.HTMLAnchorElement; -import elemental2.dom.HTMLLIElement; -import java.util.*; -import org.dominokit.domino.ui.collapsible.Collapsible; -import org.dominokit.domino.ui.config.HasComponentConfig; -import org.dominokit.domino.ui.config.TreeConfig; -import org.dominokit.domino.ui.elements.*; +import java.util.Optional; import org.dominokit.domino.ui.icons.Icon; -import org.dominokit.domino.ui.icons.StateChangeIcon; -import org.dominokit.domino.ui.icons.ToggleIcon; -import org.dominokit.domino.ui.style.WaveStyle; -import org.dominokit.domino.ui.utils.*; -/** - * Represents a tree item in a tree structure. - * - *

TreeItem is a component that can be used to build hierarchical tree structures. Each tree item - * can have child tree items, and they can be expanded or collapsed. TreeItem supports icons, - * selection, and filtering. - * - *

Usage example: - * - *

- * // Create a tree item with an icon and title
- * TreeItem treeItem = new TreeItem<>(Icon.create("folder"), "Folder 1");
- *
- * // Add child tree items
- * TreeItem childItem1 = TreeItem.create("Child 1");
- * TreeItem childItem2 = TreeItem.create("Child 2");
- * treeItem.appendChild(childItem1);
- * treeItem.appendChild(childItem2);
- * 
- * - * @see BaseDominoElement - */ -public class TreeItem extends BaseDominoElement> - implements TreeParent, - CanActivate, - CanDeactivate, - HasClickableElement, - TreeStyles, - HasComponentConfig, - HasSelectionListeners, TreeItem, TreeItem> { - - private String title; - private LIElement element; - private final AnchorElement anchorElement; - private final DivElement contentElement; - private LazyChild> itemIcon; - private final LazyChild textElement; - private final List> subItems = new LinkedList<>(); - private TreeItem activeTreeItem; - private TreeParent parent; - private final UListElement subTree; - - private T value; - - private ToggleTarget toggleTarget = ToggleTarget.ANY; - - private OriginalState originalState; - private boolean selectionListenersPaused; - private Set, ? super TreeItem>> selectionListeners = - new HashSet<>(); - private Set, ? super TreeItem>> deselectionListeners = - new HashSet<>(); - - private final EventListener anchorListener = - evt -> { - if (ToggleTarget.ANY.equals(this.toggleTarget)) { - evt.stopPropagation(); - if (isParent()) { - toggle(); - } - activateItem(); - } - }; - - private final EventListener iconListener = - evt -> { - if (ToggleTarget.ICON.equals(this.toggleTarget)) { - evt.stopPropagation(); - if (isParent()) { - toggle(); - } - activateItem(); - } - }; - - /** Constructs a new TreeItem instance. */ - private TreeItem() { - this.element = - li().addCss(dui_tree_item) - .appendChild( - anchorElement = - a().removeHref() - .addCss(dui_tree_anchor) - .appendChild(contentElement = div().addCss(dui_tree_item_content))) - .appendChild(subTree = ul().addCss(dui_tree_nav).hide()); - this.textElement = LazyChild.of(span().addCss(dui_tree_item_text), contentElement); - init(this); - - setCollapseStrategy(getConfig().getTreeDefaultCollapseStrategy(this).get()); - setAttribute(Collapsible.DUI_COLLAPSED, "true"); - } - - /** - * Constructs a new TreeItem instance with an icon and title. - * - * @param icon The icon to be displayed for the tree item. - * @param title The title of the tree item. - */ - public TreeItem(Icon icon, String title) { - this(); - setIcon(icon); - setTitle(title); - init(); - } - - /** - * Constructs a new TreeItem instance with a title. - * - * @param title The title of the tree item. - */ - public TreeItem(String title) { - this(); - setTitle(title); - init(); - } - - /** - * Constructs a new TreeItem instance with an icon. - * - * @param icon The icon to be displayed for the tree item. - */ - public TreeItem(Icon icon) { - this(); - setIcon(icon); - init(); - } - - /** - * Constructs a new TreeItem instance with a title and a value. - * - * @param title The title of the tree item. - * @param value The value associated with the tree item. - */ - public TreeItem(String title, T value) { - this(title); - this.value = value; - } - - /** - * Constructs a new TreeItem instance with an icon, title, and a value. - * - * @param icon The icon to be displayed for the tree item. - * @param title The title of the tree item. - * @param value The value associated with the tree item. - */ - public TreeItem(Icon icon, String title, T value) { - this(icon, title); - this.value = value; - } - - /** - * Constructs a new TreeItem instance with an icon and a value. - * - * @param icon The icon to be displayed for the tree item. - * @param value The value associated with the tree item. - */ - public TreeItem(Icon icon, T value) { - this(icon); - this.value = value; - } +public class TreeItem extends TreeNode, TreeItem> { /** * Creates a new TreeItem instance with the given title. @@ -203,7 +30,7 @@ public TreeItem(Icon icon, T value) { */ public static TreeItem create(String title) { TreeItem treeItem = new TreeItem<>(title); - treeItem.value = title; + treeItem.setValue(title); return treeItem; } @@ -216,7 +43,7 @@ public static TreeItem create(String title) { */ public static TreeItem create(Icon icon, String title) { TreeItem treeItem = new TreeItem<>(icon, title); - treeItem.value = title; + treeItem.setValue(title); return treeItem; } @@ -228,7 +55,7 @@ public static TreeItem create(Icon icon, String title) { */ public static TreeItem create(Icon icon) { TreeItem treeItem = new TreeItem<>(icon); - treeItem.value = ""; + treeItem.setValue(""); return treeItem; } @@ -269,242 +96,92 @@ public static TreeItem create(Icon icon, T value) { return new TreeItem<>(icon, value); } - private void init() { - addBeforeCollapseListener(() -> updateIcon(true)); - addBeforeExpandListener(() -> updateIcon(false)); - anchorElement.addClickListener(anchorListener); - applyWaves(); - } - - private void applyWaves() { - withWaves((item, waves) -> waves.setWaveStyle(WaveStyle.BLOCK)); - } - - private void activateItem() { - if (nonNull(TreeItem.this.getActiveItem())) { - TreeItem source = this.activeTreeItem; - this.activeTreeItem.deactivate(); - this.activeTreeItem = null; - triggerDeselectionListeners(source, this); - } - if (getParent().isPresent()) { - getParent().get().setActiveItem(this); - triggerSelectionListeners(this, this); - } else { - getTreeRoot().setActiveItem(this); - } - } - /** - * Appends a child tree item to this tree item. - * - * @param treeItem The child tree item to append. - * @return This tree item with the child tree item appended. - */ - public TreeItem appendChild(TreeItem treeItem) { - this.subItems.add(treeItem); - subTree.appendChild(treeItem); - treeItem.parent = this; - treeItem.setToggleTarget(this.toggleTarget); - updateIcon(isCollapsed()); - getParent() - .ifPresent( - p -> { - if (nonNull(p.getTreeRoot())) { - Tree.TreeItemIconSupplier iconSupplier = p.getTreeRoot().getIconSupplier(); - if (nonNull(iconSupplier)) { - subItems.forEach(item -> item.onSuppliedIconChanged(iconSupplier)); - } - } - }); - return this; - } - - public TreeItem appendChild(TreeItem... treeItems) { - Arrays.stream(treeItems).forEach(this::appendChild); - return this; - } - - @Override - public PrefixElement getPrefixElement() { - return PrefixElement.of(contentElement); - } - - @Override - public PostfixElement getPostfixElement() { - return PostfixElement.of(contentElement); - } - - private void updateIcon(boolean collapsed) { - if (nonNull(itemIcon) && itemIcon.isInitialized()) { - if (itemIcon.element() instanceof ToggleIcon) { - ((ToggleIcon) itemIcon.element()).toggle(); - } else if (itemIcon.element() instanceof StateChangeIcon) { - StateChangeIcon icon = (StateChangeIcon) itemIcon.element(); - if (isParent()) { - icon.setState(collapsed ? TreeItemIcon.STATE_COLLAPSED : TreeItemIcon.STATE_EXPANDED); - } else { - if (dui_active.isAppliedTo(this)) { - icon.setState(TreeItemIcon.STATE_ACTIVE); - } else { - icon.setState(TreeItemIcon.STATE_LEAF); - } - } - } - } - } - - /** - * Adds a separator to this tree item. - * - * @return This tree item with a separator added. - */ - public TreeItem addSeparator() { - subTree.appendChild(li().addCss(dui_separator)); - return this; - } - - /** - * Sets the toggle target for this tree item. + * Constructs a new TreeItem instance with an icon and title. * - * @param toggleTarget The toggle target to set. - * @return This tree item with the toggle target set. + * @param icon The icon to be displayed for the tree item. + * @param title The title of the tree item. */ - public TreeItem setToggleTarget(ToggleTarget toggleTarget) { - if (nonNull(toggleTarget)) { - - this.toggleTarget = toggleTarget; - if (ToggleTarget.ICON.equals(toggleTarget)) { - removeWaves(); - if (nonNull(itemIcon) && itemIcon.isInitialized()) { - itemIcon.get().setClickable(true).addEventListener("click", iconListener, true); - } - } else { - applyWaves(); - if (nonNull(itemIcon) && itemIcon.isInitialized()) { - itemIcon.get().setClickable(false).removeEventListener("click", iconListener); - ; - } - } - - subItems.forEach(item -> item.setToggleTarget(toggleTarget)); - } - return this; - } - - private void toggle() { - if (isParent()) { - toggleCollapse(); - } + public TreeItem(Icon icon, String title) { + super(icon, title); } /** - * Expands the tree node represented by this tree item. + * Constructs a new TreeItem instance with a title. * - * @return This tree item with the node expanded. + * @param title The title of the tree item. */ - @Override - public TreeItem expandNode() { - return show(false); + public TreeItem(String title) { + super(title); } /** - * Shows the tree node represented by this tree item. - * - * @param expandParent {@code true} to expand the parent nodes, {@code false} otherwise. - * @return This tree item with the node shown. - */ - public TreeItem show(boolean expandParent) { - if (isParent()) { - super.expand(); - } - if (expandParent) { - getParent().ifPresent(itemParent -> itemParent.expandNode(true)); - } - return this; - } - /** - * Expands the tree node represented by this tree item. If the 'expandParent' parameter is true, - * parent nodes will also be expanded. This method returns the current TreeItem instance. + * Constructs a new TreeItem instance with an icon. * - * @param expandParent True to expand parent nodes, false to expand only the current node. - * @return The current TreeItem instance. + * @param icon The icon to be displayed for the tree item. */ - @Override - public TreeItem expandNode(boolean expandParent) { - return show(expandParent); + public TreeItem(Icon icon) { + super(icon); } /** - * Toggles the collapse/expand state of the tree item. If the tree item is a parent (i.e., it has - * sub-items), it will toggle its collapse/expand state. This method returns the current TreeItem - * instance. + * Constructs a new TreeItem instance with a title and a value. * - * @return The current TreeItem instance. + * @param title The title of the tree item. + * @param value The value associated with the tree item. */ - @Override - public TreeItem toggleCollapse() { - if (isParent()) { - super.toggleCollapse(); - } - return this; + public TreeItem(String title, V value) { + super(title, value); } /** - * Returns the HTML list item element associated with this tree item. + * Constructs a new TreeItem instance with an icon, title, and a value. * - * @return The HTML list item element associated with this tree item. + * @param icon The icon to be displayed for the tree item. + * @param title The title of the tree item. + * @param value The value associated with the tree item. */ - @Override - public HTMLLIElement element() { - return element.element(); + public TreeItem(Icon icon, String title, V value) { + super(icon, title, value); } /** - * Returns the currently active (selected) tree item within the tree structure. + * Constructs a new TreeItem instance with an icon and a value. * - * @return The currently active TreeItem instance. + * @param icon The icon to be displayed for the tree item. + * @param value The value associated with the tree item. */ - @Override - public TreeItem getActiveItem() { - return activeTreeItem; + public TreeItem(Icon icon, V value) { + super(icon, value); } /** - * Returns the root of the tree structure to which this tree item belongs. + * Retrieves the selected tree item. In the context of a single tree item, this method returns the + * current tree item itself. * - * @return The root Tree instance. + * @return The selected tree item. */ @Override - public Tree getTreeRoot() { - return parent.getTreeRoot(); + public TreeItem getSelection() { + return this; } - /** - * Returns an optional parent of this tree item. If the parent exists and is a TreeItem, it - * returns an Optional containing the parent; otherwise, it returns an empty Optional. - * - * @return An Optional containing the parent TreeItem or an empty Optional. - */ @Override - public Optional> getParent() { - if (parent instanceof TreeItem) { - return Optional.of(parent); + protected void activateNode() { + if (nonNull(this.getActiveNode())) { + TreeItem source = this.activeNode; + this.activeNode.deactivate(); + this.activeNode = null; + triggerDeselectionListeners(source, getSelection()); + } + if (getParent().isPresent()) { + getParent().get().setActiveNode(this); + triggerSelectionListeners(this, getSelection()); } else { - return Optional.empty(); + getRootNode().setActiveNode(this); } } - /** - * Sets the currently active (selected) tree item within the tree structure. - * - * @param activeItem The TreeItem to set as the active item. - */ - @Override - public void setActiveItem(TreeItem activeItem) { - setActiveItem(activeItem, isSelectionListenersPaused()); - } - /** * Sets the currently active (selected) tree item within the tree structure. The 'silent' * parameter allows you to control whether triggering selection and deselection listeners should @@ -514,547 +191,26 @@ public void setActiveItem(TreeItem activeItem) { * @param silent True to suppress listener notifications; false to trigger listeners. */ @Override - public void setActiveItem(TreeItem activeItem, boolean silent) { - TreeItem source = null; + public void setActiveNode(TreeItem activeItem, boolean silent) { + TreeItem source = null; if (nonNull(activeItem)) { - if (nonNull(this.activeTreeItem) && !this.activeTreeItem.equals(activeItem)) { - source = this.activeTreeItem; - this.activeTreeItem.deactivate(); + if (nonNull(this.activeNode) && !this.activeNode.equals(activeItem)) { + source = this.activeNode; + this.activeNode.deactivate(); } - this.activeTreeItem = activeItem; - this.activeTreeItem.activate(); - getParent().ifPresent(itemParent -> itemParent.setActiveItem(this, true)); + this.activeNode = activeItem; + this.activeNode.activate(); + getParent().ifPresent(itemParent -> itemParent.setActiveNode(this, true)); if (!silent) { - triggerSelectionListeners(activeItem, activeItem); - getTreeRoot().triggerSelectionListeners(activeItem, activeItem); + triggerSelectionListeners(activeItem, getSelection()); + getRootNode().onActiveNodeChanged(activeItem, getSelection(), silent); Optional.ofNullable(source) .ifPresent( item -> { - triggerDeselectionListeners(item, activeItem); - getTreeRoot().triggerDeselectionListeners(item, activeItem); + triggerDeselectionListeners(item, getSelection()); + getRootNode().onDeselectionChanged(item, getSelection()); }); } } } - - /** - * Returns the path to this tree item. - * - * @return A list of tree items representing the path to this item, starting from the root. - */ - public List> getPath() { - List> items = new ArrayList<>(); - items.add(this); - Optional> parent = getParent(); - - while (parent.isPresent()) { - items.add((TreeItem) parent.get()); - parent = parent.get().getParent(); - } - - Collections.reverse(items); - - return items; - } - - /** - * Returns the values associated with the path to this tree item. - * - * @return A list of values representing the path to this item, starting from the root. - */ - public List getPathValues() { - List values = new ArrayList<>(); - values.add(this.getValue()); - Optional> parent = getParent(); - - while (parent.isPresent()) { - values.add(((TreeItem) parent.get()).getValue()); - parent = parent.get().getParent(); - } - - Collections.reverse(values); - - return values; - } - - /** - * Activates (selects) this tree item without activating its parent items. This method returns - * void. - */ - @Override - public void activate() { - activate(false); - } - - /** - * Activates (selects) this tree item. If 'activateParent' is true, it also activates its parent - * items in the tree structure. This method returns void. - * - * @param activateParent True to activate parent items, false to activate only this item. - */ - @Override - public void activate(boolean activateParent) { - addCss(dui_active); - if (activateParent) { - getParent().ifPresent(itemParent -> itemParent.setActiveItem(this)); - } - updateIcon(isCollapsed()); - } - - /** - * Deactivates (deselects) this tree item and any of its sub-items if it is a parent. If - * auto-collapse is enabled in the tree root, it will collapse the tree item as well. This method - * returns void. - */ - @Override - public void deactivate() { - dui_active.remove(this); - if (isParent()) { - subItems.forEach(TreeItem::deactivate); - if (getTreeRoot().isAutoCollapse()) { - collapse(); - } - } - updateIcon(isCollapsed()); - } - - /** - * Returns the clickable HTML anchor element associated with this tree item. - * - * @return The clickable HTML anchor element. - */ - @Override - public HTMLAnchorElement getClickableElement() { - return anchorElement.element(); - } - - /** - * Sets the icon for this tree item. If the 'icon' parameter is not null, it replaces the existing - * icon with the new one. This method returns the current TreeItem instance. - * - * @param icon The new icon to set. - * @return The current TreeItem instance. - */ - public TreeItem setIcon(Icon icon) { - if (nonNull(itemIcon)) { - if (itemIcon.isInitialized()) { - itemIcon.remove(); - } - } - itemIcon = LazyChild.of(icon.addCss(dui_tree_item_icon), contentElement); - itemIcon.whenInitialized( - () -> { - itemIcon.element().forEachChild(i -> i.addCss(dui_tree_item_icon)); - }); - itemIcon.whenInitialized( - () -> { - if (ToggleTarget.ICON.equals(this.toggleTarget)) { - itemIcon.element().clickable(); - } - itemIcon - .element() - .addClickListener( - evt -> { - if (ToggleTarget.ICON.equals(this.toggleTarget)) { - evt.stopPropagation(); - toggle(); - } - activateItem(); - }); - }); - itemIcon.get(); - updateIcon(isCollapsed()); - - return this; - } - - /** - * Checks if this tree item is a parent (has sub-items). - * - * @return True if this tree item is a parent, false otherwise. - */ - boolean isParent() { - return !subItems.isEmpty(); - } - - /** - * Sets the parent tree node for this tree item. - * - * @param parentTree The parent tree node. - */ - void setParent(TreeParent parentTree) { - this.parent = parentTree; - } - - /** - * Retrieves the title of this tree item. - * - * @return The title of this tree item. - */ - public String getTitle() { - return title; - } - - /** - * Filters this tree item and its children based on a search token. - * - * @param searchToken The search token to filter by. - * @return True if this tree item or any of its children match the search token, false otherwise. - */ - public boolean filter(String searchToken) { - boolean found; - if (isNull(this.originalState)) { - this.originalState = new OriginalState(isExpanded()); - } - - if (isParent()) { - found = getFilter().filter(this, searchToken) | filterChildren(searchToken); - } else { - found = getFilter().filter(this, searchToken); - } - - if (found) { - dui_hidden.remove(this); - if (isParent() && isAutoExpandFound() && isCollapsed()) { - this.expandNode(); - } - return true; - } else { - addCss(dui_hidden); - return false; - } - } - - /** - * Checks if this tree item should be automatically expanded when it's found during a search - * operation. - * - * @return True if this tree item should be automatically expanded when found, false otherwise. - */ - @Override - public boolean isAutoExpandFound() { - return getTreeRoot().isAutoExpandFound(); - } - - /** - * Clears the filter applied to this tree item and its children, restoring their original state. - */ - public void clearFilter() { - if (nonNull(originalState)) { - if (isExpanded() != originalState.expanded) { - if (this.equals(this.getTreeRoot().getActiveItem())) { - this.expandNode(); - } else { - toggleCollapse(originalState.expanded); - } - } - this.originalState = null; - } - dui_hidden.remove(this); - subItems.forEach(TreeItem::clearFilter); - } - - /** - * Filters the children of this tree item based on a search token. - * - * @param searchToken The search token to filter children by. - * @return True if any of the children match the search token, false otherwise. - */ - public boolean filterChildren(String searchToken) { - // We use the noneMatch here instead of anyMatch to make sure we are looping all children - // instead of early exit on first matching one - return subItems.stream().filter(treeItem -> treeItem.filter(searchToken)).count() > 0; - } - - /** Collapses all sub-items under this tree item recursively. */ - public void collapseAll() { - if (isParent() && !isCollapsed()) { - addCss(dui_transition_none); - subItems.forEach(TreeItem::collapseAll); - collapse(); - dui_transition_none.remove(this); - } - } - - /** Expands all sub-items under this tree item recursively. */ - public void expandAll() { - if (isParent() && isCollapsed()) { - addCss(dui_transition_none); - this.expandNode(); - subItems.forEach(TreeItem::expandAll); - dui_transition_none.remove(this); - } - } - - /** - * Retrieves the Waves effect element associated with this tree item. - * - * @return The Waves effect element. - */ - @Override - public Element getWavesElement() { - return anchorElement.element(); - } - - /** - * Checks if this tree item is a leaf (has no sub-items). - * - * @return True if this tree item is a leaf, false otherwise. - */ - public boolean isLeaf() { - return subItems.isEmpty(); - } - - /** - * Gets the list of sub-items under this tree item. - * - * @return The list of sub-items. - */ - @Override - public List> getSubItems() { - return subItems; - } - - /** - * Selects (shows and activates) this tree item. This method expands the item and activates it. - */ - public void select() { - this.show(true).activate(true); - } - - /** - * Retrieves the value associated with this tree item. - * - * @return The value associated with this tree item. - */ - public T getValue() { - return value; - } - - /** - * Sets the value associated with this tree item. - * - * @param value The new value to set. - */ - public void setValue(T value) { - this.value = value; - } - - /** - * Removes a sub-item from this tree item. - * - * @param item The sub-item to remove. - */ - @Override - public void removeItem(TreeItem item) { - if (subItems.contains(item)) { - subItems.remove(item); - } - - if (subItems.isEmpty()) { - collapse(); - } - } - - /** - * Clears all sub-items from this tree item. - * - * @return The current TreeItem instance after clearing all sub-items. - */ - public TreeItem clear() { - new ArrayList<>(subItems).forEach(TreeItem::remove); - return this; - } - - /** - * Removes this tree item from its parent. - * - * @return The current TreeItem instance after removal. - */ - @Override - public TreeItem remove() { - getParent().ifPresent(itemParent -> itemParent.removeItem(this)); - return super.remove(); - } - - /** - * Retrieves the filter associated with this tree item, which is typically inherited from its - * parent. - * - * @return The filter associated with this tree item. - */ - @Override - public TreeItemFilter> getFilter() { - return parent.getFilter(); - } - - /** - * Retrieves the text element associated with this tree item. - * - * @return The text element. - */ - public SpanElement getTextElement() { - return textElement.get(); - } - - /** - * Sets the title of this tree item and updates the text content of the associated text element. - * - * @param title The new title to set. - * @return The current TreeItem instance. - */ - public TreeItem setTitle(String title) { - this.title = title; - textElement.get().setTextContent(title); - return this; - } - - /** - * Retrieves the UListElement` that represents the sub-tree of this tree item. - * - * @return The `UListElement` representing the sub-tree. - */ - public UListElement getSubTree() { - return subTree; - } - - /** - * Handles changes in the supplied icon for this tree item and its sub-items. - * - * @param iconSupplier The icon supplier responsible for creating icons. - */ - void onSuppliedIconChanged(Tree.TreeItemIconSupplier iconSupplier) { - Icon icon = iconSupplier.createIcon(this); - if (nonNull(icon) && isNull(itemIcon)) { - setIcon(icon); - subItems.forEach(item -> item.onSuppliedIconChanged(iconSupplier)); - } - } - - /** - * Pauses selection listeners for this tree item. - * - * @return The current TreeItem instance after pausing selection listeners. - */ - @Override - public TreeItem pauseSelectionListeners() { - this.selectionListenersPaused = true; - return this; - } - - /** - * Resumes selection listeners for this tree item. - * - * @return The current TreeItem instance after resuming selection listeners. - */ - @Override - public TreeItem resumeSelectionListeners() { - this.selectionListenersPaused = false; - return this; - } - - /** - * Toggles the pause state of selection listeners. - * - * @param toggle True to pause selection listeners, false to resume them. - * @return The current TreeItem instance after toggling selection listeners. - */ - @Override - public TreeItem togglePauseSelectionListeners(boolean toggle) { - this.selectionListenersPaused = toggle; - return this; - } - - /** - * Retrieves the set of selection listeners associated with this tree item. - * - * @return The set of selection listeners. - */ - @Override - public Set, ? super TreeItem>> getSelectionListeners() { - return this.selectionListeners; - } - - /** - * Retrieves the set of deselection listeners associated with this tree item. - * - * @return The set of deselection listeners. - */ - @Override - public Set, ? super TreeItem>> - getDeselectionListeners() { - return this.deselectionListeners; - } - - /** - * Checks if selection listeners for this tree item are currently paused. - * - * @return True if selection listeners are paused, false otherwise. - */ - @Override - public boolean isSelectionListenersPaused() { - return this.selectionListenersPaused; - } - - /** - * Triggers selection listeners for this tree item when a selection event occurs. - * - * @param source The source tree item that triggered the event. - * @param selection The selected tree item. - * @return The current TreeItem instance after triggering selection listeners. - */ - @Override - public TreeItem triggerSelectionListeners(TreeItem source, TreeItem selection) { - if (!isSelectionListenersPaused()) { - this.selectionListeners.forEach( - selectionListener -> - selectionListener.onSelectionChanged(Optional.ofNullable(source), selection)); - } - return this; - } - - /** - * Triggers deselection listeners for this tree item when a deselection event occurs. - * - * @param source The source tree item that triggered the event. - * @param selection The deselected tree item. - * @return The current TreeItem instance after triggering deselection listeners. - */ - @Override - public TreeItem triggerDeselectionListeners(TreeItem source, TreeItem selection) { - if (!isSelectionListenersPaused()) { - this.deselectionListeners.forEach( - selectionListener -> - selectionListener.onSelectionChanged(Optional.ofNullable(source), selection)); - } - return this; - } - - /** - * Retrieves the selected tree item. In the context of a single tree item, this method returns the - * current tree item itself. - * - * @return The selected tree item. - */ - @Override - public TreeItem getSelection() { - return this; - } - - /** - * A private inner class representing the original state of a tree item. This state includes - * whether the tree item was expanded or collapsed. - */ - private static class OriginalState { - private boolean expanded; - - /** - * Constructs an instance of OriginalState with the specified expanded state. - * - * @param expanded True if the tree item was expanded, false if it was collapsed. - */ - public OriginalState(boolean expanded) { - this.expanded = expanded; - } - } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeNode.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeNode.java new file mode 100644 index 000000000..0f06ed48d --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeNode.java @@ -0,0 +1,895 @@ +/* + * 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.style.DisplayCss.dui_hidden; +import static org.dominokit.domino.ui.style.GenericCss.dui_active; +import static org.dominokit.domino.ui.style.GenericCss.dui_transition_none; +import static org.dominokit.domino.ui.utils.Domino.a; +import static org.dominokit.domino.ui.utils.Domino.div; +import static org.dominokit.domino.ui.utils.Domino.li; +import static org.dominokit.domino.ui.utils.Domino.span; +import static org.dominokit.domino.ui.utils.Domino.ul; + +import elemental2.dom.Element; +import elemental2.dom.EventListener; +import elemental2.dom.HTMLAnchorElement; +import elemental2.dom.HTMLLIElement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.dominokit.domino.ui.collapsible.Collapsible; +import org.dominokit.domino.ui.config.HasComponentConfig; +import org.dominokit.domino.ui.config.TreeConfig; +import org.dominokit.domino.ui.elements.AnchorElement; +import org.dominokit.domino.ui.elements.DivElement; +import org.dominokit.domino.ui.elements.LIElement; +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.StateChangeIcon; +import org.dominokit.domino.ui.icons.ToggleIcon; +import org.dominokit.domino.ui.style.WaveStyle; +import org.dominokit.domino.ui.utils.BaseDominoElement; +import org.dominokit.domino.ui.utils.HasSelectionListeners; +import org.dominokit.domino.ui.utils.LazyChild; +import org.dominokit.domino.ui.utils.PostfixElement; +import org.dominokit.domino.ui.utils.PrefixElement; +import org.dominokit.domino.ui.utils.Separator; + +public abstract class TreeNode, S> + extends BaseDominoElement + implements IsParentNode, + TreeStyles, + HasComponentConfig, + HasSelectionListeners { + + protected IsParentNode parent; + private ToggleTarget toggleTarget = ToggleTarget.ANY; + private LazyChild> nodeIcon; + private final List subNodes = new LinkedList<>(); + protected N activeNode; + + private String title; + private LIElement element; + private final AnchorElement anchorElement; + private final DivElement contentElement; + private final LazyChild textElement; + private final UListElement subTree; + + private V value; + private OriginalState originalState; + + private boolean selectionListenersPaused = false; + private Set> selectionListeners = new HashSet<>(); + private Set> deselectionListeners = new HashSet<>(); + + private final EventListener anchorListener = + evt -> { + if (ToggleTarget.ANY.equals(this.toggleTarget)) { + evt.stopPropagation(); + if (isParent()) { + toggle(); + } + activateNode(); + } + }; + + private final EventListener iconListener = + evt -> { + if (ToggleTarget.ICON.equals(this.toggleTarget)) { + evt.stopPropagation(); + if (isParent()) { + toggle(); + } + activateNode(); + } + }; + + private TreeNode() { + this.element = + li().addCss(dui_tree_item) + .appendChild( + anchorElement = + a().removeHref() + .addCss(dui_tree_anchor) + .appendChild(contentElement = div().addCss(dui_tree_item_content))) + .appendChild(subTree = ul().addCss(dui_tree_nav).hide()); + this.textElement = LazyChild.of(span().addCss(dui_tree_item_text), contentElement); + init((N) this); + + setCollapseStrategy(getConfig().getTreeDefaultCollapseStrategy(this).get()); + setAttribute(Collapsible.DUI_COLLAPSED, "true"); + } + + /** + * Constructs a new TreeItem instance with an icon and title. + * + * @param icon The icon to be displayed for the tree item. + * @param title The title of the tree item. + */ + public TreeNode(Icon icon, String title) { + this(); + setIcon(icon); + setTitle(title); + init(); + } + + /** + * Constructs a new TreeItem instance with a title. + * + * @param title The title of the tree item. + */ + public TreeNode(String title) { + this(); + setTitle(title); + init(); + } + + /** + * Constructs a new TreeItem instance with an icon. + * + * @param icon The icon to be displayed for the tree item. + */ + public TreeNode(Icon icon) { + this(); + setIcon(icon); + init(); + } + + /** + * Constructs a new TreeItem instance with a title and a value. + * + * @param title The title of the tree item. + * @param value The value associated with the tree item. + */ + public TreeNode(String title, V value) { + this(title); + this.value = value; + } + + /** + * Constructs a new TreeItem instance with an icon, title, and a value. + * + * @param icon The icon to be displayed for the tree item. + * @param title The title of the tree item. + * @param value The value associated with the tree item. + */ + public TreeNode(Icon icon, String title, V value) { + this(icon, title); + this.value = value; + } + + /** + * Constructs a new TreeItem instance with an icon and a value. + * + * @param icon The icon to be displayed for the tree item. + * @param value The value associated with the tree item. + */ + public TreeNode(Icon icon, V value) { + this(icon); + this.value = value; + } + + protected void init() { + addBeforeCollapseListener(() -> updateIcon(true)); + addBeforeExpandListener(() -> updateIcon(false)); + anchorElement.addClickListener(anchorListener); + applyWaves(); + } + + /** + * Sets the title of this tree item and updates the text content of the associated text element. + * + * @param title The new title to set. + * @return The current TreeItem instance. + */ + public N setTitle(String title) { + this.title = title; + textElement.get().setTextContent(title); + return (N) this; + } + + /** + * Retrieves the title of this tree item. + * + * @return The title of this tree item. + */ + public String getTitle() { + return title; + } + + /** + * Sets the parent tree node for this tree item. + * + * @param parentNode The parent tree node. + */ + void setParent(IsParentNode parentNode) { + this.parent = parentNode; + } + + protected abstract void activateNode(); + + /** + * Deactivates (deselects) this tree item and any of its sub-items if it is a parent. If + * auto-collapse is enabled in the tree root, it will collapse the tree item as well. This method + * returns void. + */ + public void deactivate() { + dui_active.remove(this); + if (isParent()) { + subNodes.forEach(TreeNode::deactivate); + if (getRootNode().isAutoCollapse()) { + collapse(); + } + } + updateIcon(isCollapsed()); + } + + /** + * Appends a child tree item to this tree item. + * + * @param node The child tree item to append. + * @return This tree item with the child tree item appended. + */ + public N appendChild(N node) { + this.subNodes.add(node); + subTree.appendChild(node); + node.parent = this; + node.setToggleTarget(this.toggleTarget); + updateIcon(isCollapsed()); + getParent() + .ifPresent( + p -> { + if (nonNull(p.getRootNode())) { + NodeIconSupplier iconSupplier = p.getRootNode().getIconSupplier(); + if (nonNull(iconSupplier)) { + subNodes.forEach(item -> item.onSuppliedIconChanged(iconSupplier)); + } + } + }); + return (N) this; + } + + public N appendChild(N... treeItems) { + Arrays.stream(treeItems).forEach(this::appendChild); + return (N) this; + } + + /** + * Removes a sub-item from this tree item. + * + * @param item The sub-item to remove. + */ + public void removeNode(N item) { + if (subNodes.contains(item)) { + subNodes.remove(item); + } + + if (subNodes.isEmpty()) { + collapse(); + } + } + + /** + * Clears all sub-items from this tree item. + * + * @return The current TreeItem instance after clearing all sub-items. + */ + public N clear() { + new ArrayList<>(subNodes).forEach(TreeNode::remove); + return (N) this; + } + + /** + * Removes this tree item from its parent. + * + * @return The current TreeItem instance after removal. + */ + @Override + public N remove() { + getParent().ifPresent(itemParent -> itemParent.removeNode((N) this)); + return (N) super.remove(); + } + + @Override + public PrefixElement getPrefixElement() { + return PrefixElement.of(contentElement); + } + + @Override + public PostfixElement getPostfixElement() { + return PostfixElement.of(contentElement); + } + + private void updateIcon(boolean collapsed) { + if (nonNull(nodeIcon) && nodeIcon.isInitialized()) { + if (nodeIcon.element() instanceof ToggleIcon) { + ((ToggleIcon) nodeIcon.element()).toggle(); + } else if (nodeIcon.element() instanceof StateChangeIcon) { + StateChangeIcon icon = (StateChangeIcon) nodeIcon.element(); + if (isParent()) { + icon.setState(collapsed ? TreeItemIcon.STATE_COLLAPSED : TreeItemIcon.STATE_EXPANDED); + } else { + if (dui_active.isAppliedTo(this)) { + icon.setState(TreeItemIcon.STATE_ACTIVE); + } else { + icon.setState(TreeItemIcon.STATE_LEAF); + } + } + } + } + } + + /** + * Adds a separator to this tree item. + * + * @return This tree item with a separator added. + */ + public N appendChild(Separator separator) { + subTree.appendChild(separator); + return (N) this; + } + + /** + * Returns the root of the tree structure to which this tree item belongs. + * + * @return The root Tree instance. + */ + @Override + public RootNode getRootNode() { + return parent.getRootNode(); + } + + /** + * Returns an optional parent of this tree item. If the parent exists and is a TreeItem, it + * returns an Optional containing the parent; otherwise, it returns an empty Optional. + * + * @return An Optional containing the parent TreeItem or an empty Optional. + */ + public Optional> getParent() { + if (parent instanceof TreeNode) { + return Optional.of(parent); + } else { + return Optional.empty(); + } + } + + /** + * Sets the currently active (selected) tree item within the tree structure. + * + * @param activeNode The TreeItem to set as the active item. + */ + public void setActiveNode(N activeNode) { + setActiveNode(activeNode, isSelectionListenersPaused()); + } + + /** + * Returns the path to this tree item. + * + * @return A list of tree items representing the path to this item, starting from the root. + */ + public List getPath() { + List items = new ArrayList<>(); + items.add((N) this); + Optional> parent = getParent(); + + while (parent.isPresent()) { + items.add((N) parent.get()); + parent = parent.get().getParent(); + } + + Collections.reverse(items); + + return items; + } + + /** + * Returns the values associated with the path to this tree item. + * + * @return A list of values representing the path to this item, starting from the root. + */ + public List getPathValues() { + List values = new ArrayList<>(); + values.add(this.getValue()); + Optional> parent = getParent(); + + while (parent.isPresent()) { + values.add(((N) parent.get()).getValue()); + parent = parent.get().getParent(); + } + + Collections.reverse(values); + + return values; + } + + /** + * Gets the list of sub-items under this tree item. + * + * @return The list of sub-items. + */ + public List getSubNodes() { + return subNodes; + } + + /** + * Expands (shows and activates) this tree item. This method expands the item and activates it. + */ + public void expandAndActivate() { + this.show(true).activate(true); + } + + /** + * Activates (selects) this tree item without activating its parent items. This method returns + * void. + */ + public void activate() { + activate(false); + } + + /** + * Activates (selects) this tree item. If 'activateParent' is true, it also activates its parent + * items in the tree structure. This method returns void. + * + * @param activateParent True to activate parent items, false to activate only this item. + */ + public void activate(boolean activateParent) { + addCss(dui_active); + if (activateParent) { + getParent().ifPresent(parent -> parent.setActiveNode((N) this)); + } + updateIcon(isCollapsed()); + } + + /** + * Returns the currently active (selected) tree item within the tree structure. + * + * @return The currently active TreeItem instance. + */ + public N getActiveNode() { + return activeNode; + } + + /** + * Sets the toggle target for this tree item. + * + * @param toggleTarget The toggle target to set. + * @return This tree item with the toggle target set. + */ + public TreeNode setToggleTarget(ToggleTarget toggleTarget) { + if (nonNull(toggleTarget)) { + + this.toggleTarget = toggleTarget; + if (ToggleTarget.ICON.equals(toggleTarget)) { + removeWaves(); + if (nonNull(nodeIcon) && nodeIcon.isInitialized()) { + nodeIcon.get().setClickable(true).addEventListener("click", iconListener, true); + } + } else { + applyWaves(); + if (nonNull(nodeIcon) && nodeIcon.isInitialized()) { + nodeIcon.get().setClickable(false).removeEventListener("click", iconListener); + } + } + + subNodes.forEach(item -> item.setToggleTarget(toggleTarget)); + } + return this; + } + + private void toggle() { + if (isParent()) { + toggleCollapse(); + } + } + + /** + * Checks if this tree item is a parent (has sub-items). + * + * @return True if this tree item is a parent, false otherwise. + */ + boolean isParent() { + return !subNodes.isEmpty(); + } + + /** + * Checks if this tree item is a leaf (has no sub-items). + * + * @return True if this tree item is a leaf, false otherwise. + */ + public boolean isLeaf() { + return subNodes.isEmpty(); + } + + protected void applyWaves() { + withWaves((item, waves) -> waves.setWaveStyle(WaveStyle.BLOCK)); + } + + /** + * Handles changes in the supplied icon for this tree item and its sub-items. + * + * @param iconSupplier The icon supplier responsible for creating icons. + */ + void onSuppliedIconChanged(NodeIconSupplier iconSupplier) { + Icon icon = iconSupplier.createIcon((N) this); + if (nonNull(icon) && isNull(nodeIcon)) { + setIcon(icon); + subNodes.forEach(item -> item.onSuppliedIconChanged(iconSupplier)); + } + } + + /** + * Sets the icon for this tree item. If the 'icon' parameter is not null, it replaces the existing + * icon with the new one. This method returns the current TreeItem instance. + * + * @param icon The new icon to set. + * @return The current TreeItem instance. + */ + public N setIcon(Icon icon) { + if (nonNull(nodeIcon)) { + if (nodeIcon.isInitialized()) { + nodeIcon.remove(); + } + } + nodeIcon = LazyChild.of(icon.addCss(dui_tree_item_icon), contentElement); + nodeIcon.whenInitialized( + () -> { + nodeIcon.element().forEachChild(i -> i.addCss(dui_tree_item_icon)); + }); + nodeIcon.whenInitialized( + () -> { + if (ToggleTarget.ICON.equals(this.toggleTarget)) { + nodeIcon.element().clickable(); + } + nodeIcon + .element() + .addClickListener( + evt -> { + if (ToggleTarget.ICON.equals(this.toggleTarget)) { + evt.stopPropagation(); + toggle(); + } + activateNode(); + }); + }); + nodeIcon.get(); + updateIcon(isCollapsed()); + + return (N) this; + } + + /** + * Returns the clickable HTML anchor element associated with this tree item. + * + * @return The clickable HTML anchor element. + */ + @Override + public HTMLAnchorElement getClickableElement() { + return anchorElement.element(); + } + + /** Collapses all sub-items under this tree item recursively. */ + public void collapseAll() { + if (isParent() && !isCollapsed()) { + addCss(dui_transition_none); + subNodes.forEach(TreeNode::collapseAll); + collapse(); + dui_transition_none.remove(this); + } + } + + /** Expands all sub-items under this tree item recursively. */ + public void expandAll() { + if (isParent() && isCollapsed()) { + addCss(dui_transition_none); + this.expandNode(); + subNodes.forEach(TreeNode::expandAll); + dui_transition_none.remove(this); + } + } + + /** + * Expands the tree node represented by this tree item. + * + * @return This tree item with the node expanded. + */ + @Override + public N expandNode() { + return show(false); + } + + /** + * Expands the tree node represented by this tree item. If the 'expandParent' parameter is true, + * parent nodes will also be expanded. This method returns the current TreeItem instance. + * + * @param expandParent True to expand parent nodes, false to expand only the current node. + * @return The current TreeItem instance. + */ + @Override + public N expandNode(boolean expandParent) { + return show(expandParent); + } + + /** + * Shows the tree node represented by this tree item. + * + * @param expandParent {@code true} to expand the parent nodes, {@code false} otherwise. + * @return This tree item with the node shown. + */ + public N show(boolean expandParent) { + if (isParent()) { + super.expand(); + } + if (expandParent) { + getParent().ifPresent(itemParent -> itemParent.expandNode(true)); + } + return (N) this; + } + + /** + * Clears the filter applied to this tree item and its children, restoring their original state. + */ + public void clearFilter() { + if (nonNull(originalState)) { + if (isExpanded() != originalState.expanded) { + if (this.equals(this.getRootNode().getActiveNode())) { + this.expandNode(); + } else { + toggleCollapse(originalState.expanded); + } + } + this.originalState = null; + } + dui_hidden.remove(this); + subNodes.forEach(TreeNode::clearFilter); + } + + /** + * Filters this tree item and its children based on a search token. + * + * @param searchToken The search token to filter by. + * @return True if this tree item or any of its children match the search token, false otherwise. + */ + public boolean filter(String searchToken) { + boolean found; + if (isNull(this.originalState)) { + this.originalState = new TreeNode.OriginalState(isExpanded()); + } + + if (isParent()) { + found = getFilter().filter((N) this, searchToken) | filterChildren(searchToken); + } else { + found = getFilter().filter((N) this, searchToken); + } + + if (found) { + dui_hidden.remove(this); + if (isParent() && isAutoExpandFound() && isCollapsed()) { + this.expandNode(); + } + return true; + } else { + addCss(dui_hidden); + return false; + } + } + + /** + * Filters the children of this tree item based on a search token. + * + * @param searchToken The search token to filter children by. + * @return True if any of the children match the search token, false otherwise. + */ + public boolean filterChildren(String searchToken) { + // We use the noneMatch here instead of anyMatch to make sure we are looping all children + // instead of early exit on first matching one + return subNodes.stream().filter(treeItem -> treeItem.filter(searchToken)).count() > 0; + } + + /** + * Checks if this tree item should be automatically expanded when it's found during a search + * operation. + * + * @return True if this tree item should be automatically expanded when found, false otherwise. + */ + public boolean isAutoExpandFound() { + return getRootNode().isAutoExpandFound(); + } + + /** + * Retrieves the filter associated with this tree item, which is typically inherited from its + * parent. + * + * @return The filter associated with this tree item. + */ + public TreeItemFilter getFilter() { + return parent.getFilter(); + } + + /** + * Retrieves the value associated with this tree item. + * + * @return The value associated with this tree item. + */ + public V getValue() { + return value; + } + + /** + * Sets the value associated with this tree item. + * + * @param value The new value to set. + */ + public void setValue(V value) { + this.value = value; + } + + /** + * Retrieves the Waves effect element associated with this tree item. + * + * @return The Waves effect element. + */ + @Override + public Element getWavesElement() { + return anchorElement.element(); + } + + /** + * Retrieves the text element associated with this tree item. + * + * @return The text element. + */ + public SpanElement getTextElement() { + return textElement.get(); + } + + /** + * Retrieves the UListElement` that represents the sub-tree of this tree item. + * + * @return The `UListElement` representing the sub-tree. + */ + public UListElement getSubTree() { + return subTree; + } + + /** + * Pauses selection listeners for this tree item. + * + * @return The current TreeItem instance after pausing selection listeners. + */ + @Override + public N pauseSelectionListeners() { + this.selectionListenersPaused = true; + return (N) this; + } + + /** + * Resumes selection listeners for this tree item. + * + * @return The current TreeItem instance after resuming selection listeners. + */ + @Override + public N resumeSelectionListeners() { + this.selectionListenersPaused = false; + return (N) this; + } + + /** + * Toggles the pause state of selection listeners. + * + * @param toggle True to pause selection listeners, false to resume them. + * @return The current TreeItem instance after toggling selection listeners. + */ + @Override + public N togglePauseSelectionListeners(boolean toggle) { + this.selectionListenersPaused = toggle; + return (N) this; + } + + /** + * Retrieves the set of selection listeners associated with this tree item. + * + * @return The set of selection listeners. + */ + @Override + public Set> getSelectionListeners() { + return this.selectionListeners; + } + + /** + * Retrieves the set of deselection listeners associated with this tree item. + * + * @return The set of deselection listeners. + */ + @Override + public Set> getDeselectionListeners() { + return this.deselectionListeners; + } + + /** + * Checks if selection listeners for this tree item are currently paused. + * + * @return True if selection listeners are paused, false otherwise. + */ + @Override + public boolean isSelectionListenersPaused() { + return this.selectionListenersPaused; + } + + /** + * Triggers selection listeners for this tree item when a selection event occurs. + * + * @param source The source tree item that triggered the event. + * @param selection The selected tree item. + * @return The current TreeItem instance after triggering selection listeners. + */ + @Override + public N triggerSelectionListeners(N source, S selection) { + if (!isSelectionListenersPaused()) { + this.selectionListeners.forEach( + selectionListener -> + selectionListener.onSelectionChanged(Optional.ofNullable(source), selection)); + } + return (N) this; + } + + /** + * Triggers deselection listeners for this tree item when a deselection event occurs. + * + * @param source The source tree item that triggered the event. + * @param selection The deselected tree item. + * @return The current TreeItem instance after triggering deselection listeners. + */ + @Override + public N triggerDeselectionListeners(N source, S selection) { + if (!isSelectionListenersPaused()) { + this.deselectionListeners.forEach( + selectionListener -> + selectionListener.onSelectionChanged(Optional.ofNullable(source), selection)); + } + return (N) this; + } + + @Override + public HTMLLIElement element() { + return this.element.element(); + } + + /** + * A private inner class representing the original state of a tree item. This state includes + * whether the tree item was expanded or collapsed. + */ + private static class OriginalState { + private boolean expanded; + + /** + * Constructs an instance of OriginalState with the specified expanded state. + * + * @param expanded True if the tree item was expanded, false if it was collapsed. + */ + public OriginalState(boolean expanded) { + this.expanded = expanded; + } + } +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeRoot.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeRoot.java new file mode 100644 index 000000000..51c352d5f --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeRoot.java @@ -0,0 +1,796 @@ +/* + * 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.style.DisplayCss.dui_hidden; +import static org.dominokit.domino.ui.utils.Domino.div; +import static org.dominokit.domino.ui.utils.Domino.ul; + +import elemental2.dom.HTMLDivElement; +import elemental2.dom.HTMLElement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +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.BaseDominoElement; +import org.dominokit.domino.ui.utils.ChildHandler; +import org.dominokit.domino.ui.utils.HasSelectionListeners; +import org.dominokit.domino.ui.utils.LazyChild; +import org.dominokit.domino.ui.utils.PostfixAddOn; +import org.dominokit.domino.ui.utils.Separator; + +public abstract class TreeRoot, C extends TreeRoot, S> + extends BaseDominoElement + implements TreeStyles, + IsParentNode, + RootNode, + HasSelectionListeners { + + private final List subNodes = new ArrayList<>(); + private final DivElement rootElement; + private final DivElement bodyElement; + private final UListElement subTree; + private final LazyChild headerElement; + private ToggleTarget toggleTarget = ToggleTarget.ANY; + private CollapseStrategy collapseStrategy; + private boolean autoCollapse = true; + private boolean autoExpandFound; + private LazyChild> collapseExpandAllIcon; + private NodeIconSupplier iconSupplier; + protected N activeNode; + private V value; + + private LazyChild> searchIcon; + private LazyChild search; + private TreeItemFilter filter = + (treeItem, searchToken) -> + treeItem.getTitle().toLowerCase().contains(searchToken.toLowerCase()); + + private boolean selectionListenersPaused; + private final Set> selectionListeners = new HashSet<>(); + private final Set> deselectionListeners = new HashSet<>(); + + public TreeRoot() { + this.rootElement = + div() + .addCss(dui_tree) + .appendChild( + bodyElement = + div().addCss(dui_tree_body).appendChild(subTree = ul().addCss(dui_tree_nav))); + headerElement = LazyChild.of(TreeHeader.create(), rootElement); + init((C) this); + } + + /** + * Creates a new tree with the given title. + * + * @param treeTitle The title of the tree. + */ + public TreeRoot(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 TreeRoot(String treeTitle, V value) { + this(treeTitle); + this.value = value; + } + + /** + * Sets the title of this tree. + * + * @param title The title to set. + * @return This `Tree` instance for method chaining. + */ + public C setTitle(String title) { + headerElement.get().setTitle(title); + return (C) this; + } + + /** + * Appends a child tree item to this tree. + * + * @param node The tree item to append. + * @return This Tree instance for method chaining. + */ + public C appendChild(N node) { + super.appendChild(node.element()); + node.setParent(this); + node.setToggleTarget(this.toggleTarget); + if (nonNull(collapseStrategy)) { + node.setCollapseStrategy(collapseStrategy); + } + this.subNodes.add(node); + if (nonNull(iconSupplier)) { + node.onSuppliedIconChanged(iconSupplier); + } + return (C) this; + } + + public C appendChild(N... nodes) { + Arrays.stream(nodes).forEach(this::appendChild); + return (C) 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 C setNodeIconSupplier(NodeIconSupplier iconSupplier) { + this.iconSupplier = iconSupplier; + if (nonNull(this.iconSupplier)) { + subNodes.forEach( + item -> { + item.onSuppliedIconChanged(iconSupplier); + }); + } + return (C) this; + } + + /** @deprecated use {@link #setNodeIconSupplier(NodeIconSupplier)} */ + @Deprecated + public C setTreeItemIconSupplier(NodeIconSupplier iconSupplier) { + return setNodeIconSupplier(iconSupplier); + } + + /** + * 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. + */ + public NodeIconSupplier getIconSupplier() { + return iconSupplier; + } + + /** + * Appends a separator to this tree. + * + * @return This Tree instance for method chaining. + */ + public C appendChild(Separator separator) { + super.appendChild(separator); + return (C) this; + } + + public C addSeparator() { + appendChild(Separator.create()); + return (C) this; + } + + /** + * Sets the toggle target for this tree. + * + * @param toggleTarget The toggle target to set. + * @return This `Tree` instance for method chaining. + */ + public C setToggleTarget(ToggleTarget toggleTarget) { + if (nonNull(toggleTarget)) { + subNodes.forEach(item -> item.setToggleTarget(toggleTarget)); + this.toggleTarget = toggleTarget; + } + return (C) 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 N getActiveNode() { + return activeNode; + } + + /** + * Sets the currently active tree item in this tree. The tree item will be activated, and any + * previously active item will be deactivated. + * + * @param node The tree item to set as active. + */ + @Override + public void setActiveNode(N node) { + setActiveNode(node, false); + } + + /** + * 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 RootNode getRootNode() { + return this; + } + + /** + * Returns an empty optional, as this tree does not have a parent tree. + * + * @return An empty optional. + */ + public Optional> getParent() { + return Optional.empty(); + } + + /** + * 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 node The tree item to set as active. + * @param silent {@code true} to suppress selection events, {@code false} otherwise. + */ + @Override + public void setActiveNode(N node, boolean silent) { + N source = null; + if (nonNull(this.activeNode) && !this.activeNode.equals(node)) { + source = this.activeNode; + this.activeNode.deactivate(); + } + + this.activeNode = node; + this.activeNode.activate(); + if (!silent) { + triggerSelectionListeners(node, getSelection()); + this.activeNode.triggerSelectionListeners(node, getSelection()); + Optional.ofNullable(source) + .ifPresent( + item -> { + triggerDeselectionListeners(item, getSelection()); + item.triggerDeselectionListeners(item, getSelection()); + }); + } + } + + @Override + public boolean isAutoCollapse() { + return autoCollapse; + } + + /** + * 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 C setSearchable(boolean searchable) { + if (searchable) { + + if (isNull(search)) { + search = + LazyChild.of( + Search.create(true).onSearch(TreeRoot.this::filter).onClose(this::clearFilter), + headerElement); + + search.whenInitialized( + () -> { + search + .element() + .getInputElement() + .onKeyDown( + keyEvents -> { + keyEvents.onArrowDown( + evt -> { + subNodes.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 (C) this; + } + + /** + * Sets whether this tree is foldable. + * + * @param foldingEnabled `true` to enable folding functionality, `false` otherwise. + * @return This `Tree` instance for method chaining. + */ + public C 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 (C) this; + } + + /** Expands all tree items in this tree. */ + public void expandAll() { + getSubNodes().forEach(TreeNode::expandAll); + } + + /** Collapses all tree items in this tree. */ + public void collapseAll() { + getSubNodes().forEach(TreeNode::collapseAll); + } + + /** + * 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 C setAutoCollapse(boolean autoCollapse) { + this.autoCollapse = autoCollapse; + return (C) this; + } + + /** Deactivates all tree items in this tree. */ + public void deactivateAll() { + getSubNodes().forEach(TreeNode::deactivate); + } + + /** + * Returns a list of sub-items contained within this tree. The sub-items are represented as + * instances of {@link org.dominokit.domino.ui.tree.TreeItem}. + * + * @return A list of sub-items contained within this tree. + */ + public List getSubNodes() { + return new ArrayList<>(subNodes); + } + + /** + * Enables automatic expansion of found tree items when using the search feature. + * + * @return This tree instance to allow method chaining. + */ + public C autoExpandFound() { + this.autoExpandFound = true; + return (C) 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. + */ + 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 C setAutoExpandFound(boolean autoExpandFound) { + this.autoExpandFound = autoExpandFound; + return (C) this; + } + + /** + * Sets the icon for this tree. + * + * @param icon The icon to set. + * @return This `Tree` instance for method chaining. + */ + public C setIcon(Icon icon) { + headerElement.get().setIcon(icon); + return (C) this; + } + + /** Clears the search filter applied to tree items in this tree. */ + public void clearFilter() { + subNodes.forEach(TreeNode::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) { + subNodes.forEach(treeItem -> treeItem.filter(searchToken)); + } + + /** + * Gets the current filter used for searching within the tree. + * + * @return The current filter applied to the tree items for searching. + */ + public TreeItemFilter getFilter() { + return this.filter; + } + + /** + * Sets a filter for tree items in this tree.s + * + * @param filter The filter to set. + * @return This `Tree` instance for method chaining. + */ + public C setFilter(TreeItemFilter filter) { + this.filter = filter; + return (C) this; + } + + /** + * 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 V getValue() { + return value; + } + + /** + * Sets the value associated with this tree. + * + * @param value The value to set. + */ + public void setValue(V 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 activeNodes = new ArrayList<>(); + N activeNode = getActiveNode(); + while (nonNull(activeNode)) { + activeNodes.add(activeNode); + activeNode = activeNode.getActiveNode(); + } + + return activeNodes; + } + + /** + * 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<>(); + N activeNode = getActiveNode(); + while (nonNull(activeNode)) { + activeValues.add(activeNode.getValue()); + activeNode = activeNode.getActiveNode(); + } + + 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 org.dominokit.domino.ui.tree.TreeItem#remove()} method on the + * item to detach it from the DOM. + * + * @param item The tree item to be removed. + */ + public void removeNode(N item) { + subNodes.remove(item); + item.remove(); + } + + /** + * Clears all child items from this tree. + * + * @return This `Tree` instance for method chaining. + */ + public C clear() { + subNodes.forEach(TreeNode::remove); + return (C) this; + } + + /** + * Gets the collapse strategy set for this tree. + * + * @return The collapse strategy. + */ + public CollapseStrategy getCollapseStrategy() { + return collapseStrategy; + } + + /** + * 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 C setCollapseStrategy(CollapseStrategy collapseStrategy) { + getSubNodes().forEach(tTreeItem -> setCollapseStrategy(collapseStrategy)); + this.collapseStrategy = collapseStrategy; + return (C) this; + } + + /** + * 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 C withHeader(ChildHandler handler) { + handler.apply((C) this, headerElement.get()); + return (C) this; + } + + /** + * 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 C expandNode(boolean expandParent) { + return (C) this; + } + + /** + * Expands the node within the tree. This method has no effect on the root tree. + * + * @return A reference to this tree. + */ + @Override + public C expandNode() { + return (C) 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 C pauseSelectionListeners() { + this.selectionListenersPaused = true; + return (C) 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 C resumeSelectionListeners() { + this.selectionListenersPaused = false; + return (C) 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 C togglePauseSelectionListeners(boolean toggle) { + this.selectionListenersPaused = toggle; + return (C) this; + } + + /** + * Gets the set of selection listeners registered with the tree. + * + * @return A set containing selection listeners. + */ + @Override + public Set> getSelectionListeners() { + return this.selectionListeners; + } + + /** + * Gets the set of deselection listeners registered with the tree. + * + * @return A set containing deselection listeners. + */ + @Override + public Set> 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 C triggerSelectionListeners(N source, S selection) { + if (!this.selectionListenersPaused) { + this.selectionListeners.forEach( + listener -> listener.onSelectionChanged(Optional.ofNullable(source), selection)); + } + return (C) 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 C triggerDeselectionListeners(N source, S selection) { + if (!this.selectionListenersPaused) { + this.deselectionListeners.forEach( + listener -> listener.onSelectionChanged(Optional.ofNullable(source), selection)); + } + return (C) this; + } + + @Override + public void onDeselectionChanged(N source, S selection) { + triggerDeselectionListeners(source, selection); + } + + @Override + public HTMLElement getAppendTarget() { + return subTree.element(); + } + + @Override + public HTMLDivElement element() { + return rootElement.element(); + } +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BaseDominoElement.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BaseDominoElement.java index 1bf372e77..78fc14a20 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BaseDominoElement.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BaseDominoElement.java @@ -2772,6 +2772,17 @@ public T setDisabled(boolean disabled) { } } + /** + * Sets the enabled state of this element. + * + * @param enabled {@code true} to enable the element, {@code false} to disable it. + * @return The modified DOM element. + */ + @Editor.Ignore + public T setEnabled(boolean enabled) { + return setDisabled(!enabled); + } + /** * Elevates this element to the specified elevation level. * diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/HasSelectables.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/HasSelectables.java new file mode 100644 index 000000000..d364df65b --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/HasSelectables.java @@ -0,0 +1,20 @@ +/* + * 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.utils; + +public interface HasSelectables { + void onSelectionChanged(T source, S selection, boolean silent); +} \ No newline at end of file diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/HasSelectionListeners.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/HasSelectionListeners.java index 8335bf8cc..8e179b37c 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/HasSelectionListeners.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/HasSelectionListeners.java @@ -57,13 +57,27 @@ default T addDeselectionListener(SelectionListener selecti * * @param listener The selection and deselection listener to be added. * @return The component with the added listener. + * @deprecated use {@link #addSelectionChangeListener(SelectionListener)} */ + @Deprecated default T addSelectionDeselectionListener(SelectionListener listener) { addDeselectionListener(listener); addSelectionListener(listener); return (T) this; } + /** + * Adds a selection and deselection listener to the component. + * + * @param listener The selection and deselection listener to be added. + * @return The component with the added listener. + */ + default T addSelectionChangeListener(SelectionListener listener) { + addDeselectionListener(listener); + addSelectionListener(listener); + return (T) this; + } + /** * Removes a selection listener from the component. * diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/Registry.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/Registry.java new file mode 100644 index 000000000..7982c5cbf --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/Registry.java @@ -0,0 +1,41 @@ +/* + * 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.utils; + +import java.util.HashSet; +import java.util.Set; + +public class Registry { + + private Set records = new HashSet<>(); + + public void register(RegistryRecord record) { + record.bind(this); + this.records.add(record.getRecord()); + } + + public void remove(T record) { + records.remove(record); + } + + public boolean contains(T record) { + return records.contains(record); + } + + public Set records() { + return new HashSet<>(records); + } +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/RegistryRecord.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/RegistryRecord.java new file mode 100644 index 000000000..a90017088 --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/RegistryRecord.java @@ -0,0 +1,42 @@ +/* + * 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.utils; + +public class RegistryRecord { + + private final T record; + private Registry registry; + + public static RegistryRecord of(T record) { + return new RegistryRecord<>(record); + } + + public RegistryRecord(T record) { + this.record = record; + } + + void bind(Registry registry) { + this.registry = registry; + } + + T getRecord() { + return record; + } + + public final void remove() { + this.registry.remove(record); + } +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/TreeParent.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/TreeParent.java deleted file mode 100644 index 8b4f9ea7a..000000000 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/TreeParent.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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.utils; - -import java.util.List; -import java.util.Optional; -import org.dominokit.domino.ui.elements.UListElement; -import org.dominokit.domino.ui.tree.Tree; -import org.dominokit.domino.ui.tree.TreeItem; -import org.dominokit.domino.ui.tree.TreeItemFilter; - -/** - * The {@code TreeParent} interface represents a parent node in a tree structure. - * - * @param The type of data associated with tree items. - */ -public interface TreeParent { - - /** - * Gets the currently active tree item within this parent. - * - * @return The active tree item, or {@code null} if none is active. - */ - TreeItem getActiveItem(); - - /** - * Sets the active tree item within this parent. - * - * @param activeItem The tree item to set as active. - */ - void setActiveItem(TreeItem activeItem); - - /** - * Sets the active tree item within this parent and optionally suppresses events. - * - * @param activeItem The tree item to set as active. - * @param silent {@code true} to suppress events, {@code false} otherwise. - */ - void setActiveItem(TreeItem activeItem, boolean silent); - - /** - * Gets the root of the tree structure to which this parent belongs. - * - * @return The root tree. - */ - Tree getTreeRoot(); - - /** - * Checks if auto-expand of found items is enabled. - * - * @return {@code true} if auto-expand is enabled, {@code false} otherwise. - */ - boolean isAutoExpandFound(); - - /** - * Expands the current node. - * - * @return This tree parent after expansion. - */ - TreeParent expandNode(); - - /** - * Expands the current node and optionally expands its parent node. - * - * @param expandParent {@code true} to expand the parent, {@code false} otherwise. - * @return This tree parent after expansion. - */ - TreeParent expandNode(boolean expandParent); - - /** Activates the current tree parent. */ - void activate(); - - /** - * Activates the current tree parent and optionally activates its parent node. - * - * @param activateParent {@code true} to activate the parent, {@code false} otherwise. - */ - void activate(boolean activateParent); - - /** - * Gets the parent tree parent, if one exists. - * - * @return An optional containing the parent tree parent, or an empty optional if none exists. - */ - Optional> getParent(); - - /** - * Removes the specified tree item from this tree parent. - * - * @param item The tree item to remove. - */ - void removeItem(TreeItem item); - - /** - * Gets a list of sub-items contained within this tree parent. - * - * @return A list of sub-items. - */ - List> getSubItems(); - - /** - * Gets the filter used to filter tree items within this parent. - * - * @return The tree item filter. - */ - TreeItemFilter> getFilter(); - - /** - * Gets the underlying sub-tree element of this parent. - * - * @return The sub-tree element. - */ - UListElement getSubTree(); - - /** - * Gets the value associated with this tree parent. - * - * @return The value. - */ - T getValue(); -}