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

org.dominokit.domino.ui.menu.AbstractMenuItem Maven / Gradle / Ivy

There is a newer version: 2.0.3
Show newest version
/*
 * 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 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 elemental2.dom.Event;
import elemental2.dom.HTMLAnchorElement;
import elemental2.dom.HTMLDivElement;
import elemental2.dom.HTMLElement;
import elemental2.dom.HTMLLIElement;
import elemental2.dom.Node;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.dominokit.domino.ui.grid.flex.FlexItem;
import org.dominokit.domino.ui.grid.flex.FlexLayout;
import org.dominokit.domino.ui.icons.Icons;
import org.dominokit.domino.ui.icons.MdiIcon;
import org.dominokit.domino.ui.menu.direction.BestFitSideDropDirection;
import org.dominokit.domino.ui.utils.BaseDominoElement;
import org.dominokit.domino.ui.utils.DelayedExecution;
import org.dominokit.domino.ui.utils.DominoElement;
import org.dominokit.domino.ui.utils.HasDeselectionHandler;
import org.dominokit.domino.ui.utils.HasSelectionHandler;
import org.dominokit.domino.ui.utils.PopupsCloser;
import org.jboss.elemento.EventType;
import org.jboss.elemento.IsElement;

/**
 * The base implementation for {@link AbstractMenu} items
 *
 * @param  The type of the menu item value
 * @param  The type of the class extending from this class
 */
public class AbstractMenuItem>
    extends BaseDominoElement
    implements HasSelectionHandler, HasDeselectionHandler {

  private final DominoElement root = DominoElement.of(li()).css("menu-item");
  private final DominoElement linkElement =
      DominoElement.of(a())
          .setAttribute("tabindex", "0")
          .setAttribute("aria-expanded", "true")
          .setAttribute("href", "#");
  private final FlexItem contentContainer = FlexItem.create();
  private final FlexLayout mainContainer = FlexLayout.create().css("menu-item-cntnr");
  private final FlexLayout leftAddOnsContainer = FlexLayout.create().css("ddi-left-addon");
  private final FlexItem contentElement = FlexItem.create().css("ddi-content");
  private final FlexLayout rightAddOnsContainer = FlexLayout.create().css("ddi-right-addon");

  protected AbstractMenu parent;

  private final List> selectionHandlers = new ArrayList<>();
  private final List deselectionHandlers =
      new ArrayList<>();
  private String key;
  private V value;

  private final MdiIcon indicatorIcon = Icons.ALL.menu_right_mdi();
  private FlexItem nestingIndicator =
      FlexItem.create().css("ddi-indicator").setOrder(Integer.MAX_VALUE).appendChild(indicatorIcon);

  private FlexItem noIndicator =
      FlexItem.create().css("ddi-indicator").setOrder(Integer.MAX_VALUE);

  AbstractMenu menu;

  public AbstractMenuItem() {
    init((T) this);
    mainContainer
        .setGap("4px")
        .appendChild(FlexItem.create().appendChild(leftAddOnsContainer))
        .appendChild(contentElement.setFlexGrow(1))
        .appendChild(FlexItem.create().appendChild(rightAddOnsContainer));
    contentContainer.appendChild(mainContainer);
    root.appendChild(linkElement.appendChild(contentContainer));

    this.addEventListener(
        EventType.touchstart.getName(),
        evt -> {
          evt.stopPropagation();
          evt.preventDefault();
          focus();
          openSubMenu();
        });
    this.addEventListener(EventType.touchend.getName(), this::onSelected);
    this.addEventListener(EventType.click.getName(), this::onSelected);
    this.addEventListener(EventType.mouseenter.getName(), evt -> openSubMenu());
    addRightAddOn(noIndicator);
  }

  private void onSelected(Event evt) {
    evt.stopPropagation();
    evt.preventDefault();
    select();
  }

  /**
   * Adds an element as an add-on to the left
   *
   * @param addOn {@link FlexItem}
   * @return same menu item instance
   */
  public T addLeftAddOn(FlexItem addOn) {
    leftAddOnsContainer.appendChild(addOn);
    return (T) this;
  }

  /**
   * Adds an element as an add-on to the right
   *
   * @param addOn {@link FlexItem}
   * @return same menu item instance
   */
  public T addRightAddOn(FlexItem addOn) {
    rightAddOnsContainer.appendChild(addOn);
    return (T) this;
  }

  /** @return The {@link FlexItem} of the main content conatiner */
  public FlexItem getContentElement() {
    return contentElement;
  }

  /**
   * Appends a {@link Node} to the contentElement
   *
   * @param node {@link Node} to be appended to the component
   * @return same menu item instance
   */
  @Override
  public T appendChild(Node node) {
    contentElement.appendChild(node);
    return (T) this;
  }

  /**
   * Appends a {@link IsElement} to the contentElement
   *
   * @param element {@link IsElement} to be appended to the component
   * @return same menu item instance
   */
  @Override
  public T appendChild(IsElement element) {
    contentElement.appendChild(element);
    return (T) this;
  }

  /**
   * Apply a search token matching on the menu item
   *
   * @param token String search text
   * @param caseSensitive boolean, true if the search is case-sensitive
   * @return boolean, true if the menu item match the search token, false if not
   */
  public boolean onSearch(String token, boolean caseSensitive) {
    if (isNull(token) || token.isEmpty()) {
      this.show();
    } else {
      hide();
    }
    return false;
  }

  /**
   * Selects the menu item and trigger the select handlers
   *
   * @return same menu item instance
   */
  public T select() {
    T select = select(false);
    parent.onItemSelected(this);
    return select;
  }

  /**
   * Deselects the menu item and trigger the selection handlers
   *
   * @return same menu item instance
   */
  public T deselect() {
    return deselect(false);
  }

  /**
   * Selects the menu item with the option to trigger/disable the select handlers
   *
   * @param silent boolean, true to avoid triggering the select handlers.
   * @return same menu item instance
   */
  public T select(boolean silent) {
    if (!isDisabled()) {
      setAttribute("selected", true);
      if (!silent) {
        selectionHandlers.forEach(handler -> handler.onSelection((T) this));
      }
    }
    return (T) this;
  }

  /**
   * Deselect the menu item with the option to trigger/disable the select handlers
   *
   * @param silent boolean, true to avoid triggering the select handlers.
   * @return same menu item instance
   */
  public T deselect(boolean silent) {
    if (!isDisabled()) {
      setAttribute("selected", false);
      if (!silent) {
        deselectionHandlers.forEach(DeselectionHandler::onDeselection);
      }
    }
    return (T) this;
  }

  /** @return boolean, true if the menu item is selected */
  public boolean isSelected() {
    return Optional.ofNullable(getAttribute("selected")).map(Boolean::parseBoolean).orElse(false);
  }

  /** {@inheritDoc} */
  @Override
  public T addSelectionHandler(HasSelectionHandler.SelectionHandler selectionHandler) {
    if (nonNull(selectionHandler)) {
      selectionHandlers.add(selectionHandler);
    }
    return (T) this;
  }

  /** {@inheritDoc} */
  @Override
  public T removeSelectionHandler(HasSelectionHandler.SelectionHandler selectionHandler) {
    if (nonNull(selectionHandler)) {
      selectionHandlers.remove(selectionHandler);
    }
    return (T) this;
  }

  /** {@inheritDoc} */
  @Override
  public T addDeselectionHandler(DeselectionHandler deselectionHandler) {
    if (nonNull(deselectionHandler)) {
      deselectionHandlers.add(deselectionHandler);
    }
    return (T) this;
  }

  /**
   * Force focus on the menu item
   *
   * @return same menu item instance
   */
  public T focus() {
    getClickableElement().focus();
    return (T) this;
  }

  void setParent(AbstractMenu menu) {
    this.parent = menu;
  }

  /** @return String unique key of the menu item */
  public String getKey() {
    return key;
  }

  /**
   * @param key String unique key of the menu item
   * @return String menu iten unique key
   */
  public T setKey(String key) {
    this.key = key;
    return (T) this;
  }

  /** @return the value of the menu item */
  public V getValue() {
    return value;
  }

  /**
   * @param value the value of the meu item
   * @return same menu item instance
   */
  public T setValue(V value) {
    this.value = value;
    return (T) this;
  }

  /** @return the {@link FlexItem} that represent the current menu nesting indication. */
  public FlexItem getNestingIndicator() {
    return nestingIndicator;
  }

  /**
   * Sets a custom menu nesting indicator
   *
   * @param nestingIndicator {@link FlexItem}
   * @return same menu item
   */
  public T setNestingIndicator(FlexItem nestingIndicator) {
    if (nonNull(nestingIndicator)) {
      if (this.nestingIndicator.isAttached()) {
        nestingIndicator.remove();
      }

      noIndicator.remove();
      addRightAddOn(nestingIndicator.setOrder(Integer.MAX_VALUE).css("ddi-indicator"));
      this.nestingIndicator = nestingIndicator;
    }

    return (T) this;
  }

  /**
   * Sets the sub-menu of the menu item
   *
   * @param menu {@link AbstractMenu}
   * @return same menu item
   */
  public T setMenu(AbstractMenu menu) {
    this.menu = menu;
    if (nonNull(this.menu)) {
      this.menu.setAttribute("domino-sub-menu", true);
      this.menu.removeAttribute("domino-ui-root-menu");
      setNestingIndicator(nestingIndicator);
      this.menu.setTargetElement(this);
      this.menu.setDropDirection(new BestFitSideDropDirection());
      this.menu.setParentItem(this);
    } else {
      this.nestingIndicator.remove();
    }
    return (T) this;
  }

  /** Opens the sub-menu of the menu item */
  public void openSubMenu() {
    if (nonNull(menu)) {
      DelayedExecution.execute(
          () -> {
            if (nonNull(parent)) {
              this.menu.setParent(parent);
              if (!this.parent.isDropDown()
                  || (this.parent.isDropDown() && this.parent.isOpened())) {
                this.parent.openSubMenu(this.menu);
              }
            }
          },
          200);
    } else {
      DelayedExecution.execute(
          () -> {
            if (nonNull(parent)) {
              parent.closeCurrentOpen();
            }
          },
          200);
    }
  }

  private void openSelfMenu() {
    PopupsCloser.close();
    this.menu.open();
    this.parent.setCurrentOpen(this.menu);
  }

  void onParentClosed() {
    closeSubMenu();
  }

  /**
   * Close the item sub-menu
   *
   * @return same menu item instance
   */
  public T closeSubMenu() {
    if (nonNull(this.menu)) {
      this.menu.close();
    }
    return (T) this;
  }

  /** @return The parent {@link AbstractMenu} of the menu item */
  public AbstractMenu getParent() {
    return this.parent;
  }

  /** {@inheritDoc} */
  public boolean hasMenu() {
    return nonNull(this.menu);
  }

  /** {@inheritDoc} */
  @Override
  public HTMLElement getClickableElement() {
    return linkElement.element();
  }

  /** {@inheritDoc} */
  @Override
  public HTMLLIElement element() {
    return root.element();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy