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

org.dominokit.domino.ui.keyboard.KeyboardEvents 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.keyboard;

import static java.util.Objects.nonNull;

import elemental2.dom.EventListener;
import elemental2.dom.HTMLElement;
import elemental2.dom.KeyboardEvent;
import elemental2.dom.Node;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import jsinterop.base.Js;
import org.jboss.elemento.IsElement;

/**
 * A helper class allowing listening to keyboard events on an element
 *
 * @param  the element type
 */
public class KeyboardEvents {

  public static final String ESCAPE = "escape";
  public static final String KEYDOWN = "keydown";
  public static final String KEYUP = "keyup";
  public static final String KEYPRESS = "keypress";
  public static final String ARROWDOWN = "arrowdown";
  public static final String ARROWUP = "arrowup";
  public static final String ENTER = "enter";
  public static final String DELETE = "delete";
  public static final String SPACE = "space";
  public static final String TAB = "tab";
  public static final String BACKSPACE = "backspace";

  private final Map handlers = new HashMap<>();
  private final Map ctrlHandlers = new HashMap<>();
  private KeyboardEventOptions defaultOptions = KeyboardEventOptions.create();

  /**
   * @param eventType The eventType that will trigger the handlers
   * @param elements varargs of the target elements
   */
  @SuppressWarnings("unchecked")
  public KeyboardEvents(String eventType, T... elements) {
    this(eventType, Arrays.asList(elements));
  }

  /**
   * @param eventType The eventType that will trigger the handlers
   * @param elements varargs of the target elements
   */
  @SuppressWarnings("unchecked")
  public KeyboardEvents(String eventType, List elements) {
    for (T element : elements) {
      element.addEventListener(
          eventType,
          evt -> {
            KeyboardEvent keyboardEvent = Js.uncheckedCast(evt);
            // ignore events without keycode (browser bug?)
            // example: picking value by keyboard from Chrome auto-suggest
            if (keyboardEvent.key == null) return;
            String key = keyboardEvent.key.toLowerCase();
            HandlerContext handlerContext = null;
            if (keyboardEvent.ctrlKey && ctrlHandlers.containsKey(key)) {
              handlerContext = ctrlHandlers.get(key);
            } else if (handlers.containsKey(key)) {
              handlerContext = handlers.get(key);
            }

            if (nonNull(handlerContext)) {
              handlerContext.handler.handleEvent(evt);
              if (handlerContext.options.preventDefault) {
                evt.preventDefault();
              }
              if (handlerContext.options.stopPropagation) {
                evt.stopPropagation();
              }
            }
          });
    }
  }

  /** @param elements the target elements */
  @SuppressWarnings("unchecked")
  public KeyboardEvents(T... elements) {
    this(KEYDOWN, elements);
  }

  /** @param elements the target elements */
  public KeyboardEvents(List elements) {
    this(KEYDOWN, elements);
  }

  /**
   * Static factory for creation keyboard event listener
   *
   * @param elements the target elements
   * @param  the type of the element
   * @return new instance
   * @deprecated use {@link #listenOnKeyDown(Node...)}, {@link #listenOnKeyUp(Node...)}, {@link
   *     #listenOnKeyPress(Node...)}
   */
  @Deprecated
  public static  KeyboardEvents listenOn(T... elements) {
    return new KeyboardEvents<>(elements);
  }

  /**
   * Same as {@link KeyboardEvents#listenOn(Node...)} but with wrapper {@link IsElement}
   *
   * @param elements the target {@link IsElement}
   * @param  the type of the element
   * @return new instance
   * @deprecated use {@link #listenOnKeyDown(IsElement...)}, {@link #listenOnKeyUp(IsElement...)},
   *     {@link #listenOnKeyPress(IsElement...)}
   */
  @Deprecated
  public static  KeyboardEvents listenOn(IsElement... elements) {
    return new KeyboardEvents<>(
        Arrays.stream(elements).map(IsElement::element).collect(Collectors.toList()));
  }

