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

org.dominokit.domino.ui.forms.Select Maven / Gradle / Ivy

package org.dominokit.domino.ui.forms;

import elemental2.dom.*;
import elemental2.dom.EventListener;
import org.dominokit.domino.ui.dropdown.DropDownMenu;
import org.dominokit.domino.ui.dropdown.DropDownPosition;
import org.dominokit.domino.ui.dropdown.DropdownAction;
import org.dominokit.domino.ui.dropdown.DropdownActionsGroup;
import org.dominokit.domino.ui.icons.Icons;
import org.dominokit.domino.ui.icons.MdiIcon;
import org.dominokit.domino.ui.modals.ModalBackDrop;
import org.dominokit.domino.ui.style.Color;
import org.dominokit.domino.ui.style.Styles;
import org.dominokit.domino.ui.utils.DominoElement;
import org.dominokit.domino.ui.utils.Focusable;
import org.dominokit.domino.ui.utils.HasChangeHandlers;
import org.dominokit.domino.ui.utils.IsReadOnly;
import org.jboss.gwt.elemento.core.IsElement;

import java.util.*;
import java.util.stream.Collectors;

import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.jboss.gwt.elemento.core.Elements.*;

public class Select extends BasicFormElement, T> implements Focusable>, IsReadOnly>, HasChangeHandlers, T> {

    private static final String CLICK_EVENT = "click";
    private static final String FOCUSED = "focused";

    private DominoElement container = DominoElement.of(div().css("form-group"));
    private DominoElement formLine = DominoElement.of(div().css("form-line"));
    private DominoElement formControl = DominoElement.of(div().css("form-control"));
    private DominoElement leftAddonContainer = DominoElement.of(div().css("input-addon-container"));
    private DominoElement rightAddonContainer = DominoElement.of(div().css("input-addon-container"));
    private DominoElement leftAddon;
    private DominoElement rightAddon;

    private DominoElement buttonElement = DominoElement.of(button().attr("type","button").css("select-button"));
    private DominoElement buttonValueContainer = DominoElement.of(span().css("select-value", Styles.ellipsis_text));
    private DominoElement iconContainer = DominoElement.of(div().css(Styles.pull_right));
    private DropDownMenu optionsMenu = DropDownMenu.create(buttonElement).styler(style1 -> style1.add("select-option-menu"));
    private DominoElement labelElement = DominoElement.of(label().css("form-label", "select-label"));


    private LinkedList> options = new LinkedList<>();
    private SelectOption selectedOption;
    private List> selectionHandlers = new ArrayList<>();
    private SelectionHandler autoValidationHandler;

    private List> changeHandlers = new ArrayList<>();

    private Color focusColor = Color.BLUE;
    private boolean readOnly;
    private boolean searchable;
    private boolean valid = true;
    private boolean focused;

    public Select() {
        initListeners();
        buttonElement
                .appendChild(buttonValueContainer)
                .appendChild(iconContainer);
        formControl
                .appendChild(buttonElement)
                .appendChild(labelElement);
        formLine.appendChild(formControl);
        container.appendChild(leftAddonContainer);
        container.appendChild(formLine.asElement());
        container.appendChild(rightAddonContainer);
        init(this);

        dropdown();

        setSearchable(true);
    }

    public Select(String label) {
        this();
        setLabel(label);
    }

    public Select(List> options) {
        this("", options);
    }

    public Select(String label, List> options) {
        this(label);
        options.forEach(this::appendChild);
    }

    private void initListeners() {
        EventListener clickListener = evt -> {
            open();
            evt.stopPropagation();
        };
        buttonElement.addEventListener(CLICK_EVENT, clickListener);
        labelElement.addEventListener(CLICK_EVENT, clickListener);
        buttonElement.addEventListener("focus", evt -> doFocus());
        buttonElement.addEventListener("blur", evt -> doUnfocus());
        optionsMenu.addCloseHandler(this::doFocus);
    }

    public Select open() {
        if (isEnabled() && !isReadOnly()) {
            optionsMenu.closeAllMenus();
            doOpen();
        }
        return this;
    }

    private void doOpen() {
        optionsMenu.open();
        optionsMenu.styler(style -> style.setWidth(formControl.getBoundingClientRect().width + "px"));
    }

    public void close() {
        optionsMenu.close();
    }

    public static  Select create() {
        return new Select<>();
    }

    public static  Select create(String label) {
        return new Select<>(label);
    }

    public static  Select create(String label, List> options) {
        return new Select<>(label, options);
    }

    public static  Select create(List> options) {
        return new Select<>(options);
    }

    public Select divider() {
        optionsMenu.separator();
        return this;
    }

    public Select addGroup(SelectOptionGroup group) {
        DropdownActionsGroup dropdownActionsGroup = DropdownActionsGroup.create(group.getTitleElement());
        for (SelectOption option : group.getOptions()) {
            dropdownActionsGroup.appendChild(asDropDownAction(option));
        }
        options.addAll(group.getOptions());
        optionsMenu.addGroup(dropdownActionsGroup);
        return this;
    }

    public Select addOptions(List> options) {
        options.forEach(this::appendChild);
        return this;
    }

    /**
     * @deprecated use {@link #appendChild(SelectOption)}
     */
    @Deprecated
    public Select addOption(SelectOption option) {
        return appendChild(option);
    }

    public Select appendChild(SelectOption option) {
        options.add(option);
        appendOptionValue(option);
        return this;
    }

    private void doSelectOption(SelectOption option) {
        if (isEnabled()) {
            select(option);
            close();
        }
    }

    private void appendOptionValue(SelectOption option) {
        optionsMenu.appendChild(asDropDownAction(option));
    }

    private DropdownAction asDropDownAction(SelectOption option) {
        return DropdownAction.create(option.getDisplayValue(), option.asElement())
                .addSelectionHandler(value -> doSelectOption(option));
    }

    public Select selectAt(int index) {
        return selectAt(index, false);
    }

    public Select selectAt(int index, boolean silent) {
        if (index < options.size() && index >= 0)
            select(options.get(index), silent);
        return this;
    }

    public SelectOption getOptionAt(int index) {
        if (index < options.size() && index >= 0)
            return options.get(index);
        return null;
    }

    public List> getOptions() {
        return options;
    }

    public Select select(SelectOption option) {
        return select(option, false);
    }

    public Select select(SelectOption option, boolean silent) {
        if (selectedOption != null)
            if (!option.isEqualNode(selectedOption.asElement()))
                selectedOption.deselect();
        labelElement.style().add(FOCUSED);
        this.selectedOption = option;
        option.select();
        buttonValueContainer.setTextContent(option.getDisplayValue());
        if (!silent)
            onSelection(option);
        return this;
    }

    public boolean isSelected() {
        return !isEmpty();
    }

    private void onSelection(SelectOption option) {
        for (SelectionHandler handler : selectionHandlers) {
            handler.onSelection(option);
        }

        for (ChangeHandler c : changeHandlers) {
            c.onValueChanged(option.getValue());
        }
    }

    public Select addSelectionHandler(SelectionHandler selectionHandler) {
        selectionHandlers.add(selectionHandler);
        return this;
    }

    @Override
    public Select addChangeHandler(ChangeHandler changeHandler) {
        changeHandlers.add(changeHandler);
        return this;
    }

    @Override
    public Select removeChangeHandler(ChangeHandler changeHandler) {
        changeHandlers.remove(changeHandler);
        return this;
    }

    @Override
    public boolean hasChangeHandler(ChangeHandler changeHandler) {
        return changeHandlers.contains(changeHandler);
    }

    public SelectOption getSelectedOption() {
        return selectedOption;
    }

    @Override
    public Select enable() {
        super.enable();
        buttonElement.enable();
        labelElement.enable();
        return this;
    }

    @Override
    public Select disable() {
        super.disable();
        buttonElement.disable();
        labelElement.disable();
        return this;
    }

    @Override
    public boolean isEnabled() {
        return !buttonElement.hasAttribute("disabled");
    }

    public Select dropup() {
        optionsMenu.setPosition(DropDownPosition.TOP);
        iconContainer.clearElement()
                .appendChild(getDropupIcon());
        return this;
    }

    public Select dropdown() {
        optionsMenu.setPosition(DropDownPosition.BOTTOM);
        iconContainer.clearElement().appendChild(getDropdownIcon());
        return this;
    }

    private MdiIcon getDropdownIcon() {
        return Icons.ALL.menu_down_mdi();
    }

    private MdiIcon getDropupIcon() {
        return Icons.ALL.menu_up_mdi();
    }

    @Override
    public Select value(T value) {
        return setValue(value, false);
    }

    public Select setValue(T value, boolean silent) {
        for (SelectOption option : getOptions()) {
            if (Objects.equals(option.getValue(), value)) {
                select(option, silent);
            }
        }
        return this;
    }

    @Override
    public T getValue() {
        return isSelected() ? getSelectedOption().getValue() : null;
    }

    @Override
    public boolean isEmpty() {
        return isNull(selectedOption);
    }

    @Override
    public Select clear() {
        labelElement.style().remove(FOCUSED);
        getOptions().forEach(selectOption -> selectOption.deselect(true));
        selectedOption = null;
        buttonValueContainer.setTextContent("");
        if (isAutoValidation())
            validate();
        return this;
    }

    @Override
    public Select invalidate(String errorMessage) {
        this.valid = false;
        formControl.style().remove("fc-" + focusColor.getStyle());
        labelElement.style().remove(focusColor.getStyle());
        formControl.style().add("fc-" + Color.RED.getStyle());
        labelElement.style().add(Color.RED.getStyle());
        removeLeftAddonColor(focusColor);
        setLeftAddonColor(Color.RED);
        return super.invalidate(errorMessage);
    }

    @Override
    public Select clearInvalid() {
        this.valid = true;
        formControl.style().remove("fc-" + Color.RED.getStyle());
        labelElement.style().remove(Color.RED.getStyle());
        if (isFocused()) {
            formControl.style().add("fc-" + focusColor.getStyle());
            labelElement.style().add(focusColor.getStyle());
        }

        removeLeftAddonColor(Color.RED);
        return super.clearInvalid();
    }

    public Select removeSelectionHandler(SelectionHandler selectionHandler) {
        if (nonNull(selectionHandler))
            selectionHandlers.remove(selectionHandler);
        return this;
    }

    @Override
    public Select focus() {
        if (isEnabled() && !isReadOnly()) {
            if (!isAttached()) {
                onAttached(mutationRecord -> buttonElement.asElement().focus());
            } else {
                buttonElement.asElement().focus();
            }
        }
        return this;
    }

    @Override
    public Select unfocus() {
        if (isEnabled() && !isReadOnly()) {
            if (!isAttached()) {
                onAttached(mutationRecord -> buttonElement.asElement().blur());
            } else {
                buttonElement.asElement().focus();
            }
        }
        return this;
    }

    private void doFocus() {
        if (isEnabled() && !isReadOnly()) {
            floatLabel();
            if (valid) {
                labelElement.style().add(focusColor.getStyle());
                formControl.style().add("fc-" + focusColor.getStyle());
                setLeftAddonColor(focusColor);
                buttonElement.asElement().focus();
                this.focused = true;
            }
        }
    }

    private void doUnfocus() {
        unfloatLabel();
        labelElement.style().remove(focusColor.getStyle());
        formControl.style().remove("fc-" + focusColor.getStyle());
        removeLeftAddonColor(focusColor);
        buttonElement.asElement().blur();
        this.focused = false;
    }

    protected void floatLabel() {
        getSelectLabel().style().add(FOCUSED);
    }

    protected void unfloatLabel() {
        if (isEmpty()) {
            getSelectLabel().style().remove(FOCUSED);
        }
    }

    private void setLeftAddonColor(Color focusColor) {
        if (leftAddon != null)
            leftAddon.style().add(focusColor.getStyle());
    }

    private void removeLeftAddonColor(Color focusColor) {
        if (leftAddon != null)
            leftAddon.style().remove(focusColor.getStyle());
    }

    @Override
    public boolean isFocused() {
        return focused;
    }

    @Override
    public Select setFocusColor(Color focusColor) {
        unfocus();
        this.focusColor = focusColor;
        if (isFocused())
            focus();
        return this;
    }

    public Select removeOption(SelectOption option) {
        if (nonNull(option) && getOptions().contains(option)) {
            option.deselect(true);
            option.asElement().remove();
        }
        return this;
    }

    public Select removeOptions(Collection> options) {
        if (nonNull(options) && !options.isEmpty() && !this.options.isEmpty()) {
            options.forEach(this::removeOption);
        }
        return this;
    }

