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

org.patternfly.component.chip.ChipGroup 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.chip;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.jboss.elemento.Attachable;
import org.jboss.elemento.ButtonType;
import org.jboss.elemento.Elements;
import org.jboss.elemento.Id;
import org.patternfly.component.BaseComponent;
import org.patternfly.component.Closeable;
import org.patternfly.component.ComponentType;
import org.patternfly.component.HasItems;
import org.patternfly.component.button.Button;
import org.patternfly.component.tooltip.TooltipToggle;
import org.patternfly.core.Aria;
import org.patternfly.core.Roles;
import org.patternfly.handler.CloseHandler;
import org.patternfly.style.Classes;

import elemental2.dom.Element;
import elemental2.dom.Event;
import elemental2.dom.HTMLDivElement;
import elemental2.dom.HTMLElement;
import elemental2.dom.HTMLLIElement;
import elemental2.dom.MutationRecord;

import static org.jboss.elemento.Elements.div;
import static org.jboss.elemento.Elements.failSafeRemoveFromParent;
import static org.jboss.elemento.Elements.insertBefore;
import static org.jboss.elemento.Elements.li;
import static org.jboss.elemento.Elements.removeChildrenFrom;
import static org.jboss.elemento.Elements.setVisible;
import static org.jboss.elemento.Elements.span;
import static org.jboss.elemento.Elements.ul;
import static org.jboss.elemento.EventType.click;
import static org.patternfly.component.button.Button.button;
import static org.patternfly.core.Aria.label;
import static org.patternfly.core.Aria.labelledBy;
import static org.patternfly.core.Attributes.role;
import static org.patternfly.core.Roles.group;
import static org.patternfly.handler.CloseHandler.fireEvent;
import static org.patternfly.handler.CloseHandler.shouldClose;
import static org.patternfly.icon.IconSets.fas.timesCircle;
import static org.patternfly.style.Classes.chipGroup;
import static org.patternfly.style.Classes.close;
import static org.patternfly.style.Classes.component;
import static org.patternfly.style.Classes.item;
import static org.patternfly.style.Classes.list;
import static org.patternfly.style.Classes.main;
import static org.patternfly.style.Classes.modifier;
import static org.patternfly.style.Classes.overflow;