  /**
   * Static factory for creation keyboard keydown event listener
   *
   * @param elements the target element
   * @param  the type of the element
   * @return new instance
   */
  @SuppressWarnings("unchecked")
  public static  KeyboardEvents listenOnKeyDown(T... elements) {
    return new KeyboardEvents<>(KEYDOWN, elements);
  }

  /**
   * Same as {@link KeyboardEvents#listenOnKeyDown(Node...)} but with wrapper {@link IsElement}
   *
   * @param elements the target {@link IsElement}
   * @param  the type of the element
   * @return new instance
   */
  @SuppressWarnings("unchecked")
  public static  KeyboardEvents listenOnKeyDown(
      IsElement... elements) {
    return new KeyboardEvents<>(
        KEYDOWN, Arrays.stream(elements).map(IsElement::element).collect(Collectors.toList()));
  }

  /**
   * Same as {@link KeyboardEvents#listenOnKeyDown(Node...)} but with wrapper {@link IsElement}
   *
   * @param elements the target {@link IsElement}
   * @param  the type of the element
   * @return new instance
   */
  @SuppressWarnings("unchecked")
  public static  KeyboardEvents listenOnKeyDown(
      List> elements) {
    return new KeyboardEvents<>(
        KEYDOWN, elements.stream().map(IsElement::element).collect(Collectors.toList()));
  }

  /**
   * Static factory for creation keyboard keyUp event listener
   *
   * @param elements the target element
   * @param  the type of the element
   * @return new instance
   */
  @SuppressWarnings("unchecked")
  public static  KeyboardEvents listenOnKeyUp(T... elements) {
    return new KeyboardEvents<>(KEYUP, elements);
  }

  /**
   * Same as {@link KeyboardEvents#listenOnKeyUp(Node...)} but with wrapper {@link IsElement}
   *
   * @param elements the target {@link IsElement}
   * @param  the type of the element
   * @return new instance
   */
  @SuppressWarnings("unchecked")
  public static  KeyboardEvents listenOnKeyUp(IsElement... elements) {
    return new KeyboardEvents<>(
        KEYUP, Arrays.stream(elements).map(IsElement::element).collect(Collectors.toList()));
  }

  /**
   * Same as {@link KeyboardEvents#listenOnKeyUp(Node...)} but with wrapper {@link IsElement}
   *
   * @param elements the target {@link IsElement}
   * @param  the type of the element
   * @return new instance
   */
  @SuppressWarnings("unchecked")
  public static  KeyboardEvents listenOnKeyUp(
      List> elements) {
    return new KeyboardEvents<>(
        KEYUP, elements.stream().map(IsElement::element).collect(Collectors.toList()));
  }

  /**
   * Static factory for creation keyboard keyPress event listener
   *
   * @param elements the target element
   * @param  the type of the element
   * @return new instance
   */
  @SuppressWarnings("unchecked")
  public static  KeyboardEvents listenOnKeyPress(T... elements) {
    return new KeyboardEvents<>(KEYPRESS, elements);
  }

  /**
   * Same as {@link KeyboardEvents#listenOnKeyPress(Node...)} but with wrapper {@link IsElement}
   *
   * @param elements the target {@link IsElement}
   * @param  the type of the element
   * @return new instance
   */
  @SuppressWarnings("unchecked")
  public static  KeyboardEvents listenOnKeyPress(
      IsElement... elements) {
    return new KeyboardEvents<>(
        KEYPRESS, Arrays.stream(elements).map(IsElement::element).collect(Collectors.toList()));
  }

  /**
   * Same as {@link KeyboardEvents#listenOnKeyPress(Node...)} but with wrapper {@link IsElement}
   *
   * @param elements the target {@link IsElement}
   * @param  the type of the element
   * @return new instance
   */
  @SuppressWarnings("unchecked")
  public static  KeyboardEvents listenOnKeyPress(
      List> elements) {
    return new KeyboardEvents<>(
        KEYPRESS, elements.stream().map(IsElement::element).collect(Collectors.toList()));
  }

  /**
   * On escape button pressed
   *
   * @param escapeHandler the {@link EventListener} to call
   * @return same instance
   */
  // ---------------- handlers ----------------
  public KeyboardEvents onEscape(EventListener escapeHandler) {
    return onEscape(escapeHandler, defaultOptions());
  }

  /**
   * On escape button pressed with {@code options}
   *
   * @param escapeHandler the {@link EventListener} to call
   * @param options the {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents onEscape(EventListener escapeHandler, KeyboardEventOptions options) {
    return addHandler(ESCAPE, contextOf(escapeHandler, options));
  }

  /**
   * On arrow up or arrow down buttons pressed
   *
   * @param arrowDownHandler the {@link EventListener} to call
   * @return same instance
   */
  public KeyboardEvents onArrowUpDown(EventListener arrowDownHandler) {
    return onArrowUp(arrowDownHandler).onArrowDown(arrowDownHandler);
  }

  /**
   * On arrow up or arrow down buttons pressed with options
   *
   * @param arrowDownHandler the {@link EventListener} to call
   * @param options the {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents onArrowUpDown(
      EventListener arrowDownHandler, KeyboardEventOptions options) {
    return onArrowUp(arrowDownHandler, options).onArrowDown(arrowDownHandler, options);
  }

  /**
   * On arrow down button pressed
   *
   * @param arrowDownHandler the {@link EventListener} to call
   * @return same instance
   */
  public KeyboardEvents onArrowDown(EventListener arrowDownHandler) {
    return onArrowDown(arrowDownHandler, defaultOptions());
  }

  /**
   * On arrow down button pressed with options
   *
   * @param arrowDownHandler the {@link EventListener} to call
   * @param options the {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents onArrowDown(
      EventListener arrowDownHandler, KeyboardEventOptions options) {
    return addHandler(ARROWDOWN, contextOf(arrowDownHandler, options));
  }

  /**
   * On arrow up button pressed with options
   *
   * @param arrowUpHandler the {@link EventListener} to call
   * @return same instance
   */
  public KeyboardEvents onArrowUp(EventListener arrowUpHandler) {
    return onArrowUp(arrowUpHandler, defaultOptions());
  }

  /**
   * On arrow up button pressed with options
   *
   * @param arrowUpHandler the {@link EventListener} to call
   * @param options the {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents onArrowUp(EventListener arrowUpHandler, KeyboardEventOptions options) {
    return addHandler(ARROWUP, contextOf(arrowUpHandler, options));
  }

  /**
   * On enter button pressed
   *
   * @param enterHandler the {@link EventListener} to call
   * @return same instance
   */
  public KeyboardEvents onEnter(EventListener enterHandler) {
    return onEnter(enterHandler, defaultOptions());
  }

  /**
   * On enter button pressed with options
   *
   * @param enterHandler the {@link EventListener} to call
   * @param options the {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents onEnter(EventListener enterHandler, KeyboardEventOptions options) {
    return addHandler(ENTER, contextOf(enterHandler, options));
  }

  /**
   * On delete button pressed
   *
   * @param deleteHandler the {@link EventListener} to call
   * @return same instance
   */
  public KeyboardEvents onDelete(EventListener deleteHandler) {
    return onDelete(deleteHandler, defaultOptions());
  }

  /**
   * On delete button pressed with options
   *
   * @param deleteHandler the {@link EventListener} to call
   * @param options the {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents onDelete(EventListener deleteHandler, KeyboardEventOptions options) {
    return addHandler(DELETE, contextOf(deleteHandler, options));
  }

  /**
   * On space button pressed
   *
   * @param spaceHandler the {@link EventListener} to call
   * @return same instance
   */
  public KeyboardEvents onSpace(EventListener spaceHandler) {
    return onSpace(spaceHandler, defaultOptions());
  }

  /**
   * On space button pressed with options
   *
   * @param spaceHandler the {@link EventListener} to call
   * @param options the {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents onSpace(EventListener spaceHandler, KeyboardEventOptions options) {
    return addHandler(SPACE, contextOf(spaceHandler, options));
  }

  /**
   * On tab button pressed
   *
   * @param tabHandler the {@link EventListener} to call
   * @return same instance
   */
  public KeyboardEvents onTab(EventListener tabHandler) {
    return onTab(tabHandler, defaultOptions());
  }

  /**
   * On tab button pressed with options
   *
   * @param tabHandler the {@link EventListener} to call
   * @param options the {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents onTab(EventListener tabHandler, KeyboardEventOptions options) {
    return addHandler(TAB, contextOf(tabHandler, options));
  }

  /**
   * On key button pressed with options
   *
   * @param handler the {@link EventListener} to call
   * @param options the {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents on(String key, EventListener handler, KeyboardEventOptions options) {
    return addHandler(key, contextOf(handler, options));
  }

  /**
   * On key button pressed
   *
   * @param handler the {@link EventListener} to call
   * @return same instance
   */
  public KeyboardEvents on(String key, EventListener handler) {
    return on(key, handler, defaultOptions());
  }

  private KeyboardEvents addHandler(String type, HandlerContext handlerContext) {
    handlers.put(type, handlerContext);
    return this;
  }

  // ---------------- ctrl handlers ----------------

  /**
   * On ctrl + backspace buttons pressed
   *
   * @param ctrlBackspaceHandler the {@link EventListener} to call
   * @return same instance
   */
  public KeyboardEvents onCtrlBackspace(EventListener ctrlBackspaceHandler) {
    return onCtrlBackspace(ctrlBackspaceHandler, defaultOptions());
  }

  /**
   * On ctrl + backspace buttons pressed with options
   *
   * @param ctrlBackspaceHandler the {@link EventListener} to call
   * @param options the {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents onCtrlBackspace(
      EventListener ctrlBackspaceHandler, KeyboardEventOptions options) {
    return addCtrlHandler(BACKSPACE, contextOf(ctrlBackspaceHandler, options));
  }

  /**
   * Sets the default {@link KeyboardEventOptions}
   *
   * @param defaultOptions the default {@link KeyboardEventOptions}
   * @return same instance
   */
  public KeyboardEvents setDefaultOptions(KeyboardEventOptions defaultOptions) {
    this.defaultOptions = defaultOptions;
    return this;
  }

  private KeyboardEvents addCtrlHandler(String type, HandlerContext handlerContext) {
    ctrlHandlers.put(type, handlerContext);
    return this;
  }

  private HandlerContext contextOf(EventListener handler, KeyboardEventOptions options) {
    return new HandlerContext(handler, options);
  }

  private KeyboardEventOptions defaultOptions() {
    return defaultOptions;
  }

  /** Context to hold keyboard event options */
  public static class KeyboardEventOptions {
    private boolean preventDefault = false;
    private boolean stopPropagation = false;

    /** @return new instance */
    public static KeyboardEventOptions create() {
      return new KeyboardEventOptions();
    }

    /**
     * Sets if prevent default behaviour is enabled or not
     *
     * @param preventDefault true to prevent default, false otherwise
     * @return same instance
     */
    public KeyboardEventOptions setPreventDefault(boolean preventDefault) {
      this.preventDefault = preventDefault;
      return this;
    }

    /**
     * Sets if stop event propagation is enabled or not
     *
     * @param stopPropagation true to stop propagation, false otherwise
     * @return same instance
     */
    public KeyboardEventOptions setStopPropagation(boolean stopPropagation) {
      this.stopPropagation = stopPropagation;
      return this;
    }
  }

  private static class HandlerContext {
    private final EventListener handler;
    private final KeyboardEventOptions options;

    public HandlerContext(EventListener handler, KeyboardEventOptions options) {
      this.handler = handler;
      this.options = options;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy