Vaadin Right Click/Context Click

To add right-click (or context-click) listener to any component in Vaadin 23+:

/*
 * Copyright 2000-2024 Vaadin Ltd.
 *
 * 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 com.vaadin.starter.skeleton;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.DomEvent;
import com.vaadin.flow.component.EventData;

/**
 * Event fired when a component is right-clicked (A.K.A. context-click).
 * @param <C>
 *            The source component type
 */
@DomEvent("contextmenu")
public class ContextMenuEvent<C extends Component> extends ComponentEvent<C> {

   private final int screenX;
   private final int screenY;

   private final int clientX;
   private final int clientY;

   private final int clickCount;
   private final int button;
   private final boolean ctrlKey;
   private final boolean shiftKey;
   private final boolean altKey;
   private final boolean metaKey;

   /**
    * Creates a new click event.
    *
    * @param source
    *            the component that fired the event
    * @param fromClient
    *            <code>true</code> if the event was originally fired on the
    *            client, <code>false</code> if the event originates from
    *            server-side logic
    * @param screenX
    *            the x coordinate of the click event, relative to the upper
    *            left corner of the screen, -1 if unknown
    * @param screenY
    *            the y coordinate of the click event, relative to the upper
    *            left corner of the screen, -i if unknown
    * @param clientX
    *            the x coordinate of the click event, relative to the upper
    *            left corner of the browser viewport, -1 if unknown
    * @param clientY
    *            the y coordinate of the click event, relative to the upper
    *            left corner of the browser viewport, -1 if unknown
    * @param clickCount
    *            the number of consecutive clicks recently recorded
    * @param button
    *            the id of the pressed mouse button
    * @param ctrlKey
    *            <code>true</code> if the control key was down when the event
    *            was fired, <code>false</code> otherwise
    * @param shiftKey
    *            <code>true</code> if the shift key was down when the event was
    *            fired, <code>false</code> otherwise
    * @param altKey
    *            <code>true</code> if the alt key was down when the event was
    *            fired, <code>false</code> otherwise
    * @param metaKey
    *            <code>true</code> if the meta key was down when the event was
    *            fired, <code>false</code> otherwise
    *
    */
   public ContextMenuEvent(Component source, boolean fromClient,
                           @EventData("event.screenX") int screenX,
                           @EventData("event.screenY") int screenY,
                           @EventData("event.clientX") int clientX,
                           @EventData("event.clientY") int clientY,
                           @EventData("event.detail") int clickCount,
                           @EventData("event.button") int button,
                           @EventData("event.ctrlKey") boolean ctrlKey,
                           @EventData("event.shiftKey") boolean shiftKey,
                           @EventData("event.altKey") boolean altKey,
                           @EventData("event.metaKey") boolean metaKey) {
      super((C) source, fromClient);
      this.screenX = screenX;
      this.screenY = screenY;
      this.clientX = clientX;
      this.clientY = clientY;
      this.clickCount = clickCount;
      this.button = button;
      this.ctrlKey = ctrlKey;
      this.shiftKey = shiftKey;
      this.altKey = altKey;
      this.metaKey = metaKey;
   }

   /**
    * Creates a new server-side click event with no additional information.
    *
    * @param source
    *            the component that fired the event
    */
   public ContextMenuEvent(Component source) {
      // source, notClient, 4 coordinates, clickCount, button, 4 modifier keys
      this(source, false, -1, -1, -1, -1, 1, -1, false, false, false, false);
   }

   /**
    * Gets the x coordinate of the click event, relative to the upper left
    * corner of the browser viewport.
    *
    * @return the x coordinate, -1 if unknown
    */
   public int getClientX() {
      return clientX;
   }

   /**
    * Gets the y coordinate of the click event, relative to the upper left
    * corner of the browser viewport.
    *
    * @return the y coordinate, -1 if unknown
    */
   public int getClientY() {
      return clientY;
   }

   /**
    * Gets the x coordinate of the click event, relative to the upper left
    * corner of the screen.
    *
    * @return the x coordinate, -1 if unknown
    */
   public int getScreenX() {
      return screenX;
   }

   /**
    * Gets the y coordinate of the click event, relative to the upper left
    * corner of the screen.
    *
    * @return the y coordinate, -1 if unknown
    */
   public int getScreenY() {
      return screenY;
   }

   /**
    * Gets the number of consecutive clicks recently recorded.
    *
    * @return the click count
    */
   public int getClickCount() {
      return clickCount;
   }

   /**
    * Gets the id of the pressed mouse button.
    * <ul>
    * <li>-1: No button
    * <li>0: The primary button, typically the left mouse button
    * <li>1: The middle button,
    * <li>2: The secondary button, typically the right mouse button
    * <li>3: The first additional button, typically the back button
    * <li>4: The second additional button, typically the forward button
    * <li>5+: More additional buttons without any typical meanings
    * </ul>
    *
    * @return the button id, or -1 if no button was pressed
    */
   public int getButton() {
      return button;
   }

   /**
    * Checks whether the ctrl key was was down when the event was fired.
    *
    * @return <code>true</code> if the ctrl key was down when the event was
    *         fired, <code>false</code> otherwise
    */
   public boolean isCtrlKey() {
      return ctrlKey;
   }

   /**
    * Checks whether the alt key was was down when the event was fired.
    *
    * @return <code>true</code> if the alt key was down when the event was
    *         fired, <code>false</code> otherwise
    */
   public boolean isAltKey() {
      return altKey;
   }

   /**
    * Checks whether the meta key was was down when the event was fired.
    *
    * @return <code>true</code> if the meta key was down when the event was
    *         fired, <code>false</code> otherwise
    */
   public boolean isMetaKey() {
      return metaKey;
   }

   /**
    * Checks whether the shift key was was down when the event was fired.
    *
    * @return <code>true</code> if the shift key was down when the event was
    *         fired, <code>false</code> otherwise
    */
   public boolean isShiftKey() {
      return shiftKey;
   }

   @Override
   public String toString() {
      return "ContextMenuEvent{" +
              "screenX=" + screenX +
              ", screenY=" + screenY +
              ", clientX=" + clientX +
              ", clientY=" + clientY +
              ", clickCount=" + clickCount +
              ", button=" + button +
              ", ctrlKey=" + ctrlKey +
              ", shiftKey=" + shiftKey +
              ", altKey=" + altKey +
              ", metaKey=" + metaKey +
              '}';
   }

    /**
     * Adds a right-click/context-click listener to this component.
     *
     * @param listener
     *            the listener to add, not <code>null</code>
     * @return a handle that can be used for removing the listener
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    @NotNull
    public static <T extends Component> Registration addListenerTo(
            @NotNull T component,
            @NotNull ComponentEventListener<ContextMenuEvent<T>> listener) {
        return ComponentUtil.addListener(component, ContextMenuEvent.class,
                (ComponentEventListener) listener, d -> d.preventDefault());
    }
}

Now you can register the listener:

ContextMenuEvent.addListenerTo(button, e -> System.out.println(e));

The preventDefault() is missing for Vaadin 23, just use d.addEventData("event.preventDefault()").

Written on July 24, 2024