    public Select removeAllOptions() {
        options.clear();
        optionsMenu.clearActions();
        clear();
        return this;
    }

    @Override
    public Select setReadOnly(boolean readOnly) {
        this.readOnly = readOnly;
        if (readOnly) {
            formControl.style().add("readonly");
            iconContainer.hide();
        } else {
            formControl.style().remove("readonly");
            iconContainer.show();
        }
        buttonElement.setReadOnly(readOnly);
        return this;
    }

    @Override
    public boolean isReadOnly() {
        return readOnly;
    }

    @FunctionalInterface
    public interface SelectionHandler {
        void onSelection(SelectOption option);
    }

    public DominoElement getSelectButton() {
        return buttonElement;
    }

    public DominoElement getSelectLabel() {
        return labelElement;
    }

    @Override
    public Select setAutoValidation(boolean autoValidation) {
        if (autoValidation) {
            if (isNull(autoValidationHandler)) {
                autoValidationHandler = option -> validate();
                addSelectionHandler(autoValidationHandler);
            }
        } else {
            removeSelectionHandler(autoValidationHandler);
            autoValidationHandler = null;
        }
        return this;
    }

    @Override
    public boolean isAutoValidation() {
        return nonNull(autoValidationHandler);
    }


    public Select setLeftAddon(IsElement leftAddon) {
        return setLeftAddon(leftAddon.asElement());
    }

    public Select setLeftAddon(HTMLElement leftAddon) {
        this.leftAddon = DominoElement.of(leftAddon);
        setAddon(leftAddonContainer, leftAddon, leftAddon);
        return this;
    }

    public Select setRightAddon(IsElement rightAddon) {
        return setRightAddon(rightAddon.asElement());
    }

    public Select setRightAddon(HTMLElement rightAddon) {
        this.rightAddon = DominoElement.of(rightAddon);
        setAddon(rightAddonContainer, rightAddon, rightAddon);
        return this;
    }

    public DominoElement getLeftAddonContainer() {
        return leftAddonContainer;
    }

    public DominoElement getRightAddonContainer() {
        return rightAddonContainer;
    }

    public Select removeRightAddon() {
        if (nonNull(rightAddon)) {
            rightAddonContainer.removeChild(rightAddon);
        }
        return this;
    }

    public Select removeLeftAddon() {
        if (nonNull(leftAddon)) {
            leftAddonContainer.removeChild(leftAddon);
        }
        return this;
    }

    private void setAddon(DominoElement container, Element oldAddon, Element addon) {
        if (nonNull(oldAddon)) {
            oldAddon.remove();
        }
        if (nonNull(addon)) {
            List oldClasses = new ArrayList<>(addon.classList.asList());
            for (String oldClass : oldClasses) {
                addon.classList.remove(oldClass);
            }
            oldClasses.add(0, "input-addon");
            for (String oldClass : oldClasses) {
                addon.classList.add(oldClass);
            }
            container.appendChild(addon);
        }
    }

    public List getValues() {
        return options.stream().map(SelectOption::getValue).collect(Collectors.toList());
    }

    public List getKeys() {
        return options.stream().map(SelectOption::getKey).collect(Collectors.toList());
    }

    public boolean containsKey(String key) {
        return getKeys().contains(key);
    }

    public boolean containsValue(T value) {
        return getValues().contains(value);
    }

    public Select setSearchable(boolean searchable) {
        optionsMenu.setSearchable(searchable);
        this.searchable = searchable;
        return this;
    }

    public boolean isSearchable() {
        return searchable;
    }

    @Override
    public DominoElement getInputElement() {
        return formLine;
    }

    @Override
    protected DominoElement getLabelElement() {
        return labelElement;
    }

    @Override
    protected DominoElement getFieldContainer() {
        return formLine;
    }


    @Override
    public HTMLElement asElement() {
        return container.asElement();
    }

    @Override
    public String getStringValue() {
        SelectOption selectedOption = getSelectedOption();
        if (nonNull(selectedOption)) {
            return selectedOption.getDisplayValue();
        }
        return null;
    }

    public int getSelectedIndex() {
        return options.indexOf(getSelectedOption());
    }

    public static void closeAllSelects() {
        DropDownMenu.closeAllMenus();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy