
org.dominokit.domino.ui.forms.AbstractSelect Maven / Gradle / Ivy
/*
* Copyright © 2019 Dominokit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dominokit.domino.ui.forms;
import static elemental2.dom.DomGlobal.window;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.dominokit.domino.ui.style.Unit.px;
import static org.jboss.elemento.Elements.button;
import static org.jboss.elemento.Elements.span;
import elemental2.dom.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
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.BaseIcon;
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.Styles;
import org.dominokit.domino.ui.utils.AppendStrategy;
import org.dominokit.domino.ui.utils.BaseDominoElement;
import org.dominokit.domino.ui.utils.DominoElement;
import org.dominokit.domino.ui.utils.DominoUIConfig;
/**
* The base implementation for dropdown select form fields components
*
* @param The type of the field value
* @param The type of the single option value
* @param The type of the field extending from this class
*/
public abstract class AbstractSelect>
extends AbstractValueBox {
private static final String CLICK_EVENT = "click";
protected SelectOption noneOption = SelectOption.create(null, "none", "None");
private DominoElement buttonElement;
protected DominoElement buttonValueContainer =
DominoElement.of(span()).css("select-value", Styles.ellipsis_text);
private final DominoElement placeholderNode =
DominoElement.of(span()).css("select-placeholder");
protected final DominoElement valuesContainer = DominoElement.of(span());
protected LinkedList> options = new LinkedList<>();
private DropDownMenu optionsMenu;
private List> selectionHandlers = new ArrayList<>();
private Supplier> arrowIconSupplier = Icons.ALL::menu_down_mdi;
private BaseIcon> arrowIcon;
private OptionRenderer optionRenderer = SelectOption::element;
private boolean searchable;
private boolean creatable;
private boolean clearable;
private DominoElement arrowIconContainer;
private int popupWidth = 0;
private String dropDirection = "auto";
private boolean closePopOverOnOpen = false;
private boolean autoCloseOnSelect = true;
/** Creates an empty select */
public AbstractSelect() {
super("button", "");
optionsMenu = DropDownMenu.create(fieldContainer).addCss("select-option-menu");
optionsMenu.setAppendTarget(DomGlobal.document.body);
optionsMenu.setAppendStrategy(AppendStrategy.FIRST);
optionsMenu.setPosition(
DominoUIConfig.INSTANCE.getDefaultSelectPopupPosition().createPosition(this));
optionsMenu.addOpenHandler(this::resumeFocusValidation);
optionsMenu.addOpenHandler(this::scrollToSelectedOption);
buttonElement.appendChild(buttonValueContainer);
buttonValueContainer.appendChild(placeholderNode);
buttonValueContainer.appendChild(valuesContainer);
initListeners();
dropdown();
setSearchable(true);
setCreatable(false);
addChangeHandler(
value -> {
if (isNull(value)) {
clear();
}
});
css("d-select");
}
/**
* Create an empty select with a lable
*
* @param label String
*/
public AbstractSelect(String label) {
this();
setLabel(label);
}
/**
* Create a select field with a label and an initial options list
*
* @param label String
* @param options List of {@link SelectOption}
*/
public AbstractSelect(String label, List> options) {
this(label);
options.forEach(this::appendChild);
}
/**
* Ceate a select with empty label and a list of initial options
*
* @param options List of {@link SelectOption}
*/
public AbstractSelect(List> options) {
this("", options);
}
private void initListeners() {
EventListener clickListener =
evt -> {
pauseFocusValidation();
if (closePopOverOnOpen) {
ModalBackDrop.closePopovers();
}
open();
evt.stopPropagation();
};
if (nonNull(arrowIcon)) {
arrowIcon.addClickListener(clickListener);
}
buttonElement.addEventListener(CLICK_EVENT, clickListener);
getLabelElement()
.ifPresent(labelElement -> labelElement.addEventListener(CLICK_EVENT, clickListener));
buttonElement.addEventListener("focus", evt -> focus());
buttonElement.addEventListener("blur", evt -> unfocus());
optionsMenu.addCloseHandler(
() -> {
this.focus();
validate();
});
}
/**
* Sets a supplier for an icon to use as the dropdown arrow
*
* @param arrowIconSupplier Supplier for {@link BaseIcon}
*/
public void setArrowIconSupplier(Supplier> arrowIconSupplier) {
if (nonNull(arrowIconSupplier)) {
this.arrowIconSupplier = arrowIconSupplier;
}
}
@Override
public S clear(boolean silent) {
unfloatLabel();
clearValue(silent);
valuesContainer.setTextContent("");
showPlaceholder();
if (isAutoValidation()) validate();
return (S) this;
}
/**
* Opens the select dropdown menu
*
* @return same component instance
*/
public S open() {
if (isEnabled() && !isReadOnly()) {
DropDownMenu.closeAllMenus();
doOpen();
}
return (S) this;
}
private void doOpen() {
optionsMenu.open();
optionsMenu.styler(
style -> style.setWidth(getFieldInputContainer().getBoundingClientRect().width + "px"));
}
/** Closes the select dropdown menu */
public S close() {
optionsMenu.close();
return (S) this;
}
/**
* Adds a separator item between the select option in the dropdown menu
*
* @return same select instance
*/
public S divider() {
optionsMenu.separator();
return (S) this;
}
/**
* Adds a select options group to the dropdown menu
*
* @param group {@link SelectOptionGroup}
* @return same select instance
*/
public S addGroup(SelectOptionGroup group) {
DropdownActionsGroup> dropdownActionsGroup =
DropdownActionsGroup.create(group.getTitleElement());
for (SelectOption option : group.getOptions()) {
addOptionToGroup(dropdownActionsGroup, option);
}
group.setAddOptionConsumer(
selectOption -> {
addOptionToGroup(dropdownActionsGroup, selectOption);
});
optionsMenu.addGroup(dropdownActionsGroup);
return (S) this;
}
private void addOptionToGroup(
DropdownActionsGroup> dropdownActionsGroup, SelectOption option) {
dropdownActionsGroup.appendChild(asDropDownAction(option));
options.add(option);
}
/**
* Adds a List of options to the select dropdown menu
*
* @param options List of {@link SelectOption}
* @return same select instance
*/
public S addOptions(List> options) {
options.forEach(this::appendChild);
return (S) this;
}
/**
* Sets the dropdown menu width
*
* @param width int
* @return same select instance
*/
public S setPopupWidth(int width) {
this.popupWidth = width;
return (S) this;
}
/**
* Adds an option to the select dropdown menu
*
* @param option {@link SelectOption}
* @return same select instance
*/
public S appendChild(SelectOption option) {
return appendChild(option, selectOptionDropdownAction -> {});
}
public S appendChild(SelectOption option, Consumer>> andThen) {
options.add(option);
appendOptionValue(option, andThen);
return (S) this;
}
/**
* Insert an option as the first option in the dropdown menu
*
* @param option {@link SelectOption}
* @return same select instance
*/
public S insertFirst(SelectOption option) {
options.add(0, option);
insertFirstOptionValue(option);
return (S) this;
}
private void doSelectOption(SelectOption option) {
if (isEnabled()) {
select(option);
if (this.autoCloseOnSelect) {
close();
}
}
}
private void appendOptionValue(
SelectOption option, Consumer>> andThen) {
DropdownAction> action = asDropDownAction(option);
optionsMenu.appendChild(action);
andThen.accept(action);
}
private void insertFirstOptionValue(SelectOption option) {
optionsMenu.insertFirst(asDropDownAction(option));
}
private DropdownAction> asDropDownAction(SelectOption option) {
return DropdownAction.create(option, optionRenderer.element(option))
.setAutoClose(this.autoCloseOnSelect)
.setExcludeFromSearchResults(option.isExcludeFromSearchResults())
.addSelectionHandler(value -> doSelectOption(option));
}
/**
* Selects the option at the specified index if exists and set its value as the select value
*
* @param index int
* @return same select instance
*/
public S selectAt(int index) {
return selectAt(index, false);
}
/**
* Selects the option at the specified index if exists and set its value as the select value
*
* @param index int
* @param silent boolean, true to avoid triggering change handlers
* @return same select instance
*/
public S selectAt(int index, boolean silent) {
if (index < options.size() && index >= 0) select(options.get(index), silent);
return (S) this;
}
/**
* @param index int
* @return the {@link SelectOption} at the specified index if exists or else null
*/
public SelectOption getOptionAt(int index) {
if (index < options.size() && index >= 0) return options.get(index);
return null;
}
/** @return a List of all {@link SelectOption}s of this select component */
public List> getOptions() {
return options;
}
/**
* Selects the specified option if it is one of this select options
*
* @param option {@link SelectOption}
* @return same select instance
*/
public S select(SelectOption option) {
return select(option, false);
}
/**
* Selects the option at the specified index if exists and set its value as the select value
*
* @param option {@link SelectOption}
* @param silent boolean, true to avoid triggering change handlers
* @return same select instance
*/
public abstract S select(SelectOption option, boolean silent);
/** @return boolean, true if the select has a selected option */
public abstract boolean isSelected();
/**
* By default this will call the Selection Handlers and the Change handlers
*
* @param option the new selected {@link SelectOption}
*/
protected void onSelection(SelectOption option) {
for (SelectionHandler handler : selectionHandlers) {
handler.onSelection(option);
}
for (ChangeHandler super T> c : changeHandlers) {
c.onValueChanged(getValue());
}
}
/**
* @param selectionHandler {@link SelectionHandler}
* @return same select instance
*/
public S addSelectionHandler(SelectionHandler selectionHandler) {
selectionHandlers.add(selectionHandler);
return (S) this;
}
/** {@inheritDoc} */
@Override
public S enable() {
super.enable();
buttonElement.enable();
getLabelElement().ifPresent(BaseDominoElement::enable);
return (S) this;
}
/** {@inheritDoc} */
@Override
public S disable() {
super.disable();
buttonElement.disable();
getLabelElement().ifPresent(BaseDominoElement::disable);
return (S) this;
}
/** {@inheritDoc} */
@Override
public boolean isEnabled() {
return !buttonElement.hasAttribute("disabled");
}
/**
* force open the select dropdown menu to open up by default
*
* @return same select instance
*/
public S dropup() {
this.dropDirection = "up";
return (S) this;
}
private void onDropup() {
if (searchable) {
optionsMenu.appendChild(optionsMenu.getSearchContainer());
optionsMenu.getSearchContainer().removeCss("pos-top").addCss("pos-bottom");
optionsMenu.removeCss("pos-top").addCss("pos-bottom");
}
}
/**
* force open the select dropdown menu to open down by default
*
* @return same select instance
*/
public S dropdown() {
this.dropDirection = "down";
return (S) this;
}
private void onDropdown() {
if (searchable) {
optionsMenu.insertFirst(optionsMenu.getSearchContainer());
optionsMenu.getSearchContainer().removeCss("pos-bottom").addCss("pos-top");
optionsMenu.removeCss("pos-bottom").addCss("pos-top");
}
}
private MdiIcon getDropdownIcon() {
return Icons.ALL.menu_down_mdi();
}
private MdiIcon getDropupIcon() {
return Icons.ALL.menu_up_mdi();
}
/** {@inheritDoc} */
@Override
public S value(T value) {
return setValue(value, false);
}
/**
* Set the value with the ability to do so without triggering change handlers
*
* @param value T
* @param silent boolean, true to avoid triggering change handlers
* @return same select instance
*/
public abstract S setValue(T value, boolean silent);
/**
* @param selectionHandler {@link SelectionHandler}
* @return same select instance
*/
public S removeSelectionHandler(SelectionHandler selectionHandler) {
if (nonNull(selectionHandler)) selectionHandlers.remove(selectionHandler);
return (S) this;
}
/**
* Removes an option from the select dropdown menu
*
* @param option {@link SelectOption}
* @return same select instance
*/
public S removeOption(SelectOption option) {
if (nonNull(option) && getOptions().remove(option)) {
option.deselect(true);
option.element().remove();
}
return (S) this;
}
/**
* Removes a list of options from the select dropdown menu
*
* @param options collection of {@link SelectOption}
* @return same select instance
*/
public S removeOptions(Collection> options) {
if (nonNull(options) && !options.isEmpty() && !this.options.isEmpty()) {
options.forEach(this::removeOption);
}
return (S) this;
}
/**
* Removes all options from the select dropdown menu
*
* @return same select instance
*/
public S removeAllOptions() {
options.clear();
optionsMenu.clearActions();
clear();
if (isClearable()) {
setClearable(true);
}
return (S) this;
}
/** {@inheritDoc} */
@Override
public S setReadOnly(boolean readOnly) {
super.setReadOnly(readOnly);
if (readOnly) {
DominoElement.of(arrowIconContainer).hide();
floatLabel();
} else {
DominoElement.of(arrowIconContainer).show();
if (isEmptyIgnoreSpaces()) {
unfloatLabel();
}
}
buttonElement.setReadOnly(readOnly);
return (S) this;
}
/**
* Sets the option renderer
*
* @param optionRenderer the {@link OptionRenderer}
* @return same instance
*/
public S setOptionRenderer(OptionRenderer optionRenderer) {
this.optionRenderer = optionRenderer;
return (S) this;
}
/**
* A function to implement logic that will be called when the user change the selection in the
* select
*
* @param The type of the select value
*/
@FunctionalInterface
public interface SelectionHandler {
/** @param option the selected {@link SelectOption} */
void onSelection(SelectOption option);
}
/**
* the select box is actually rendered with a button
*
* @return the {@link HTMLButtonElement} that is actually displaying the selected option
*/
public DominoElement getSelectButton() {
return buttonElement;
}
/**
* @deprecated use {@link #getLabelElement()}
* @return the {@link HTMLLabelElement} of the select wrapped as {@link DominoElement}
*/
@Deprecated
public DominoElement getSelectLabel() {
return getLabelElement().get();
}
/**
* {@inheritDoc}
*
* @param autoValidate {@link AutoValidate}
* @return the {@link AutoValidator} implementation for the select which is {@link
* SelectAutoValidator}
*/
@Override
protected AutoValidator createAutoValidator(AutoValidate autoValidate) {
return new SelectAutoValidator<>(this, autoValidate);
}
private void setAddon(
DominoElement container, DominoElement 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);
}
}
/** @return a List of all V values from all the options of this select */
public List getValues() {
return options.stream().map(SelectOption::getValue).collect(Collectors.toList());
}
/** @return a List of all String keys of all the options of this select */
public List getKeys() {
return options.stream().map(SelectOption::getKey).collect(Collectors.toList());
}
/**
* Check if the select has an option with the specified key
*
* @param key String
* @return boolean, true of option with that key exists
*/
public boolean containsKey(String key) {
return getKeys().contains(key);
}
/**
* Check if the select has an option with the specified value
*
* @param value V
* @return boolean, true of option with that value exists
*/
public boolean containsValue(V value) {
return getValues().contains(value);
}
/**
* Disable/Enable search for the select.
*
* @param searchable boolean, if true a text box will show up in the dropdown menu to search for
* options
* @return same select instance
*/
public S setSearchable(boolean searchable) {
optionsMenu.setSearchable(searchable);
this.searchable = searchable;
return (S) this;
}
/**
* Enable/Disable on the fly option creation
*
* @param creatable boolean, if true a button will show up to allow the user to create a new
* select option and add it to the dropdown list
* @return same select instance
*/
public S setCreatable(boolean creatable) {
optionsMenu.setCreatable(creatable);
this.creatable = creatable;
return (S) this;
}
/**
* Adds a handler that will be called whenever we add a new option to the select using the {@link
* #setCreatable(boolean)} feature
*
* @param onAddOptionHandler {@link OnAddOptionHandler}
* @return same select instance
*/
public S setOnAddOptionHandler(OnAddOptionHandler onAddOptionHandler) {
if (!isNull(onAddOptionHandler)) {
optionsMenu.setOnAddListener(
(String input) -> {
onAddOptionHandler.onAddOption(
input,
createdOption -> {
if (!isNull(createdOption)) {
appendChild(createdOption);
select(createdOption);
}
});
});
}
return (S) this;
}
/**
* Closes the dropdown menu and call the handler
*
* @param closeMenuHandler {@link CloseMenuHandler}
* @return same select instance
*/
public S closeMenu(CloseMenuHandler closeMenuHandler) {
optionsMenu.close();
closeMenuHandler.onMenuClosed();
return (S) this;
}
/** @return boolean, true if search is enabled on this select */
public boolean isSearchable() {
return searchable;
}
/** @return boolean, true is creatable feature is enabled on this select */
public boolean isCreatable() {
return creatable;
}
/** closes all currently opened selects dropdown menus */
public static void closeAllSelects() {
DropDownMenu.closeAllMenus();
}
/**
* Selects an option by its key if exists
*
* @param key String
* @return same select instance
*/
public S selectByKey(String key) {
return selectByKey(key, false);
}
/**
* Selects an option by its key if exists with ability to avoid triggering change handlers
*
* @param key String
* @param silent boolean, true to avoid triggering change handlers
* @return same select instance
*/
public S selectByKey(String key, boolean silent) {
for (SelectOption option : getOptions()) {
if (option.getKey().equals(key)) {
select(option, silent);
}
}
return (S) this;
}
/**
* Enable/Disable the none option in the field
*
* @param clearable boolean, if true a none option will added to the select as the first option,
* when selected it actually nulls the select value
* @return same select instance
*/
public S setClearable(boolean clearable) {
this.clearable = clearable;
if (clearable && !options.contains(noneOption)) {
insertFirst(noneOption);
} else {
removeOption(noneOption);
}
return (S) this;
}
/** @return boolean, true if this select value can be cleared */
public boolean isClearable() {
return clearable;
}
/**
* sets the text display for the none option from the {@link #setClearable(boolean)}
*
* @param clearableText String
* @return same select instance
*/
public S setClearableText(String clearableText) {
noneOption.setDisplayValue(clearableText);
return (S) this;
}
/**
* @return String display value of the none option when {@link #setClearable(boolean)} is enabled
*/
public String getClearableText() {
return noneOption.getDisplayValue();
}
/** @return String dropdown direction up or down */
public String getDropDirection() {
return dropDirection;
}
/** @return the {@link HTMLElement} that contains the button of this select */
public DominoElement getButtonValueContainer() {
return buttonValueContainer;
}
/**
* Sets a custom dropdown position for this select
*
* @param dropPosition {@link DropDownPosition}
* @return same select instance
*/
public S setDropPosition(DropDownPosition dropPosition) {
optionsMenu.setPosition(dropPosition);
return (S) this;
}
/** {@inheritDoc} for the select this will create a button element */
@Override
protected HTMLElement createInputElement(String type) {
buttonElement = DominoElement.of(button()).attr("type", "button").css("select-button");
return buttonElement.element();
}
/** {@inheritDoc} */
@Override
protected void showPlaceholder() {
if (getPlaceholder() != null && shouldShowPlaceholder()) {
placeholderNode.setTextContent(getPlaceholder());
}
}
/** {@inheritDoc} */
@Override
protected void hidePlaceholder() {
if (getPlaceholder() != null && !shouldShowPlaceholder()) {
placeholderNode.clearElement();
}
}
/** {@inheritDoc} for thes select this creates the dropdown menu arrow */
@Override
protected DominoElement createMandatoryAddOn() {
if (isNull(arrowIconSupplier)) {
arrowIcon = Icons.ALL.menu_down_mdi().clickable();
} else {
arrowIcon = arrowIconSupplier.get().clickable();
}
arrowIconContainer = DominoElement.div().appendChild(arrowIcon);
return arrowIconContainer;
}
/** {@inheritDoc} */
@Override
protected void doSetValue(T value) {}
/**
* Sets a custom search filter to be used when {@link #setSearchable(boolean)} is enabled
*
* @param searchFilter {@link org.dominokit.domino.ui.dropdown.DropDownMenu.SearchFilter}
* @return same select instance
*/
public S setSearchFilter(DropDownMenu.SearchFilter searchFilter) {
this.optionsMenu.setSearchFilter(searchFilter);
return (S) this;
}
/** @return the {@link DropDownMenu} of this select */
public DropDownMenu getOptionsMenu() {
return optionsMenu;
}
/** @return boolean, true if closePopOverOnOpen is enabled */
public boolean isClosePopOverOnOpen() {
return closePopOverOnOpen;
}
/**
* Enable/Disable closing other popups in the screen when opens the select dropdown menu
*
* @param closePopOverOnOpen boolean, true to close other popups
* @return same select instance
*/
public S setClosePopOverOnOpen(boolean closePopOverOnOpen) {
this.closePopOverOnOpen = closePopOverOnOpen;
return (S) this;
}
/** @return boolean, true if the dropdown menu should close after selecting an option */
public boolean isAutoCloseOnSelect() {
return autoCloseOnSelect;
}
/**
* @param autoCloseOnSelect boolean, if true the dropdown menu will close after selecting an
* option otherwise it remains open
* @return same select instance
*/
public S setAutoCloseOnSelect(boolean autoCloseOnSelect) {
this.autoCloseOnSelect = autoCloseOnSelect;
optionsMenu
.getActions()
.forEach(dropdownAction -> dropdownAction.setAutoClose(autoCloseOnSelect));
return (S) this;
}
/**
* implementation of this method will determine how the select will scroll to the selected option
* when opens the dropdown menu
*/
protected abstract void scrollToSelectedOption();
/**
* A {@link DropDownPosition} that opens the select dropdown menu up or down based on the largest
* space available, the menu will show where the is more space
*
* @param The type of the field value
* @param The type of the single option value
* @param The type of the field extending from this class
*/
public static class PopupPositionTopDown>
implements DropDownPosition {
private DropDownPositionUp up = new DropDownPositionUp();
private DropDownPositionDown down = new DropDownPositionDown();
private final AbstractSelect select;
public PopupPositionTopDown(AbstractSelect select) {
this.select = select;
}
@Override
public void position(HTMLElement popup, HTMLElement target) {
DOMRect targetRect = target.getBoundingClientRect();
double distanceToMiddle = ((targetRect.top) - (targetRect.height / 2));
double windowMiddle = DomGlobal.window.innerHeight / 2;
double popupHeight =
popup.getElementsByClassName("dropdown-menu").getAt(0).getBoundingClientRect().height;
double distanceToBottom = window.innerHeight - targetRect.bottom;
double distanceToTop = (targetRect.top + targetRect.height);
boolean hasSpaceBelow = distanceToBottom > popupHeight;
boolean hasSpaceUp = distanceToTop > popupHeight;
if (("up".equalsIgnoreCase(select.dropDirection) && hasSpaceUp)
|| ((distanceToMiddle >= windowMiddle) && !hasSpaceBelow)
|| (hasSpaceUp && !hasSpaceBelow)) {
up.position(popup, target);
select.onDropup();
popup.setAttribute("popup-direction", "top");
} else {
down.position(popup, target);
select.onDropdown();
popup.setAttribute("popup-direction", "down");
}
popup.style.setProperty(
"width", select.popupWidth > 0 ? (select.popupWidth + "px") : (targetRect.width + "px"));
}
}
/** A {@link DropDownPosition} that opens the select dropdown menu always up */
public static class DropDownPositionUp implements DropDownPosition {
@Override
public void position(HTMLElement actionsMenu, HTMLElement target) {
DOMRect targetRect = target.getBoundingClientRect();
actionsMenu.style.setProperty(
"bottom", px.of(((window.innerHeight - targetRect.bottom) - window.pageYOffset)));
actionsMenu.style.setProperty("left", px.of((targetRect.left + window.pageXOffset)));
actionsMenu.style.removeProperty("top");
}
}
/** A {@link DropDownPosition} that opens the select dropdown menu always down */
public static class DropDownPositionDown implements DropDownPosition {
@Override
public void position(HTMLElement actionsMenu, HTMLElement target) {
DOMRect targetRect = target.getBoundingClientRect();
actionsMenu.style.setProperty("top", px.of((targetRect.top + window.pageYOffset)));
actionsMenu.style.setProperty("left", px.of((targetRect.left + window.pageXOffset)));
actionsMenu.style.removeProperty("bottom");
}
}
private static class SelectAutoValidator>
extends AutoValidator {
private AbstractSelect select;
private SelectionHandler selectionHandler;
public SelectAutoValidator(AbstractSelect select, AutoValidate autoValidate) {
super(autoValidate);
this.select = select;
}
/** {@inheritDoc} */
@Override
public void attach() {
selectionHandler = option -> autoValidate.apply();
select.addSelectionHandler(selectionHandler);
}
/** {@inheritDoc} */
@Override
public void remove() {
select.removeSelectionHandler(selectionHandler);
}
}
/**
* A function for implementing logic to be executed when a new option is added on the fly using
* the {@link #setCreatable(boolean)} feature
*
* @param the type of the select value
*/
@FunctionalInterface
public interface OnAddOptionHandler {
/**
* Takes the user input and convert it into a SelectOption
*
* @param input String user input
* @param completeHandler a callback Consumer of a {@link SelectOption} that should be called
* after creating the option
*/
void onAddOption(String input, Consumer> completeHandler);
}
/**
* A function for implementing logic that will be executed whenever the select dropdown is closed
*
* @param the type of the select value
*/
@FunctionalInterface
public interface CloseMenuHandler {
/** */
void onMenuClosed();
}
/**
* An interface for rendering the {@link SelectOption}
*
* @param the type of the object inside the option
*/
@FunctionalInterface
public interface OptionRenderer {
/**
* @param option the option to render
* @return the {@link HTMLElement} representing the option
*/
HTMLElement element(SelectOption option);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy