All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.patternfly.component.tree.TreeView Maven / Gradle / Ivy

There is a newer version: 0.2.11
Show newest version
/*
 *  Copyright 2023 Red Hat
 *
 *  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
 *
 *      https://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.patternfly.component.tree;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.gwtproject.event.shared.HandlerRegistration;
import org.jboss.elemento.Attachable;
import org.jboss.elemento.HTMLContainerBuilder;
import org.patternfly.component.BaseComponent;
import org.patternfly.component.ComponentType;
import org.patternfly.component.HasItems;
import org.patternfly.core.Aria;
import org.patternfly.handler.MultiSelectHandler;
import org.patternfly.handler.SelectHandler;
import org.patternfly.handler.ToggleHandler;
import org.patternfly.style.Classes;

import elemental2.dom.Element;
import elemental2.dom.Event;
import elemental2.dom.HTMLElement;
import elemental2.dom.HTMLUListElement;
import elemental2.dom.KeyboardEvent;
import elemental2.dom.MutationRecord;
import elemental2.dom.ScrollIntoViewOptions;
import elemental2.promise.Promise;

import static elemental2.dom.DomGlobal.document;
import static java.util.Collections.emptyList;
import static java.util.Collections.reverse;
import static org.jboss.elemento.Elements.div;
import static org.jboss.elemento.Elements.removeChildrenFrom;
import static org.jboss.elemento.Elements.ul;
import static org.jboss.elemento.EventType.bind;
import static org.jboss.elemento.EventType.keydown;
import static org.patternfly.component.tree.TreeViewType.default_;
import static org.patternfly.component.tree.TreeViewType.selectableItems;
import static org.patternfly.core.Attributes.role;
import static org.patternfly.core.Roles.tree;
import static org.patternfly.style.Classes.component;
import static org.patternfly.style.Classes.list;
import static org.patternfly.style.Classes.node;
import static org.patternfly.style.Classes.treeView;
import static org.patternfly.style.Modifiers.toggleModifier;

/**
 * A tree view is a structure that displays data in a hierarchical view. It can be used in a
 * {@linkplain org.patternfly.component.menu.Dropdown dropdown}, {@linkplain org.patternfly.component.drawer.Drawer drawer},
 * primary-detail, {@linkplain org.patternfly.component.modal.Modal modal}, or
 * {@linkplain org.patternfly.component.wizard.Wizard wizard}.
 *
 * @see https://www.patternfly.org/components/tree-view
 */
public class TreeView extends BaseComponent implements
        HasItems,
        Attachable {

    // ------------------------------------------------------ factory

    public static TreeView treeView() {
        return new TreeView(default_);
    }

    public static TreeView treeView(TreeViewType type) {
        return new TreeView(type);
    }

    // ------------------------------------------------------ instance

    final TreeViewType type;
    private final LinkedHashMap items;
    private final HTMLContainerBuilder ul;
    private final List> toggleHandler;
    private final List> selectHandler;
    private final List> multiSelectHandler;
    Supplier icon;
    Supplier expandedIcon;
    private HandlerRegistration keyHandler;

    TreeView(TreeViewType type) {
        super(ComponentType.TreeView, div().css(component(treeView)).element());
        this.type = type;
        this.items = new LinkedHashMap<>();
        this.toggleHandler = new ArrayList<>();
        this.selectHandler = new ArrayList<>();
        this.multiSelectHandler = new ArrayList<>();
        this.icon = null;
        this.expandedIcon = null;

        add(ul = ul().css(component(treeView, list)).attr(role, tree));
        Attachable.register(this, this);
    }

    @Override
    public void attach(MutationRecord mutationRecord) {
        traverseItems(this, treeViewItem -> treeViewItem.finishDOM(this));
        if (items.values().iterator().hasNext()) {
            TreeViewItem item = items.values().iterator().next();
            if (item.tabElement != null) {
                item.tabElement.tabIndex = 0;
            }
        }
        keyHandler = bind(element(), keydown, event -> {
            switch (type) {
                case default_:
                    handleKeys(event);
                    break;
                case selectableItems:
                case checkboxes:
                    handleKeysSelectable(event);
                    break;
            }
        });
    }

    @Override
    public void detach(MutationRecord mutationRecord) {
        if (keyHandler != null) {
            keyHandler.removeHandler();
        }
    }

    // ------------------------------------------------------ add

    @Override
    public TreeView add(TreeViewItem item) {
        items.put(item.identifier(), item);
        item.finishDOM(this);
        ul.add(item);
        return this;
    }

    // ------------------------------------------------------ builder

    /** Same as {@linkplain #guides(boolean) guides(true)} */
    public TreeView guides() {
        return guides(true);
    }

    /** Adds/removes {@linkplain Classes#modifier(String) modifier(guides)} */
    public TreeView guides(boolean guides) {
        return toggleModifier(that(), element(), Classes.guides, guides);
    }

    public TreeView icon(Supplier icon) {
        this.icon = icon;
        return this;
    }

    public TreeView expandedIcon(Supplier icon) {
        this.expandedIcon = icon;
        return this;
    }

    @Override
    public TreeView that() {
        return this;
    }

    // ------------------------------------------------------ aria

    public TreeView ariaLabel(String label) {
        return aria(Aria.label, label);
    }

    // ------------------------------------------------------ events

    public TreeView onToggle(ToggleHandler toggleHandler) {
        this.toggleHandler.add(toggleHandler);
        return this;
    }

    public TreeView onSelect(SelectHandler selectHandler) {
        this.selectHandler.add(selectHandler);
        return this;
    }

    public TreeView onMultiSelect(MultiSelectHandler selectHandler) {
        this.multiSelectHandler.add(selectHandler);
        return this;
    }

    // ------------------------------------------------------ api

    public Promise> load(String identifier) {
        return load(findItem(identifier, items));
    }

    public Promise> load(TreeViewItem item) {
        if (item != null) {
            return item.load();
        } else {
            return Promise.resolve(emptyList());
        }
    }

    public void toggle(TreeViewItem item) {
        toggle(item, true);
    }

    public void toggle(TreeViewItem item, boolean fireEvent) {
        if (item != null) {
            item.toggle(fireEvent);
            if (fireEvent) {
                toggleHandler.forEach(th -> th.onToggle(new Event(""), item, item.expanded()));
            }
        }
    }

    public void select(String identifier) {
        select(findItem(identifier, items), true, true);
    }

    public void select(TreeViewItem item) {
        select(item, true, true);
    }

    public void select(TreeViewItem item, boolean selected) {
        select(item, selected, true);
    }

    public void select(TreeViewItem item, boolean selected, boolean fireEvent) {
        if (item != null) {
            if (type == default_ || type == selectableItems) {
                // unselect all items
                traverseItems(this, itm -> itm.markSelected(type, false));
            }
            // select specified item
            item.markSelected(type, selected);
            if (fireEvent) {
                selectHandler.forEach(sh -> sh.onSelect(new Event(""), item, selected));
                if (!multiSelectHandler.isEmpty()) {
                    multiSelectHandler.forEach(msh -> msh.onSelect(new Event(""), this, selectedItems()));
                }
            }
            // collapse items from root to item
            List parents = parents(item);
            reverse(parents);
            for (TreeViewItem treeViewItem : parents) {
                treeViewItem.expand(false);
            }

            ScrollIntoViewOptions options = ScrollIntoViewOptions.create();
            options.setBlock("nearest");
            options.setInline("nearest");
            item.contentElement.scrollIntoView(options);
        }
    }

    public List selectedItems() {
        List selected = new ArrayList<>();
        traverseItems(this, itm -> {
            if (itm.selected()) {
                selected.add(itm);
            }
        });
        return selected;
    }

    public void reset() {
        traverseItems(this, TreeViewItem::reset);
    }

    public TreeViewItem findItem(String identifier) {
        return findItem(identifier, items);
    }

    @Override
    public Iterator iterator() {
        return items.values().iterator();
    }

    @Override
    public int size() {
        return items.size();
    }

    @Override
    public boolean isEmpty() {
        return items.isEmpty();
    }

    @Override
    public void clear() {
        removeChildrenFrom(ul);
        items.clear();
    }

    // ------------------------------------------------------ internal

    /**
     * Traverses the tree structure of TreeViewItems and applies the provided code to each item. This method recursively
     * traverses the items and their children.
     *
     * @param items the TreeViewItems to traverse
     * @param code  the Consumer that will be applied to each TreeViewItem
     */
    static void traverseItems(HasItems items, Consumer code) {
        for (TreeViewItem item : items) {
            code.accept(item);
            traverseItems(item, code);
        }
    }

    private TreeViewItem findItem(String id, LinkedHashMap items) {
        TreeViewItem treeViewItem = items.get(id);
        if (treeViewItem == null) {
            for (Map.Entry entry : items.entrySet()) {
                treeViewItem = findItem(id, entry.getValue().items);
                if (treeViewItem != null) {
                    break;
                }
            }
        }
        return treeViewItem;
    }

    private List parents(TreeViewItem item) {
        List parents = new ArrayList<>();
        addParent(item, parents);
        return parents;
    }

    private void addParent(TreeViewItem item, List parents) {
        if (item != null && item.parent != null) {
            parents.add(item.parent);
            addParent(item.parent, parents);
        }
    }

    private void handleKeys(KeyboardEvent event) {
        HTMLElement target = (HTMLElement) event.target;
        if (!target.classList.contains(component(treeView, node))) {
            return;
        }
        Element activeElement = document.activeElement;
    }

    private void handleKeysSelectable(KeyboardEvent event) {
        HTMLElement target = (HTMLElement) event.target;
        if (!target.classList.contains(component(treeView, node))) {
            return;
        }
        Element activeElement = document.activeElement;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy