From a7b2de20d60ea157501d21d8e5179126571e7c36 Mon Sep 17 00:00:00 2001 From: Ilia Motornyi Date: Mon, 10 Jul 2017 15:04:22 +0300 Subject: [PATCH] Implement tree item context click Fixes #9606 Fixes #9645 Fixes #9647 --- .../client/ui/AbstractComponentConnector.java | 3 +- .../ui/composite/CompositeConnector.java | 8 ++ .../components/components-tree.asciidoc | 11 ++ .../java/com/vaadin/event/MouseEvents.java | 11 ++ .../communication/ServerRpcHandler.java | 6 +- server/src/main/java/com/vaadin/ui/Tree.java | 118 ++++++++++++++++-- .../contextclick/TreeV8ContextClick.java | 72 +++++++++++ .../contextclick/TreeV8ContextClickTest.java | 63 ++++++++++ 8 files changed, 281 insertions(+), 11 deletions(-) create mode 100644 uitest/src/main/java/com/vaadin/tests/contextclick/TreeV8ContextClick.java create mode 100644 uitest/src/test/java/com/vaadin/tests/contextclick/TreeV8ContextClickTest.java diff --git a/client/src/main/java/com/vaadin/client/ui/AbstractComponentConnector.java b/client/src/main/java/com/vaadin/client/ui/AbstractComponentConnector.java index 8ba94f9ba7..1eb9908335 100644 --- a/client/src/main/java/com/vaadin/client/ui/AbstractComponentConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/AbstractComponentConnector.java @@ -330,7 +330,8 @@ public abstract class AbstractComponentConnector extends AbstractConnector * interface. * * @since 7.6 - * @param event + * @param details + * @param eventTarget */ protected void sendContextClickEvent(MouseEventDetails details, EventTarget eventTarget) { diff --git a/client/src/main/java/com/vaadin/client/ui/composite/CompositeConnector.java b/client/src/main/java/com/vaadin/client/ui/composite/CompositeConnector.java index 93e42fab1f..916363cf23 100644 --- a/client/src/main/java/com/vaadin/client/ui/composite/CompositeConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/composite/CompositeConnector.java @@ -15,6 +15,7 @@ */ package com.vaadin.client.ui.composite; +import com.google.gwt.dom.client.EventTarget; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ComponentConnector; @@ -22,6 +23,7 @@ import com.vaadin.client.ConnectorHierarchyChangeEvent; import com.vaadin.client.HasComponentsConnector; import com.vaadin.client.ui.AbstractHasComponentsConnector; import com.vaadin.shared.AbstractComponentState; +import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.Connect.LoadStyle; import com.vaadin.ui.Composite; @@ -93,4 +95,10 @@ public class CompositeConnector extends AbstractHasComponentsConnector { ConnectorHierarchyChangeEvent event) { // Handled in getChildConnector } + + @Override + protected void sendContextClickEvent(MouseEventDetails details, EventTarget eventTarget) { + //Do nothing, because Composite is not an actual component, and the event + //must be handled in inner components. + } } diff --git a/documentation/components/components-tree.asciidoc b/documentation/components/components-tree.asciidoc index 03ea963170..cc44bec6ea 100644 --- a/documentation/components/components-tree.asciidoc +++ b/documentation/components/components-tree.asciidoc @@ -98,6 +98,17 @@ tree.addItemClickListener(event -> ); ---- +[[components.tree.right.clicks]] +=== Right-clicks +Right-clicks are supported similar way via `addContextClickListener()` method + +[source, java] +---- + tree.addContextClickListener(event -> Notification.show( + ((TreeContextClickEvent)event).getItem() + " Clicked") + ); +---- + [[components.tree.expandcollapse]] == Expanding and Collapsing Nodes diff --git a/server/src/main/java/com/vaadin/event/MouseEvents.java b/server/src/main/java/com/vaadin/event/MouseEvents.java index 76f14d0840..34456f2498 100644 --- a/server/src/main/java/com/vaadin/event/MouseEvents.java +++ b/server/src/main/java/com/vaadin/event/MouseEvents.java @@ -182,6 +182,17 @@ public interface MouseEvents { public String getButtonName() { return details.getButtonName(); } + + /** + * Returns an information about mouse event like position, buttons + * pressed etc. + * + * @since 8.1 + * @return An information about mouse event + */ + public MouseEventDetails getMouseEventDetails() { + return details; + } } /** diff --git a/server/src/main/java/com/vaadin/server/communication/ServerRpcHandler.java b/server/src/main/java/com/vaadin/server/communication/ServerRpcHandler.java index e4783cbf6e..f2caf5631c 100644 --- a/server/src/main/java/com/vaadin/server/communication/ServerRpcHandler.java +++ b/server/src/main/java/com/vaadin/server/communication/ServerRpcHandler.java @@ -591,10 +591,12 @@ public class ServerRpcHandler implements Serializable { * corresponding to the received method invocation has been * registered. */ - getLogger().warning("Ignoring RPC call to " + interfaceName + "." + String message = "Ignoring RPC call to " + interfaceName + "." + methodName + " in connector " + connector.getClass().getName() + "(" + connectorId - + ") as no RPC implementation is registered"); + + ") as no RPC implementation is registered"; + assert rpcManager != null : message; + getLogger().warning(message); return null; } diff --git a/server/src/main/java/com/vaadin/ui/Tree.java b/server/src/main/java/com/vaadin/ui/Tree.java index 6ae86efe37..be00417374 100644 --- a/server/src/main/java/com/vaadin/ui/Tree.java +++ b/server/src/main/java/com/vaadin/ui/Tree.java @@ -34,12 +34,14 @@ import com.vaadin.data.provider.TreeDataProvider; import com.vaadin.event.CollapseEvent; import com.vaadin.event.CollapseEvent.CollapseListener; import com.vaadin.event.ConnectorEvent; +import com.vaadin.event.ContextClickEvent; import com.vaadin.event.ExpandEvent; import com.vaadin.event.ExpandEvent.ExpandListener; import com.vaadin.event.SerializableEventListener; import com.vaadin.event.selection.SelectionListener; import com.vaadin.server.ErrorMessage; import com.vaadin.server.Resource; +import com.vaadin.shared.EventId; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.Registration; import com.vaadin.shared.ui.ContentMode; @@ -69,6 +71,7 @@ public class Tree extends Composite @Deprecated private static final Method ITEM_CLICK_METHOD = ReflectTools .findMethod(ItemClickListener.class, "itemClick", ItemClick.class); + private Registration contextClickRegistration = null; /** * A listener for item click events. @@ -483,7 +486,7 @@ public class Tree extends Composite * Adds a selection listener to the current selection model. *

* NOTE: If selection mode is switched with - * {@link setSelectionMode(SelectionMode)}, then this listener is not + * {@link #setSelectionMode(SelectionMode)}, then this listener is not * triggered anymore when selection changes! * * @param listener @@ -492,7 +495,7 @@ public class Tree extends Composite * * @throws UnsupportedOperationException * if selection has been disabled with - * {@link SelectionMode.NONE} + * {@link SelectionMode#NONE} */ public Registration addSelectionListener(SelectionListener listener) { return treeGrid.addSelectionListener(listener); @@ -630,6 +633,7 @@ public class Tree extends Composite * @param listener * the item click listener, not null * @return a registration for the listener + * @see #addContextClickListener */ public Registration addItemClickListener(ItemClickListener listener) { return addListener(ItemClick.class, listener, ITEM_CLICK_METHOD); @@ -655,12 +659,12 @@ public class Tree extends Composite Objects.requireNonNull(selectionMode, "Can not set selection mode to null"); switch (selectionMode) { - case MULTI: - TreeMultiSelectionModel model = new TreeMultiSelectionModel<>(); - treeGrid.setSelectionModel(model); - return model; - default: - return treeGrid.setSelectionMode(selectionMode); + case MULTI: + TreeMultiSelectionModel model = new TreeMultiSelectionModel<>(); + treeGrid.setSelectionModel(model); + return model; + default: + return treeGrid.setSelectionMode(selectionMode); } } @@ -811,4 +815,102 @@ public class Tree extends Composite public void setContentMode(ContentMode contentMode) { renderer.getState().mode = contentMode; } + + /** + * Adds a context click listener that gets notified when a context click + * happens. + * + * @param listener + * the context click listener to add, not null + * actual event provided to the listener is {@link TreeContextClickEvent} + * @return a registration object for removing the listener + * + * @since 8.1 + * @see #addItemClickListener + * @see Registration + */ + @Override + public Registration addContextClickListener(ContextClickEvent.ContextClickListener listener) { + Registration registration = + addListener(EventId.CONTEXT_CLICK, ContextClickEvent.class, + listener, ContextClickEvent.CONTEXT_CLICK_METHOD); + setupContextClickListener(); + return () -> { + registration.remove(); + setupContextClickListener(); + }; + } + + @Override + @Deprecated + public void removeContextClickListener(ContextClickEvent.ContextClickListener listener) { + super.removeContextClickListener(listener); + setupContextClickListener(); + } + + private void setupContextClickListener() { + if (hasListeners(ContextClickEvent.class)) { + if (contextClickRegistration == null) { + contextClickRegistration = treeGrid.addContextClickListener( + event -> { + T item = null; + if (event instanceof Grid.GridContextClickEvent) { + item = ((Grid.GridContextClickEvent) event).getItem(); + } + fireEvent(new TreeContextClickEvent<>(this, event.getMouseEventDetails(), item)); + } + ); + } + } else if (contextClickRegistration != null) { + contextClickRegistration.remove(); + contextClickRegistration = null; + } + } + + /** + * ContextClickEvent for the Tree Component. + *

+ * Usage: + *

+     * tree.addContextClickListener(event -> Notification.show(
+     *       ((TreeContextClickEvent<Person>)event).getItem() + " Clicked")
+     * );
+     * 
+ * + * @param the tree bean type + */ + public static class TreeContextClickEvent extends ContextClickEvent { + + private final T item; + + /** + * Creates a new context click event. + * + * @param source the tree where the context click occurred + * @param mouseEventDetails details about mouse position + * @param item the item which was clicked or {@code null} + * if the click happened outside any item + */ + public TreeContextClickEvent(Tree source, + MouseEventDetails mouseEventDetails, + T item) { + super(source, mouseEventDetails); + this.item = item; + } + + /** + * Returns the item of context clicked row. + * + * @return clicked item; {@code null} + * the click happened outside any item + */ + public T getItem() { + return item; + } + + @Override + public Tree getComponent() { + return (Tree) super.getComponent(); + } + } } diff --git a/uitest/src/main/java/com/vaadin/tests/contextclick/TreeV8ContextClick.java b/uitest/src/main/java/com/vaadin/tests/contextclick/TreeV8ContextClick.java new file mode 100644 index 0000000000..ed30eb47d6 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/contextclick/TreeV8ContextClick.java @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2016 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.tests.contextclick; + +import com.vaadin.data.TreeData; +import com.vaadin.shared.Registration; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Tree; +import com.vaadin.ui.Tree.TreeContextClickEvent; + +import java.util.Collections; + +public class TreeV8ContextClick extends + AbstractContextClickUI, TreeContextClickEvent> { + + @Override + protected Tree createTestComponent() { + TreeData treeData = new TreeData<>(); + for (int i = 0; i < 3; i++) { + String grandDad = "Granddad " + i; + treeData.addItems(null, grandDad); + for (int j = 0; j < 4; j++) { + String dad = "Dad " + i + "/" + j; + treeData.addItems(grandDad, dad); + for (int k = 0; k < 5; k++) { + treeData.addItems(dad, "Son " + i + "/" + j + "/" + k); + } + } + } + Tree tree = new Tree<>("Clane", treeData); + tree.setWidth("100%"); + return tree; + } + + @Override + protected void handleContextClickEvent( + TreeContextClickEvent event) { + String value = event.getItem(); + log("ContextClickEvent value: " + value); + } + + @Override + protected HorizontalLayout createContextClickControls() { + HorizontalLayout controls = super.createContextClickControls(); + controls.addComponent( + new Button("Remove all content", new Button.ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + testComponent.setItems(Collections.emptyList()); + testComponent.setHeight("200px"); + } + })); + return controls; + } + +} diff --git a/uitest/src/test/java/com/vaadin/tests/contextclick/TreeV8ContextClickTest.java b/uitest/src/test/java/com/vaadin/tests/contextclick/TreeV8ContextClickTest.java new file mode 100644 index 0000000000..3272cce334 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/contextclick/TreeV8ContextClickTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2000-2016 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.tests.contextclick; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.elements.TreeElement; +import org.junit.Test; +import org.openqa.selenium.WebElement; + +import static org.junit.Assert.assertEquals; + +public class TreeV8ContextClickTest extends AbstractContextClickTest { + + @Test + public void testBodyContextClickWithTypedListener() { + addOrRemoveTypedListener(); + + TreeElement tree = $(TreeElement.class).first(); + contextClick(tree.getItem(0)); + + assertEquals( + "1. ContextClickEvent value: Granddad 0", + getLogRow(0)); + + tree.expand(0); + tree.expand(2); + contextClick(tree.getItem(6)); + + assertEquals( + "2. ContextClickEvent value: Son 0/1/3", + getLogRow(0)); + } + + /** + * Performs a context click on given element at coordinates 20, 10 followed + * by a regular click. This prevents browser context menu from blocking + * future operations. + * + * A smaller X offset might hit the resize handle of the previous cell that + * overlaps with the next header cell. + * + * @param e + * web element + */ + @Override + protected void contextClick(WebElement e) { + contextClick(e, 20, 10); + } + +} -- 2.39.5