public class ChipGroup extends BaseComponent implements
        HasItems,
        Closeable,
        Attachable {

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

    public static ChipGroup chipGroup() {
        return new ChipGroup(null);
    }

    public static ChipGroup chipGroup(String category) {
        return new ChipGroup(category);
    }

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

    private static final int DEFAULT_NUM_CHIPS = 3;
    private static final String REMAINING_PLACEHOLDER = "${remaining}";

    private final HTMLElement listElement;
    private final Map items;
    private final List> closeHandler;
    private boolean expanded;
    private int numChips;
    private String collapsedText;
    private String expandedText;
    private Chip overflowChip;
    private Button closeButton;
    private HTMLElement categoryElement;
    private HTMLElement overflowItem;
    private TooltipToggle tooltipToggle;

    ChipGroup(String category) {
        super(ComponentType.ChipGroup, div().css(component(chipGroup)).attr(role, group).element());
        this.items = new LinkedHashMap<>();
        this.closeHandler = new ArrayList<>();
        this.expanded = false;
        this.numChips = DEFAULT_NUM_CHIPS;
        this.collapsedText = REMAINING_PLACEHOLDER + " more";
        this.expandedText = "Show less";

        add(div().css(component(chipGroup, main))
                .add(listElement = ul().css(component(chipGroup, list))
                        .attr(role, Roles.list)
                        .element()));
        if (category != null) {
            category(category);
        } else {
            aria(Classes.label, "Chip group category");
        }
        storeComponent();
        Attachable.register(this, this);
    }

    @Override
    public void attach(MutationRecord mutationRecord) {
        if (tooltipToggle != null) {
            tooltipToggle.eval();
        }
    }

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

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

    @Override
    public ChipGroup add(Chip chip) {
        items.put(chip.identifier(), chip);
        HTMLLIElement itemElement = li().css(component(chipGroup, list, item))
                .add(chip)
                .element();
        if (overflowItem != null) {
            insertBefore(itemElement, overflowItem);
        } else {
            listElement.appendChild(itemElement);
        }

        overflow();
        return this;
    }

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

    public ChipGroup category(String category) {
        if (categoryElement == null) {
            String categoryId = Id.unique(componentType().id, "category");
            css(modifier(Classes.category));
            categoryElement = span().css(component(chipGroup, Classes.label))
                    .id(categoryId)
                    .element();
            insertBefore(categoryElement, listElement);
            aria(labelledBy, categoryId);
            tooltipToggle = new TooltipToggle(categoryElement);
        }
        categoryElement.textContent = category;
        return this;
    }

    public ChipGroup numChips(int numChips) {
        if (numChips > 0) {
            this.numChips = numChips;
        }
        return this;
    }

    public ChipGroup closable() {
        return closable(null);
    }

    public ChipGroup closable(CloseHandler closeHandler) {
        this.closeHandler.add(closeHandler);

        String closeId = Id.unique(componentType().id, "close");
        String labelledBy = closeId;
        if (categoryElement != null) {
            labelledBy += " " + categoryElement.id;
        }

        add(div().css(component(chipGroup, close))
                .add(closeButton = button().icon(timesCircle()).plain()
                        .id(closeId)
                        .aria(label, "Close chip group")
                        .aria(Aria.labelledBy, labelledBy)
                        .on(click, event -> close(event, true))));
        return this;
    }

    public ChipGroup collapsedText(String collapsedText) {
        this.collapsedText = collapsedText;
        return this;
    }

    public ChipGroup expandedText(String expandedText) {
        this.expandedText = expandedText;
        return this;
    }

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

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

    @Override
    public ChipGroup aria(String name, String value) {
        if (name.equals(label)) {
            // set label also on the list element
            listElement.setAttribute(label, value);
        }
        return super.aria(name, value);
    }

    public ChipGroup ariaCloseLabel(String label) {
        if (closeButton != null) {
            closeButton.aria(Aria.label, label);
        }
        return this;
    }

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

    @Override
    public ChipGroup onClose(CloseHandler closeHandler) {
        if (closeHandler != null) {
            this.closeHandler.add(closeHandler);
        }
        return this;
    }

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

    @Override
    public void close(Event event, boolean fireEvent) {
        if (shouldClose(this, closeHandler, event, fireEvent)) {
            failSafeRemoveFromParent(this);
            fireEvent(this, closeHandler, event, fireEvent);
        }
    }

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

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

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

    public void clear() {
        removeChildrenFrom(listElement);
        items.clear();
        overflowItem = null;
        overflowChip = null;
    }

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

    void close(Chip chip) {
        items.remove(chip.identifier());
        Element element = chip.element().parentElement;
        if (element != null) {
            failSafeRemoveFromParent(element);
        } else {
            failSafeRemoveFromParent(chip);
        }
        overflow();
    }

    private void overflow() {
        if (items.size() > numChips && overflowItem == null && overflowChip == null) {
            String identifier = Id.unique(componentType().id, "overflow");
            overflowItem = li().css(component(chipGroup, list, item))
                    .add(overflowChip = new Chip(Elements.button(ButtonType.button), identifier, "")
                            .readonly()
                            .css(modifier(overflow))
                            .on(click, event -> toggle()))
                    .element();
            listElement.appendChild(overflowItem);
        }
        int index = 0;
        for (Chip chip : items.values()) {
            setVisible((HTMLElement) chip.element().parentElement, expanded || index < numChips);
            index++;
        }
        if (overflowItem != null && overflowChip != null) {
            setVisible(overflowItem, items.size() > numChips);
            overflowChip.textElement.textContent = expanded ? expandedText
                    : collapsedText.replace(REMAINING_PLACEHOLDER, String.valueOf(items.size() - numChips));
        }
    }

    private void toggle() {
        expanded = !expanded;
        overflow();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy