Skip to content

Instantly share code, notes, and snippets.

@TatuLund
Created December 13, 2023 14:47
Show Gist options
  • Save TatuLund/a94cdc093b836fb68b080e5525e5823d to your computer and use it in GitHub Desktop.
Save TatuLund/a94cdc093b836fb68b080e5525e5823d to your computer and use it in GitHub Desktop.
Example of how to open GridContextMenu with the left click on the icon while opening it with the right click elsewhere.
package com.example.application.views;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Focusable;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.Shortcuts;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.Grid.Column;
import com.vaadin.flow.component.grid.Grid.SelectionMode;
import com.vaadin.flow.component.grid.contextmenu.GridContextMenu;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.textfield.TextFieldVariant;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.router.Route;
@Route(value = "grid", layout = MainLayout.class)
public class GridContextMenuView extends Div {
Grid<Pojo> grid = new Grid<>();
private Optional<Pojo> item;
private Optional<Column<Pojo>> column;
private GridContextMenu<Pojo> menu;
public GridContextMenuView() {
TextField valueField = new TextField();
valueField.setWidth("100%");
TextField textField = new TextField();
textField.setWidth("100%");
valueField.addThemeVariants(TextFieldVariant.LUMO_SMALL);
textField.addThemeVariants(TextFieldVariant.LUMO_SMALL);
valueField.setAutoselect(true);
textField.setAutoselect(true);
grid.getStyle().set("--lumo-space-xs", "1px");
grid.getStyle().set("--lumo-space-m", "1px");
Binder<Pojo> binder = new Binder<>();
binder.forField(valueField).bind(Pojo::getValue, Pojo::setValue);
binder.forField(textField).bind(Pojo::getText, Pojo::setText);
grid.addColumn(Pojo::getValue).setEditorComponent(valueField)
.setHeader("Value").setSortable(true).setResizable(true);
grid.addColumn(Pojo::getText).setEditorComponent(textField)
.setHeader("Text").setSortable(true).setResizable(true);
grid.getEditor().setBinder(binder);
grid.getEditor().setBuffered(true);
grid.setSelectionMode(SelectionMode.NONE);
menu = grid.addContextMenu();
grid.addContextMenu().addItem("Edit", e -> {
if (grid.getEditor().isOpen()) {
grid.getEditor().save();
}
e.getItem().ifPresent(pojo -> {
grid.getEditor().editItem(pojo);
});
});
grid.addCellFocusListener(e -> {
item = e.getItem();
column = e.getColumn();
item.ifPresentOrElse(pojo -> {
}, () -> grid.getEditor().save());
});
grid.addComponentColumn(item -> {
Icon icon = VaadinIcon.MENU.create();
icon.addClickListener(e -> icon.getElement()
.executeJs("const y = this.getBoundingClientRect().top;"
+ "const x = this.getBoundingClientRect().left;"
+ "const ev = new MouseEvent('contextmenu', {"
+ " 'view': window,"
+ " 'bubbles': true,"
+ " 'button': 2,"
+ " 'cancelable': true,"
+ " 'x': x,"
+ " 'y': y"
+ "});"
+ "this.dispatchEvent(ev);"));
return icon;
}).setFrozenToEnd(true);
grid.setWidth("400px");
grid.setItems(IntStream.range(1, 1000).mapToObj(i -> randomString())
.map(s -> new Pojo(s)).collect(Collectors.toList()));
Shortcuts.addShortcutListener(grid, e -> {
item.ifPresent(item -> {
if (grid.getEditor().isOpen()) {
grid.getEditor().save();
} else {
grid.getEditor().editItem(item);
column.ifPresent(column -> {
Component component = column.getEditorComponent();
if (component instanceof Focusable) {
Focusable<?> field = (Focusable<?>) component;
field.focus();
}
});
}
});
}, Key.ENTER).listenOn(grid);
add(grid);
}
// Create String of 4 random characters from the set [a-z]
private static String randomString() {
Random random = new Random();
return random.ints(4, 97, 123).mapToObj(i -> String.valueOf((char) i))
.collect(Collectors.joining());
}
public class Pojo {
private String value;
private String text = randomString().toUpperCase();
public Pojo(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
}
@myronenkom
Copy link

myronenkom commented Sep 4, 2024

Hi @TatuLund
regarding this part

icon.addClickListener(e -> icon.getElement()
                    .executeJs("const y = this.getBoundingClientRect().top;"
                            + "const x = this.getBoundingClientRect().left;"
                            + "const ev = new MouseEvent('contextmenu', {"
                            + "    'view': window,"
                            + "    'bubbles': true,"
                            + "    'button': 2,"
                            + "    'cancelable': true,"
                            + "    'x': x,"
                            + "    'y': y"
                            + "});"
                            + "this.dispatchEvent(ev);"));

Instead of fetching x and y in JS code I suppose we can use these ClickEvent properties: e.getScreenX and e.getScreenY, worked for me.

However I continue to believe that this is still just a workaround and some official, standardized approach would be preferable.

@TatuLund
Copy link
Author

TatuLund commented Sep 4, 2024

I suppose we can use these ClickEvent properties: e.getScreenX and e.getScreenY, worked for me.

Yes. There are variations of the approach you can use.

There is feature request here: vaadin/flow-components#3606

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment