import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler;
import com.vaadin.client.DirectionalManagedLayout;
import com.vaadin.client.HasComponentsConnector;
+import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.Paintable;
import com.vaadin.client.ServerConnector;
import com.vaadin.client.TooltipInfo;
import com.vaadin.client.ui.PostLayoutListener;
import com.vaadin.client.ui.VScrollTable;
import com.vaadin.client.ui.VScrollTable.ContextMenuDetails;
+import com.vaadin.client.ui.VScrollTable.FooterCell;
+import com.vaadin.client.ui.VScrollTable.HeaderCell;
import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
+import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.table.TableConstants;
+import com.vaadin.shared.ui.table.TableConstants.Section;
+import com.vaadin.shared.ui.table.TableServerRpc;
import com.vaadin.shared.ui.table.TableState;
@Connect(com.vaadin.ui.Table.class)
getWidget().onUnregister();
}
+ @Override
+ protected void sendContextClickEvent(ContextMenuEvent event) {
+ EventTarget eventTarget = event.getNativeEvent().getEventTarget();
+ if (!Element.is(eventTarget)) {
+ super.sendContextClickEvent(event);
+ return;
+ }
+ Element e = Element.as(eventTarget);
+
+ Section section;
+ String colKey;
+ String rowKey = null;
+ if (getWidget().tFoot.getElement().isOrHasChild(e)) {
+ section = Section.FOOTER;
+ FooterCell w = WidgetUtil.findWidget(e, FooterCell.class);
+ colKey = w.getColKey();
+ } else if (getWidget().tHead.getElement().isOrHasChild(e)) {
+ section = Section.HEADER;
+ HeaderCell w = WidgetUtil.findWidget(e, HeaderCell.class);
+ colKey = w.getColKey();
+ } else if (getWidget().scrollBody.getElement().isOrHasChild(e)) {
+ section = Section.BODY;
+ VScrollTableRow w = WidgetUtil.findWidget(e, VScrollTableRow.class);
+ rowKey = w.getKey();
+ colKey = getWidget().tHead.getHeaderCell(
+ getElementIndex(e, w.getElement())).getColKey();
+ } else {
+ super.sendContextClickEvent(event);
+ return;
+ }
+
+ MouseEventDetails details = MouseEventDetailsBuilder
+ .buildMouseEventDetails(event.getNativeEvent());
+
+ // Prevent browser default context menu
+ event.preventDefault();
+ event.stopPropagation();
+
+ getRpcProxy(TableServerRpc.class).contextClick(rowKey, colKey, section,
+ details);
+ }
+
+ private int getElementIndex(Element e,
+ com.google.gwt.user.client.Element element) {
+ int i = 0;
+ Element current = element.getFirstChildElement();
+ while (!current.isOrHasChild(e)) {
+ current = current.getNextSiblingElement();
+ ++i;
+ }
+ return i;
+ }
+
/*
* (non-Javadoc)
*
import com.vaadin.data.util.converter.ConverterUtil;
import com.vaadin.event.Action;
import com.vaadin.event.Action.Handler;
+import com.vaadin.event.ContextClickEvent;
import com.vaadin.event.DataBoundTransferable;
import com.vaadin.event.ItemClickEvent;
import com.vaadin.event.ItemClickEvent.ItemClickListener;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.MultiSelectMode;
import com.vaadin.shared.ui.table.TableConstants;
+import com.vaadin.shared.ui.table.TableConstants.Section;
+import com.vaadin.shared.ui.table.TableServerRpc;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
*/
public Table() {
setRowHeaderMode(ROW_HEADER_MODE_HIDDEN);
+
+ registerRpc(new TableServerRpc() {
+
+ @Override
+ public void contextClick(String rowKey, String colKey,
+ Section section, MouseEventDetails details) {
+ Object itemId = itemIdMapper.get(rowKey);
+ Object propertyId = columnIdMap.get(colKey);
+ fireEvent(new TableContextClickEvent(Table.this, details,
+ itemId, propertyId, section));
+ }
+ });
}
/**
return result;
}
+ /* Context Click handling */
+
+ public static class TableContextClickEvent extends ContextClickEvent {
+
+ private final Object itemId;
+ private final Object propertyId;
+ private final Section section;
+
+ public TableContextClickEvent(Table source,
+ MouseEventDetails mouseEventDetails, Object itemId,
+ Object propertyId, Section section) {
+ super(source, mouseEventDetails);
+
+ this.itemId = itemId;
+ this.propertyId = propertyId;
+ this.section = section;
+ }
+
+ /**
+ * Returns the item id of context clicked row.
+ *
+ * @return item id of clicked row; <code>null</code> if header or footer
+ */
+ public Object getItemId() {
+ return itemId;
+ }
+
+ /**
+ * Returns the property id of context clicked column.
+ *
+ * @return property id
+ */
+ public Object getPropertyId() {
+ return propertyId;
+ }
+
+ /**
+ * Returns the clicked section of Table.
+ *
+ * @return section of Table
+ */
+ public Section getSection() {
+ return section;
+ }
+
+ @Override
+ public Table getComponent() {
+ return (Table) super.getComponent();
+ }
+ }
+
private final Logger getLogger() {
if (logger == null) {
logger = Logger.getLogger(Table.class.getName());
--- /dev/null
+/*
+ * Copyright 2000-2014 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.server.component.table;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.event.ContextClickEvent;
+import com.vaadin.event.ContextClickEvent.ContextClickListener;
+import com.vaadin.shared.ui.table.TableConstants.Section;
+import com.vaadin.ui.Table;
+
+public class TableContextClickTest extends Table {
+
+ private String error = null;
+ private boolean handled = false;
+
+ @Test
+ public void testContextClickListenerWithTableEvent() {
+ addContextClickListener(new ContextClickListener() {
+
+ @Override
+ public void contextClick(ContextClickEvent event) {
+ if (!(event instanceof TableContextClickEvent)) {
+ return;
+ }
+
+ TableContextClickEvent e = (TableContextClickEvent) event;
+ if (e.getSection() != Section.BODY) {
+ error = "Event section was not BODY.";
+ }
+ handled = true;
+ }
+ });
+ fireEvent(new TableContextClickEvent(this, null, null, null,
+ Section.BODY));
+
+ if (error != null) {
+ Assert.fail(error);
+ } else if (!handled) {
+ Assert.fail("Event was not handled by the ContextClickListener");
+ }
+ }
+}
import java.io.Serializable;
public class TableConstants implements Serializable {
+ /**
+ * Enum describing different sections of Table.
+ */
+ public enum Section {
+ HEADER, BODY, FOOTER
+ }
+
public static final String ITEM_CLICK_EVENT_ID = "itemClick";
public static final String HEADER_CLICK_EVENT_ID = "handleHeaderClick";
public static final String FOOTER_CLICK_EVENT_ID = "handleFooterClick";
--- /dev/null
+/*
+ * Copyright 2000-2014 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.shared.ui.table;
+
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.communication.ServerRpc;
+import com.vaadin.shared.ui.table.TableConstants.Section;
+
+/**
+ * Client-to-server RPC interface for the Table component
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public interface TableServerRpc extends ServerRpc {
+
+ /**
+ * Informs the server that a context click happened inside of Table
+ */
+ public void contextClick(String rowKey, String colKey, Section section,
+ MouseEventDetails details);
+
+}
--- /dev/null
+/*
+ * Copyright 2000-2014 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 static org.junit.Assert.assertEquals;
+
+import java.util.List;
+
+import org.junit.Test;
+import org.openqa.selenium.Point;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.testbench.elements.AbstractComponentElement;
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.parallel.BrowserUtil;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public abstract class AbstractContextClickTest extends MultiBrowserTest {
+
+ @Override
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ return getBrowsersSupportingContextMenu();
+ }
+
+ @Test
+ public void testDefaultListener() {
+ openTestURL();
+
+ addOrRemoveDefaultListener();
+
+ assertDefaultContextClickListener(1);
+ }
+
+ protected void assertNoContextClickHandler() {
+ AbstractComponentElement component = $(AbstractComponentElement.class)
+ .id("testComponent");
+
+ String log = getLogRow(0);
+
+ contextClick(component);
+
+ assertEquals("Log entry without a context click listener.", log,
+ getLogRow(0));
+
+ }
+
+ protected void assertDefaultContextClickListener(int index) {
+ AbstractComponentElement component = $(AbstractComponentElement.class)
+ .id("testComponent");
+
+ contextClick(component);
+
+ Point l = component.getLocation();
+
+ int drift = 0;
+ // IE 10 and 11 report different Y location.
+ if (BrowserUtil.isIE(getDesiredCapabilities(), 10)
+ || BrowserUtil.isIE(getDesiredCapabilities(), 11)) {
+ drift = 1;
+ }
+
+ assertEquals(index + ". ContextClickEvent: (" + (l.getX() + 10) + ", "
+ + (l.getY() + 10 + drift) + ")", getLogRow(0));
+ }
+
+ protected void addOrRemoveDefaultListener() {
+ $(ButtonElement.class).caption("Add/Remove default listener").first()
+ .click();
+ }
+
+ protected void addOrRemoveTypedListener() {
+ $(ButtonElement.class).caption("Add/Remove typed listener").first()
+ .click();
+ }
+
+ /**
+ * Performs a context click followed by a regular click. This prevents
+ * browser context menu from blocking future operations.
+ *
+ * @param e
+ * web element
+ */
+ protected void contextClick(WebElement e) {
+ new Actions(getDriver()).moveToElement(e, 10, 10).contextClick()
+ .perform();
+ new Actions(getDriver()).moveToElement(e, 5, 5).click().perform();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2014 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.annotations.Theme;
+import com.vaadin.event.ContextClickEvent;
+import com.vaadin.event.ContextClickEvent.ContextClickListener;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+
+@Theme("valo")
+public abstract class AbstractContextClickUI<T extends AbstractComponent, E extends ContextClickEvent>
+ extends AbstractTestUIWithLog {
+
+ private final class ListenerHandler implements Button.ClickListener {
+ private boolean hasListener = false;
+ private final ContextClickListener listener;
+
+ public ListenerHandler(ContextClickListener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public void buttonClick(ClickEvent event) {
+ if (!hasListener) {
+ testComponent.addContextClickListener(listener);
+ event.getButton().setDescription("Remove listener");
+ hasListener = true;
+ } else {
+ testComponent.removeContextClickListener(listener);
+ event.getButton().setDescription("Add listener");
+ hasListener = false;
+ }
+ }
+ }
+
+ protected T testComponent;
+ private ContextClickListener defaultListener = new ContextClickListener() {
+
+ @Override
+ public void contextClick(ContextClickEvent event) {
+ log("ContextClickEvent: (" + event.getClientX() + ", "
+ + event.getClientY() + ")");
+ }
+ };
+
+ private ContextClickListener typedListener = new ContextClickListener() {
+
+ @Override
+ public void contextClick(ContextClickEvent event) {
+ try {
+ E typedEvent = (E) event;
+ handleContextClickEvent(typedEvent);
+ } catch (Exception e) {
+ log("UNEXPECTED EVENT TYPE!");
+ }
+ }
+ };
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ testComponent = createTestComponent();
+ testComponent.setId("testComponent");
+
+ addComponent(testComponent);
+
+ addComponent(new Button("Add/Remove default listener",
+ new ListenerHandler(defaultListener)));
+ addComponent(new Button("Add/Remove typed listener",
+ new ListenerHandler(typedListener)));
+ }
+
+ protected abstract T createTestComponent();
+
+ protected abstract void handleContextClickEvent(E event);
+}
--- /dev/null
+/*
+ * Copyright 2000-2014 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 org.junit.Test;
+
+public class ListenerAddAndRemoveTest extends TableContextClickTestBase {
+
+ @Test
+ public void testAddAndRemoveListeners() {
+ openTestURL();
+
+ // Add typed listener
+ addOrRemoveTypedListener();
+
+ // Add default listener
+ addOrRemoveDefaultListener();
+
+ // Remove the default listener
+ addOrRemoveDefaultListener();
+
+ // Check that typed listener is still working
+ assertTypedContextClickListener(1);
+
+ // Re-add the default listener
+ addOrRemoveDefaultListener();
+
+ // Remove typed listener
+ addOrRemoveTypedListener();
+
+ // Check that default listener still works
+ assertDefaultContextClickListener(3);
+
+ // Remove default listener
+ addOrRemoveDefaultListener();
+
+ // Assert no listeners present.
+ assertNoContextClickHandler();
+
+ // Re-add typed listener
+ addOrRemoveTypedListener();
+
+ assertTypedContextClickListener(4);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2014 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.Item;
+import com.vaadin.shared.ui.table.TableConstants.Section;
+import com.vaadin.tests.util.PersonContainer;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.Table.TableContextClickEvent;
+
+public class TableContextClick extends
+ AbstractContextClickUI<Table, TableContextClickEvent> {
+
+ @Override
+ protected Table createTestComponent() {
+ Table table = new Table();
+ table.setContainerDataSource(PersonContainer.createWithTestData());
+ table.setFooterVisible(true);
+ return table;
+ }
+
+ @Override
+ protected void handleContextClickEvent(TableContextClickEvent event) {
+ String value = "";
+ Object propertyId = event.getPropertyId();
+ if (event.getItemId() != null) {
+ Item item = event.getComponent().getContainerDataSource()
+ .getItem(event.getItemId());
+ value += item.getItemProperty("firstName").getValue() + " ";
+ value += item.getItemProperty("lastName").getValue();
+ } else if (event.getSection() == Section.HEADER) {
+ value = testComponent.getColumnHeader(propertyId);
+ } else if (event.getSection() == Section.FOOTER) {
+ value = testComponent.getColumnFooter(propertyId);
+ }
+ log("ContextClickEvent value: " + value + ", propertyId: " + propertyId
+ + ", section: " + event.getSection());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2014 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 static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.vaadin.testbench.elements.TableElement;
+
+public class TableContextClickTest extends TableContextClickTestBase {
+
+ @Test
+ public void testBodyContextClickWithTypedListener() {
+ openTestURL();
+
+ addOrRemoveTypedListener();
+
+ assertTypedContextClickListener(1);
+ }
+
+ @Test
+ public void testHeaderContextClickWithTypedListener() {
+ openTestURL();
+
+ addOrRemoveTypedListener();
+
+ contextClick($(TableElement.class).first().getHeaderCell(0));
+
+ assertEquals(
+ "1. ContextClickEvent value: address, propertyId: address, section: HEADER",
+ getLogRow(0));
+
+ contextClick($(TableElement.class).first().getHeaderCell(3));
+
+ assertEquals(
+ "2. ContextClickEvent value: lastName, propertyId: lastName, section: HEADER",
+ getLogRow(0));
+ }
+
+ @Test
+ public void testFooterContextClickWithTypedListener() {
+ openTestURL();
+
+ addOrRemoveTypedListener();
+
+ contextClick($(TableElement.class).first().getFooterCell(0));
+
+ assertEquals(
+ "1. ContextClickEvent value: null, propertyId: address, section: FOOTER",
+ getLogRow(0));
+
+ contextClick($(TableElement.class).first().getFooterCell(3));
+
+ assertEquals(
+ "2. ContextClickEvent value: null, propertyId: lastName, section: FOOTER",
+ getLogRow(0));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2000-2014 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 static org.junit.Assert.assertEquals;
+
+import com.vaadin.testbench.elements.TableElement;
+
+public abstract class TableContextClickTestBase extends
+ AbstractContextClickTest {
+
+ @Override
+ protected Class<?> getUIClass() {
+ return TableContextClick.class;
+ }
+
+ protected void assertTypedContextClickListener(int startIndex) {
+ contextClick($(TableElement.class).first().getCell(0, 0));
+
+ assertEquals(
+ (startIndex++)
+ + ". ContextClickEvent value: Lisa Schneider, propertyId: address, section: BODY",
+ getLogRow(0));
+
+ contextClick($(TableElement.class).first().getCell(0, 3));
+
+ assertEquals(
+ startIndex
+ + ". ContextClickEvent value: Lisa Schneider, propertyId: lastName, section: BODY",
+ getLogRow(0));
+ }
+}