org.dominokit.domino.ui.menu.AbstractMenu 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.menu;
import static elemental2.dom.DomGlobal.document;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.jboss.elemento.Elements.a;
import static org.jboss.elemento.Elements.li;
import static org.jboss.elemento.Elements.span;
import static org.jboss.elemento.Elements.ul;
import elemental2.dom.Event;
import elemental2.dom.EventListener;
import elemental2.dom.HTMLAnchorElement;
import elemental2.dom.HTMLDivElement;
import elemental2.dom.HTMLElement;
import elemental2.dom.HTMLLIElement;
import elemental2.dom.HTMLUListElement;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.dominokit.domino.ui.grid.flex.FlexDirection;
import org.dominokit.domino.ui.grid.flex.FlexItem;
import org.dominokit.domino.ui.grid.flex.FlexLayout;
import org.dominokit.domino.ui.icons.BaseIcon;
import org.dominokit.domino.ui.icons.Icons;
import org.dominokit.domino.ui.keyboard.KeyboardEvents;
import org.dominokit.domino.ui.mediaquery.MediaQuery;
import org.dominokit.domino.ui.menu.direction.BestSideUpDownDropDirection;
import org.dominokit.domino.ui.menu.direction.DropDirection;
import org.dominokit.domino.ui.menu.direction.MiddleOfScreenDropDirection;
import org.dominokit.domino.ui.menu.direction.MouseBestFitDirection;
import org.dominokit.domino.ui.modals.ModalBackDrop;
import org.dominokit.domino.ui.search.SearchBox;
import org.dominokit.domino.ui.style.Elevation;
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.KeyboardNavigation;
import org.dominokit.domino.ui.utils.PopupsCloser;
import org.jboss.elemento.EventType;
import org.jboss.elemento.IsElement;
/**
* The base component to create a menu like UI.
*
* @param The type of the menu items value
* @param The type of the class extending from this base class
*/
public abstract class AbstractMenu>
extends BaseDominoElement {
protected final SearchBox searchBox;
protected FlexLayout menuElement = FlexLayout.create();
protected final FlexItem headContainer =
FlexItem.create().css("menu-container", "menu-head");
protected final FlexItem searchContainer =
FlexItem.create().css("menu-container", "menu-search");
protected final FlexItem subHeaderContainer =
FlexItem.create().css("menu-container", "menu-subheader");
protected final FlexItem mainContainer =
FlexItem.create().css("menu-container", "menu-main");
protected final DominoElement itemsContainer = DominoElement.of(ul());
protected final DominoElement addMissingElement =
DominoElement.of(a())
.css("create-missing")
.setAttribute("tabindex", "0")
.setAttribute("aria-expanded", "true")
.setAttribute("href", "#");
protected final MenuHeader menuHeader;
private HTMLElement focusElement;
protected KeyboardNavigation> keyboardNavigation;
protected boolean searchable;
protected boolean caseSensitive = false;
protected String createMissingLabel = "Create ";
private MissingItemHandler missingItemHandler;
private DominoElement noResultElement = DominoElement.of(li()).css("no-results");
protected List> menuItems = new ArrayList<>();
protected boolean autoCloseOnSelect = true;
protected final List> selectionHandlers = new ArrayList<>();
protected boolean headerVisible = false;
private AbstractMenu currentOpen;
private boolean smallScreen;
private DropDirection dropDirection = new BestSideUpDownDropDirection();
private final DropDirection contextMenuDropDirection = new MouseBestFitDirection();
private final DropDirection smallScreenDropDirection = new MiddleOfScreenDropDirection();
private DropDirection effectiveDropDirection = dropDirection;
private HTMLElement targetElement;
private HTMLElement appendTarget = document.body;
private AppendStrategy appendStrategy = AppendStrategy.LAST;
private final List closeHandlers = new ArrayList<>();
private final List openHandlers = new ArrayList<>();
private AbstractMenu parent;
private AbstractMenuItem parentItem;
private final EventListener openListener =
evt -> {
evt.stopPropagation();
evt.preventDefault();
getEffectiveDropDirection().init(evt);
open();
};
private final FlexItem backArrowContainer =
FlexItem.create().setOrder(0).css("back-arrow-icon").hide();
private boolean contextMenu = false;
private boolean useSmallScreensDirection = true;
private boolean dropDown = false;
public AbstractMenu() {
init((T) this);
menuHeader = new MenuHeader<>(this);
menuElement.setDirection(FlexDirection.TOP_TO_BOTTOM);
searchBox = SearchBox.create().addSearchListener(this::onSearch);
this.appendChild(headContainer.hide().appendChild(menuHeader))
.appendChild(searchContainer)
.appendChild(subHeaderContainer)
.appendChild(mainContainer.setFlexGrow(1).appendChild(itemsContainer));
menuElement.css("dom-ui", "menu", "menu-bordered");
keyboardNavigation =
KeyboardNavigation.create(menuItems)
.setTabOptions(new KeyboardNavigation.EventOptions(false, true))
.setTabHandler(
(event, item) -> {
if (keyboardNavigation.isLastFocusableItem(item)) {
event.preventDefault();
if (isSearchable()) {
searchBox.getTextBox().getInputElement().element().focus();
} else {
keyboardNavigation.focusTopFocusableItem();
}
}
})
.setEnterHandler((event, item) -> item.select())
.registerNavigationHandler("ArrowRight", (event, item) -> item.openSubMenu())
.registerNavigationHandler(
"ArrowLeft",
(event, item) -> {
if (nonNull(getParentItem())) {
getParentItem().focus();
this.close();
}
})
.onSelect((event, item) -> item.select())
.focusCondition(item -> !item.isCollapsed() && !item.isDisabled())
.onFocus(
item -> {
if (isDropDown()) {
if (isOpened()) {
item.focus();
}
} else {
item.focus();
}
})
.onEscape(this::close);
element.addEventListener("keydown", keyboardNavigation);
KeyboardEvents.listenOnKeyDown(searchBox.getTextBox().getInputElement())
.onArrowDown(
evt -> {
if (nonNull(missingItemHandler) && addMissingElement.isAttached()) {
addMissingElement.element().focus();
} else {
keyboardNavigation.focusAt(0);
}
});
addMissingElement.addClickListener(
evt -> {
evt.preventDefault();
evt.stopPropagation();
onAddMissingElement();
});
KeyboardEvents.listenOnKeyDown(addMissingElement)
.onEnter(
evt -> {
evt.preventDefault();
evt.stopPropagation();
onAddMissingElement();
});
MediaQuery.addOnMediumAndDownListener(
() -> {
this.smallScreen = true;
});
MediaQuery.addOnLargeAndUpListener(
() -> {
this.smallScreen = false;
headContainer.toggleDisplay(headerVisible);
backArrowContainer.hide();
});
backArrowContainer.appendChild(
Icons.ALL
.keyboard_backspace()
.clickable()
.addClickListener(this::backToParent)
.addEventListener("touchend", this::backToParent)
.addEventListener("touchstart", Event::stopPropagation));
menuHeader.leftAddOnsContainer.appendChild(backArrowContainer);
}
private void onAddMissingElement() {
missingItemHandler.onMissingItem(searchBox.getTextBox().getValue(), (T) this);
onSearch(searchBox.getTextBox().getValue());
}
/**
* Set the menu icon in the header, setting the icon will force the header to show up if not
* visible
*
* @param icon Any Icon instance that extends from {@link BaseIcon}
* @return the same menu instance
*/
public T setIcon(BaseIcon> icon) {
menuHeader.setIcon(icon);
setHeaderVisible(true);
return (T) this;
}
/**
* Set the menu title in the header, setting the title will force the header to show up if not
* visible
*
* @param title String
* @return same menu instance
*/
public T setTitle(String title) {
menuHeader.setTitle(title);
setHeaderVisible(true);
return (T) this;
}
/**
* Appends an element to menu actions bar, appending an action element will force the header to
* show up if not visible
*
* @param element {@link HTMLElement}
* @return same menu instance
*/
public T appendAction(HTMLElement element) {
menuHeader.appendAction(element);
setHeaderVisible(true);
return (T) this;
}
/**
* Appends an element to menu actions bar, appending an action element will force the header to
* show up if not visible
*
* @param element {@link IsElement}
* @return same menu instance
*/
public T appendAction(IsElement> element) {
menuHeader.appendAction(element);
setHeaderVisible(true);
return (T) this;
}
/**
* Appends a child element to the menu subheader, the subheader will show up below the search and
* before the menu items.
*
* @param element {@link HTMLElement}
* @return same menu instance
*/
public T appendSubHeaderChild(HTMLElement element) {
subHeaderContainer.appendChild(element);
autoHideShowSubHeader();
return (T) this;
}
/**
* Appends a child element to the menu subheader, the subheader will show up below the search and
* before the menu items.
*
* @param element {@link IsElement}
* @return same menu instance
*/
public T appendSunHeaderChild(IsElement> element) {
subHeaderContainer.appendChild(element);
autoHideShowSubHeader();
return (T) this;
}
private void autoHideShowSubHeader() {
subHeaderContainer.toggleDisplay(!subHeaderContainer.isEmptyElement());
}
/**
* Appends a menu item to this menu
*
* @param menuItem {@link AbstractMenu}
* @return same menu instance
*/
public T appendChild(AbstractMenuItem menuItem) {
if (nonNull(menuItem)) {
itemsContainer.appendChild(menuItem);
menuItems.add(menuItem);
menuItem.setParent(this);
}
return (T) this;
}
/**
* Removes a menu item from this menu
*
* @param menuItem {@link AbstractMenu}
* @return same menu instance
*/
public T removeItem(AbstractMenuItem menuItem) {
if (this.menuItems.contains(menuItem)) {
menuItem.remove();
this.menuItems.remove(menuItem);
}
return (T) this;
}
/**
* Appends a separator item to this menu, separator will show up as a simple border.
*
* @return same menu instance
*/
public T appendSeparator() {
this.itemsContainer.appendChild(
DominoElement.of(li()).add(DominoElement.of(span()).css("ddi-separator")));
return (T) this;
}
/** @return the {@link FlexItem} containing the {@link MenuHeader} component */
public FlexItem getHeadContainer() {
return headContainer;
}
/** @return The {@link FlexItem} containing the sub-header the menu */
public FlexItem getSubHeaderContainer() {
return subHeaderContainer;
}
/** @return the {@link FlexItem} that wrap this menu items container */
public FlexItem getMainContainer() {
return mainContainer;
}
/** {@inheritDoc} */
@Override
public HTMLDivElement element() {
return menuElement.element();
}
private void clearSearch() {
searchBox.clearSearch();
}
/**
* If search is enabled, when search is trigger it will call this method.
*
* @param token String user input in the {@link SearchBox}
* @return boolean, true if there is at least one menu item that matched the search token, else
* will return false.
*/
public boolean onSearch(String token) {
this.menuItems.forEach(AbstractMenuItem::closeSubMenu);
boolean emptyToken = emptyToken(token);
if (emptyToken) {
this.removeCss("has-search");
this.addMissingElement.remove();
} else {
this.css("has-search");
}
if (nonNull(missingItemHandler) && !emptyToken) {
addMissingElement.setInnerHtml(getCreateMissingLabel() + "" + token + "");
searchContainer.appendChild(addMissingElement);
}
long count =
this.menuItems.stream()
.filter(dropDownItem -> dropDownItem.onSearch(token, isCaseSensitive()))
.count();
if (count < 1 && menuItems.size() > 0) {
this.itemsContainer.appendChild(
noResultElement.setInnerHtml("No results matched " + " " + token + ""));
} else {
noResultElement.remove();
}
return count > 0;
}
/** @return String label that indicate the create missing items action */
public String getCreateMissingLabel() {
return createMissingLabel;
}
/**
* Sets the label that will appear when no elements match the search, default is "create"
*
* @param createMissingLabel String
* @return same menu instance
*/
public T setCreateMissingLabel(String createMissingLabel) {
if (isNull(createMissingLabel) || createMissingLabel.isEmpty()) {
this.createMissingLabel = "Create ";
} else {
this.createMissingLabel = createMissingLabel;
}
return (T) this;
}
private boolean emptyToken(String token) {
return isNull(token) || token.isEmpty();
}
/** @return a List of {@link AbstractMenuItem} of this menu */
public List> getMenuItems() {
return menuItems;
}
/**
* @return The {@link DominoElement} of the {@link HTMLLIElement} that is used to represent no
* results when search is applied
*/
public DominoElement getNoResultElement() {
return noResultElement;
}
/**
* Sets a custom element to represent no results when search is applied.
*
* @param noResultElement {@link HTMLLIElement}
* @return same menu instance
*/
public T setNoResultElement(HTMLLIElement noResultElement) {
if (nonNull(noResultElement)) {
this.noResultElement = DominoElement.of(noResultElement);
}
return (T) this;
}
/**
* Sets a custom element to represent no results when search is applied.
*
* @param noResultElement {@link IsElement} of {@link HTMLLIElement}
* @return same menu instance
*/
public T setNoResultElement(IsElement noResultElement) {
if (nonNull(noResultElement)) {
setNoResultElement(noResultElement.element());
}
return (T) this;
}
/** @return boolean, true if this menu search is case-sensitive */
public boolean isCaseSensitive() {
return caseSensitive;
}
public T setCaseSensitive(boolean caseSensitive) {
this.caseSensitive = caseSensitive;
return (T) this;
}
/** @return the {@link HTMLElement} that should be focused by default when open the menu */
public HTMLElement getFocusElement() {
if (isNull(this.focusElement)) {
if (isSearchable()) {
return this.searchBox.getTextBox().getInputElement().element();
} else if (!this.menuItems.isEmpty()) {
return menuItems.get(0).getClickableElement();
} else {
return this.itemsContainer.element();
}
}
return focusElement;
}
/**
* Sets a custom element as the default focus element of the menu
*
* @param focusElement {@link HTMLElement}
* @return same menu instance
*/
public T setFocusElement(HTMLElement focusElement) {
this.focusElement = focusElement;
return (T) this;
}
/**
* Sets a custom element as the default focus element of the menu
*
* @param focusElement {@link IsElement}
* @return same menu instance
*/
public T setFocusElement(IsElement extends HTMLElement> focusElement) {
return setFocusElement(focusElement.element());
}
/** @return the {@link SearchBox} of the menu */
public SearchBox getSearchBox() {
return searchBox;
}
/** @return The {@link FlexItem} that contains the search box of the menu */
public FlexItem getSearchContainer() {
return searchContainer;
}
/**
* @return The {@link DominoElement} of the {@link HTMLUListElement} that contains the menu items.
*/
public DominoElement getItemsContainer() {
return itemsContainer;
}
/** @return The {@link KeyboardNavigation} of the menu */
public KeyboardNavigation> getKeyboardNavigation() {
return keyboardNavigation;
}
/** @return The {@link MenuHeader} component of the menu */
public MenuHeader getMenuHeader() {
return menuHeader;
}
/**
* Change the visibility of the menu header
*
* @param visible boolean, True to show the header, false to hide it.
* @return same menu instance
*/
public T setHeaderVisible(boolean visible) {
headContainer.toggleDisplay(visible);
this.headerVisible = visible;
return (T) this;
}
/** @return boolean, true if search is enabled */
public boolean isSearchable() {
return searchable;
}
/**
* Enable/Disable search
*
* @param searchable boolean, true to search box and enable search, false to hide the search box
* and disable search.
* @return same menu instance
*/
public T setSearchable(boolean searchable) {
if (searchable) {
searchContainer.appendChild(searchBox);
} else {
searchContainer.clearElement();
}
this.searchable = searchable;
return (T) this;
}
/**
* Set a handler that will allow the user to handle a search that does not match any existing
* items.
*
* @param missingItemHandler {@link MissingItemHandler}
* @return same menu instance
*/
public T setMissingItemHandler(MissingItemHandler missingItemHandler) {
this.missingItemHandler = missingItemHandler;
return (T) this;
}
/**
* Selects the specified menu item if it is one of this menu items
*
* @param menuItem {@link AbstractMenuItem}
* @return same menu instance
*/
public T select(AbstractMenuItem menuItem) {
return select(menuItem, false);
}
/**
* Selects the menu item at the specified index if exists
*
* @param menuItem {@link AbstractMenuItem}
* @param silent boolean, true to avoid triggering change handlers
* @return same menu instance
*/
public T select(AbstractMenuItem menuItem, boolean silent) {
menuItem.select(silent);
return (T) this;
}
/**
* Selects the menu item at the specified index if exists
*
* @param index int
* @return same menu instance
*/
public T selectAt(int index) {
return selectAt(index, false);
}
/**
* Selects the menu at the specified index if exists
*
* @param index int
* @param silent boolean, true to avoid triggering change handlers
* @return same menu instance
*/
public T selectAt(int index, boolean silent) {
if (index < menuItems.size() && index >= 0) select(menuItems.get(index), silent);
return (T) this;
}
/**
* Selects a menu item by its key if exists
*
* @param key String
* @return same menu instance
*/
public T selectByKey(String key) {
return selectByKey(key, false);
}
/**
* Selects a menu item 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 menu instance
*/
public T selectByKey(String key, boolean silent) {
for (AbstractMenuItem menuItem : getMenuItems()) {
if (menuItem.getKey().equals(key)) {
select(menuItem, silent);
}
}
return (T) this;
}
/** @return boolean, true if the menu should close after selecting a menu item */
public boolean isAutoCloseOnSelect() {
return autoCloseOnSelect;
}
/**
* @param autoCloseOnSelect boolean, if true the menu will close after selecting a menu item
* otherwise it remains open
* @return same menu instance
*/
public T setAutoCloseOnSelect(boolean autoCloseOnSelect) {
this.autoCloseOnSelect = autoCloseOnSelect;
return (T) this;
}
/**
* Adds/removes the outer borders of the menu, this will add/remove the menu-bordered style
*
* @param bordered boolean, true to show borders, false to remove them
* @return same menu instance
*/
public T setBordered(boolean bordered) {
removeCss("menu-bordered");
if (bordered) {
css("menu-bordered");
}
return (T) this;
}
/**
* Adds a global selection handler that will apply to all menu items
*
* @param selectionHandler {@link MenuItemSelectionHandler}
* @return same menu instance
*/
public T addSelectionHandler(MenuItemSelectionHandler selectionHandler) {
if (nonNull(selectionHandler)) {
selectionHandlers.add(selectionHandler);
}
return (T) this;
}
/**
* removes a global selection handler that was applied to all menu items
*
* @param selectionHandler {@link MenuItemSelectionHandler}
* @return same menu instance
*/
public T removeSelectionHandler(MenuItemSelectionHandler selectionHandler) {
if (nonNull(selectionHandler)) {
selectionHandlers.remove(selectionHandler);
}
return (T) this;
}
/**
* Opens a sub menu that has this menu as its parent
*
* @param dropMenu {@link AbstractMenu} to open
* @return same menu instance
*/
public T openSubMenu(AbstractMenu dropMenu) {
if (!Objects.equals(currentOpen, dropMenu)) {
closeCurrentOpen();
}
dropMenu.open();
setCurrentOpen(dropMenu);
return (T) this;
}
void setCurrentOpen(AbstractMenu dropMenu) {
this.currentOpen = dropMenu;
}
void closeCurrentOpen() {
if (nonNull(currentOpen)) {
currentOpen.close();
}
}
private void backToParent(Event evt) {
evt.stopPropagation();
evt.preventDefault();
this.close();
if (nonNull(parent)) {
this.parent.open(true);
}
}
/** @return True if the menu is opened, false otherwise */
public boolean isOpened() {
return isDropDown() && element.isAttached();
}
/**
* Opens the menu with a boolean to indicate if the first element should be focused
*
* @param focus true to focus the first element
*/
public void open(boolean focus) {
if (isDropDown()) {
if (isOpened()) {
getEffectiveDropDirection().position(element.element(), getTargetElement());
} else {
closeOthers();
searchBox.clearSearch();
onAttached(
mutationRecord -> {
getEffectiveDropDirection().position(element.element(), getTargetElement());
if (focus) {
focus();
}
element.setCssProperty("z-index", ModalBackDrop.getNextZIndex() + 10 + "");
openHandlers.forEach(OpenHandler::onOpen);
DominoElement.of(getTargetElement()).onDetached(targetDetach -> close());
DominoElement.of(getAppendTarget()).onDetached(targetDetach -> close());
});
appendStrategy.onAppend(getAppendTarget(), element.element());
onDetached(record -> close());
if (smallScreen && nonNull(parent) && parent.isDropDown()) {
parent.hide();
headContainer.show();
backArrowContainer.show();
}
show();
}
}
}
protected DropDirection getEffectiveDropDirection() {
if (isUseSmallScreensDirection() && smallScreen) {
return smallScreenDropDirection;
} else {
if (isContextMenu()) {
return contextMenuDropDirection;
} else {
return dropDirection;
}
}
}
private void closeOthers() {
if (this.hasAttribute("domino-sub-menu")
&& Boolean.parseBoolean(this.getAttribute("domino-sub-menu"))) {
return;
}
PopupsCloser.close();
}
private void focus() {
getFocusElement().focus();
}
/**
* @return the {@link HTMLElement} that triggers this menu to open, and which the positioning of
* the menu will be based on.
*/
public HTMLElement getTargetElement() {
return targetElement;
}
/**
* @param targetElement The {@link IsElement} that triggers this menu to open, and which the
* positioning of the menu will be based on.
* @return same menu instance
*/
public T setTargetElement(IsElement> targetElement) {
return (T) setTargetElement(targetElement.element());
}
/**
* @param targetElement The {@link HTMLElement} that triggers this menu to open, and which the
* positioning of the menu will be based on.
* @return same menu instance
*/
public T setTargetElement(HTMLElement targetElement) {
if (nonNull(this.targetElement)) {
this.targetElement.removeEventListener(
isContextMenu() ? EventType.contextmenu.getName() : EventType.click.getName(),
openListener);
}
this.targetElement = targetElement;
if (nonNull(this.targetElement)) {
applyTargetListeners();
setDropDown(true);
} else {
setDropDown(false);
}
return (T) this;
}
/** @return the {@link HTMLElement} to which the menu will be appended to when opened. */
public HTMLElement getAppendTarget() {
return appendTarget;
}
/**
* set the {@link HTMLElement} to which the menu will be appended to when opened.
*
* @param appendTarget {@link HTMLElement}
* @return same menu instance
*/
public T setAppendTarget(HTMLElement appendTarget) {
if (isNull(appendTarget)) {
this.appendTarget = document.body;
} else {
this.appendTarget = appendTarget;
}
return (T) this;
}
/**
* Opens the menu
*
* @return same menu instance
*/
public T open() {
if (isDropDown()) {
open(true);
}
return (T) this;
}
/**
* Close the menu
*
* @return same menu instance
*/
public T close() {
if (isDropDown()) {
if (isOpened()) {
this.remove();
getTargetElement().focus();
searchBox.clearSearch();
menuItems.forEach(AbstractMenuItem::onParentClosed);
closeHandlers.forEach(CloseHandler::onClose);
if (smallScreen && nonNull(parent) && parent.isDropDown()) {
parent.show();
}
}
}
return (T) this;
}
/** @return The current {@link DropDirection} of the menu */
public DropDirection getDropDirection() {
return dropDirection;
}
/**
* Sets the {@link DropDirection} of the menu
*
* @param dropDirection {@link DropDirection}
* @return same menu instance
*/
public T setDropDirection(DropDirection dropDirection) {
if (effectiveDropDirection.equals(this.dropDirection)) {
this.dropDirection = dropDirection;
this.effectiveDropDirection = this.dropDirection;
} else {
this.dropDirection = dropDirection;
}
return (T) this;
}
/**
* Adds a close handler to be called when the menu is closed
*
* @param closeHandler The {@link CloseHandler} to add
* @return same instance
*/
public T addCloseHandler(CloseHandler closeHandler) {
closeHandlers.add(closeHandler);
return (T) this;
}
/**
* Removes a close handler
*
* @param closeHandler The {@link CloseHandler} to remove
* @return same instance
*/
public T removeCloseHandler(CloseHandler closeHandler) {
closeHandlers.remove(closeHandler);
return (T) this;
}
/**
* Adds an open handler to be called when the menu is opened
*
* @param openHandler The {@link OpenHandler} to add
* @return same instance
*/
public T addOpenHandler(OpenHandler openHandler) {
openHandlers.add(openHandler);
return (T) this;
}
/**
* Removes an open handler
*
* @param openHandler The {@link OpenHandler} to remove
* @return same instance
*/
public T removeOpenHandler(OpenHandler openHandler) {
openHandlers.remove(openHandler);
return (T) this;
}
void setParent(AbstractMenu parent) {
this.parent = parent;
}
/** @return the parent {@link AbstractMenu} of the menu */
public AbstractMenu, ?> getParent() {
return (T) parent;
}
void setParentItem(AbstractMenuItem parentItem) {
this.parentItem = parentItem;
}
/** @return the {@link AbstractMenuItem} that opens the menu */
public AbstractMenuItem getParentItem() {
return parentItem;
}
/** @return boolean, true if the menu is a context menu that will open on right click */
public boolean isContextMenu() {
return contextMenu;
}
/**
* Set the menu as a context menu that will open when the target element got a right click instead
* of a click
*
* @param contextMenu booleanm true to make the menu a context menu
* @return same menu instance
*/
public T setContextMenu(boolean contextMenu) {
this.contextMenu = contextMenu;
if (nonNull(targetElement)) {
applyTargetListeners();
}
return (T) this;
}
private void applyTargetListeners() {
if (isContextMenu()) {
getTargetElement().removeEventListener(EventType.click.getName(), openListener);
getTargetElement().addEventListener(EventType.contextmenu.getName(), openListener);
} else {
getTargetElement().removeEventListener(EventType.contextmenu.getName(), openListener);
getTargetElement().addEventListener(EventType.click.getName(), openListener);
}
}
protected void onItemSelected(AbstractMenuItem item) {
if (nonNull(parent)) {
parent.onItemSelected(item);
} else {
if (isAutoCloseOnSelect() && !item.hasMenu()) {
PopupsCloser.close();
}
selectionHandlers.forEach(selectionHandler -> selectionHandler.onItemSelected(item));
}
}
/**
* @return boolean, tru if use of small screens drop direction to the middle of screen is used or
* else false
*/
public boolean isUseSmallScreensDirection() {
return useSmallScreensDirection;
}
/**
* @param useSmallScreensDropDirection boolean, true to allow the switch to small screen middle of
* screen position, false to use the provided menu drop direction
* @return same menu instance
*/
public T setUseSmallScreensDirection(boolean useSmallScreensDropDirection) {
this.useSmallScreensDirection = useSmallScreensDropDirection;
if (!useSmallScreensDropDirection && getEffectiveDropDirection() == smallScreenDropDirection) {
this.effectiveDropDirection = dropDirection;
}
return (T) this;
}
public boolean isDropDown() {
return dropDown;
}
private void setDropDown(boolean dropdown) {
if (dropdown) {
this.setAttribute("domino-ui-root-menu", true)
.setAttribute(PopupsCloser.DOMINO_UI_AUTO_CLOSABLE, true)
.css("drop-menu");
menuElement.elevate(Elevation.LEVEL_1);
} else {
this.removeAttribute("domino-ui-root-menu")
.removeAttribute(PopupsCloser.DOMINO_UI_AUTO_CLOSABLE)
.removeCss("drop-menu");
menuElement.elevate(Elevation.NONE);
}
this.dropDown = dropdown;
}
/** A handler that will be called when closing the menu */
@FunctionalInterface
public interface CloseHandler {
/** Will be called when the menu is closed */
void onClose();
}
/** A handler that will be called when opening the menu */
@FunctionalInterface
public interface OpenHandler {
/** Will be called when the menu is opened */
void onOpen();
}
/**
* A functional interface to implement menu items selection handlers
*
* @param V the type of the menu item value
*/
@FunctionalInterface
public interface MenuItemSelectionHandler {
/**
* Will be called when a menu item is called
*
* @param menuItem The {@link AbstractMenuItem} selected
*/
void onItemSelected(AbstractMenuItem menuItem);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy