summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenrik Paul <henrik@vaadin.com>2015-03-09 14:31:37 +0200
committerHenrik Paul <henrik@vaadin.com>2015-03-17 12:59:05 +0200
commit84c143dd76ed1d27d03c0d695e7218b477d008fe (patch)
tree1070f41763d46572af72eb230f18c9d3ce610b48
parentf61cf666f3d28ac57b6c3cd5de30d9b54814d683 (diff)
downloadvaadin-framework-84c143dd76ed1d27d03c0d695e7218b477d008fe.tar.gz
vaadin-framework-84c143dd76ed1d27d03c0d695e7218b477d008fe.zip
Server side Grid can open details on the client side (#16644)
Change-Id: Ibff5a83b3a09c7c530926dadae9138ba3823f27a
-rw-r--r--client/src/com/vaadin/client/connectors/GridConnector.java26
-rw-r--r--client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java53
-rw-r--r--client/src/com/vaadin/client/widget/grid/DetailsGenerator.java1
-rw-r--r--client/src/com/vaadin/client/widgets/Grid.java7
-rw-r--r--server/src/com/vaadin/data/RpcDataProviderExtension.java72
-rw-r--r--server/src/com/vaadin/ui/Grid.java91
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/GridState.java10
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java14
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java76
9 files changed, 341 insertions, 9 deletions
diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java
index 55f07ecf85..f476982c15 100644
--- a/client/src/com/vaadin/client/connectors/GridConnector.java
+++ b/client/src/com/vaadin/client/connectors/GridConnector.java
@@ -31,6 +31,7 @@ import java.util.logging.Logger;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
@@ -45,6 +46,7 @@ import com.vaadin.client.ui.AbstractHasComponentsConnector;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.client.widget.grid.CellReference;
import com.vaadin.client.widget.grid.CellStyleGenerator;
+import com.vaadin.client.widget.grid.DetailsGenerator;
import com.vaadin.client.widget.grid.EditorHandler;
import com.vaadin.client.widget.grid.RowReference;
import com.vaadin.client.widget.grid.RowStyleGenerator;
@@ -101,7 +103,7 @@ import elemental.json.JsonValue;
*/
@Connect(com.vaadin.ui.Grid.class)
public class GridConnector extends AbstractHasComponentsConnector implements
- SimpleManagedLayout {
+ SimpleManagedLayout, RpcDataSourceConnector.DetailsListener {
private static final class CustomCellStyleGenerator implements
CellStyleGenerator<JsonObject> {
@@ -360,6 +362,14 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
}
+ private class CustomDetailsGenerator implements DetailsGenerator {
+ @Override
+ public Widget getDetails(int rowIndex) {
+ // TODO
+ return new Label("[todo]");
+ }
+ }
+
/**
* Maps a generated column id to a grid column instance
*/
@@ -501,7 +511,11 @@ public class GridConnector extends AbstractHasComponentsConnector implements
});
getWidget().setEditorHandler(new CustomEditorHandler());
+
+ getWidget().setDetailsGenerator(new CustomDetailsGenerator());
+
getLayoutManager().registerDependency(this, getWidget().getElement());
+
layout();
}
@@ -994,4 +1008,14 @@ public class GridConnector extends AbstractHasComponentsConnector implements
public void layout() {
getWidget().onResize();
}
+
+ @Override
+ public void reapplyDetailsVisibility(int rowIndex, JsonObject row) {
+ if (row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE)
+ && row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE)) {
+ getWidget().setDetailsVisible(rowIndex, true);
+ } else {
+ getWidget().setDetailsVisible(rowIndex, false);
+ }
+ }
}
diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
index f8d6ebcb62..ae4249de78 100644
--- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
+++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
@@ -17,6 +17,7 @@
package com.vaadin.client.connectors;
import java.util.ArrayList;
+import java.util.List;
import com.vaadin.client.ServerConnector;
import com.vaadin.client.data.AbstractRemoteDataSource;
@@ -43,6 +44,28 @@ import elemental.json.JsonObject;
@Connect(com.vaadin.data.RpcDataProviderExtension.class)
public class RpcDataSourceConnector extends AbstractExtensionConnector {
+ /**
+ * A callback interface to let {@link GridConnector} know that detail
+ * visibilities might have changed.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+ interface DetailsListener {
+
+ /**
+ * A request to verify (and correct) the visibility for a row, given
+ * updated metadata.
+ *
+ * @param rowIndex
+ * the index of the row that should be checked
+ * @param row
+ * the row object to check visibility for
+ * @see GridState#JSONKEY_DETAILS_VISIBLE
+ */
+ void reapplyDetailsVisibility(int rowIndex, JsonObject row);
+ }
+
public class RpcDataSource extends AbstractRemoteDataSource<JsonObject> {
protected RpcDataSource() {
@@ -56,27 +79,28 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
rows.add(rowObject);
}
- dataSource.setRowData(firstRow, rows);
+ RpcDataSource.this.setRowData(firstRow, rows);
}
@Override
public void removeRowData(int firstRow, int count) {
- dataSource.removeRowData(firstRow, count);
+ RpcDataSource.this.removeRowData(firstRow, count);
}
@Override
public void insertRowData(int firstRow, int count) {
- dataSource.insertRowData(firstRow, count);
+ RpcDataSource.this.insertRowData(firstRow, count);
}
@Override
public void resetDataAndSize(int size) {
- dataSource.resetDataAndSize(size);
+ RpcDataSource.this.resetDataAndSize(size);
}
});
}
private DataRequestRpc rpcProxy = getRpcProxy(DataRequestRpc.class);
+ private DetailsListener detailsListener;
@Override
protected void requestRows(int firstRowIndex, int numberOfRows,
@@ -170,7 +194,24 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
if (!handle.isPinned()) {
rpcProxy.setPinned(key, false);
}
+ }
+
+ void setDetailsListener(DetailsListener detailsListener) {
+ this.detailsListener = detailsListener;
+ }
+ @Override
+ protected void setRowData(int firstRowIndex, List<JsonObject> rowData) {
+ super.setRowData(firstRowIndex, rowData);
+
+ /*
+ * Intercepting details information from the data source, rerouting
+ * them back to the GridConnector (as a details listener)
+ */
+ for (int i = 0; i < rowData.size(); i++) {
+ detailsListener.reapplyDetailsVisibility(firstRowIndex + i,
+ rowData.get(i));
+ }
}
}
@@ -178,6 +219,8 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
@Override
protected void extend(ServerConnector target) {
- ((GridConnector) target).setDataSource(dataSource);
+ GridConnector gridConnector = (GridConnector) target;
+ dataSource.setDetailsListener(gridConnector);
+ gridConnector.setDataSource(dataSource);
}
}
diff --git a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java
index 264aa4e614..309e3f1ea3 100644
--- a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java
+++ b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java
@@ -25,6 +25,7 @@ import com.google.gwt.user.client.ui.Widget;
*/
public interface DetailsGenerator {
+ /** A details generator that provides no details */
public static final DetailsGenerator NULL = new DetailsGenerator() {
@Override
public Widget getDetails(int rowIndex) {
diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java
index b3906591c0..f4aaf798b7 100644
--- a/client/src/com/vaadin/client/widgets/Grid.java
+++ b/client/src/com/vaadin/client/widgets/Grid.java
@@ -6326,10 +6326,17 @@ public class Grid<T> extends ResizeComposite implements
* @since
* @param detailsGenerator
* the details generator to set
+ * @throws IllegalArgumentException
+ * if detailsGenerator is <code>null</code>;
*/
public void setDetailsGenerator(DetailsGenerator detailsGenerator)
throws IllegalArgumentException {
+ if (detailsGenerator == null) {
+ throw new IllegalArgumentException(
+ "Details generator may not be null");
+ }
+
this.detailsGenerator = detailsGenerator;
// this will refresh all visible spacers
diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java
index 991cb0537d..cf2284a62e 100644
--- a/server/src/com/vaadin/data/RpcDataProviderExtension.java
+++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java
@@ -95,7 +95,7 @@ public class RpcDataProviderExtension extends AbstractExtension {
// private implementation
}
- void setActiveRange(Range newActiveRange) {
+ public void setActiveRange(Range newActiveRange) {
final Range[] removed = activeRange.partitionWith(newActiveRange);
final Range[] added = newActiveRange.partitionWith(activeRange);
@@ -163,7 +163,7 @@ public class RpcDataProviderExtension extends AbstractExtension {
return String.valueOf(rollingIndex++);
}
- String getKey(Object itemId) {
+ public String getKey(Object itemId) {
String key = itemIdToKey.get(itemId);
if (key == null) {
key = nextKey();
@@ -241,6 +241,20 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
/**
+ * Gets the row index for a given item.
+ *
+ * @since
+ * @param itemId
+ * the item id of the item for which to get the item
+ * @return the index of the item, or -1 if no such item could be found
+ */
+ @SuppressWarnings("boxing")
+ public int getIndex(Object itemId) {
+ Integer integer = indexToItemId.inverse().get(itemId);
+ return integer != null ? integer : -1;
+ }
+
+ /**
* Pin an item id to be cached indefinitely.
* <p>
* Normally when an itemId is not an active row, it is discarded from
@@ -304,7 +318,7 @@ public class RpcDataProviderExtension extends AbstractExtension {
return pinnedItemIds.contains(itemId);
}
- Object itemIdAtIndex(int index) {
+ private Object itemIdAtIndex(int index) {
return indexToItemId.get(Integer.valueOf(index));
}
}
@@ -728,6 +742,12 @@ public class RpcDataProviderExtension extends AbstractExtension {
private boolean bareItemSetTriggeredSizeChange = false;
/**
+ * This map represents all the details that are user-defined as visible.
+ * This does not reflect the status in the DOM.
+ */
+ private Set<Object> visibleDetails = new HashSet<Object>();
+
+ /**
* Creates a new data provider using the given container.
*
* @param container
@@ -859,6 +879,10 @@ public class RpcDataProviderExtension extends AbstractExtension {
rowObject.put(GridState.JSONKEY_DATA, rowData);
rowObject.put(GridState.JSONKEY_ROWKEY, keyMapper.getKey(itemId));
+ if (visibleDetails.contains(itemId)) {
+ rowObject.put(GridState.JSONKEY_DETAILS_VISIBLE, true);
+ }
+
rowReference.set(itemId);
CellStyleGenerator cellStyleGenerator = grid.getCellStyleGenerator();
@@ -1116,4 +1140,46 @@ public class RpcDataProviderExtension extends AbstractExtension {
return Logger.getLogger(RpcDataProviderExtension.class.getName());
}
+ /**
+ * Marks a row's details to be visible or hidden.
+ * <p>
+ * If that row is currently in the client side's cache, this information
+ * will be sent over to the client.
+ *
+ * @since
+ * @param itemId
+ * the id of the item of which to change the details visibility
+ * @param visible
+ * <code>true</code> to show the details, <code>false</code> to
+ * hide
+ */
+ public void setDetailsVisible(Object itemId, boolean visible) {
+ final boolean modified;
+ if (visible) {
+ modified = visibleDetails.add(itemId);
+ } else {
+ modified = visibleDetails.remove(itemId);
+ }
+
+ int rowIndex = keyMapper.getIndex(itemId);
+ boolean modifiedRowIsActive = activeRowHandler.activeRange
+ .contains(rowIndex);
+ if (modified && modifiedRowIsActive) {
+ updateRowData(itemId);
+ }
+ }
+
+ /**
+ * Checks whether the details for a row is marked as visible.
+ *
+ * @since
+ * @param itemId
+ * the id of the item of which to check the visibility
+ * @return <code>true</code> iff the detials are visible for the item. This
+ * might return <code>true</code> even if the row is not currently
+ * visible in the DOM
+ */
+ public boolean isDetailsVisible(Object itemId) {
+ return visibleDetails.contains(itemId);
+ }
}
diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java
index 22ef0333c2..b56bb0d036 100644
--- a/server/src/com/vaadin/ui/Grid.java
+++ b/server/src/com/vaadin/ui/Grid.java
@@ -165,6 +165,34 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
SortNotifier, SelectiveRenderer, ItemClickNotifier {
/**
+ * A callback interface for generating details for a particular row in Grid.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+ public interface DetailsGenerator extends Serializable {
+
+ /** A details generator that provides no details */
+ public DetailsGenerator NULL = new DetailsGenerator() {
+ @Override
+ public Component getDetails(RowReference rowReference) {
+ return null;
+ }
+ };
+
+ /**
+ * This method is called for whenever a new details row needs to be
+ * generated.
+ *
+ * @param rowReference
+ * the reference for the row for which to generate details
+ * @return the details for the given row, or <code>null</code> to leave
+ * the details empty.
+ */
+ Component getDetails(RowReference rowReference);
+ }
+
+ /**
* Custom field group that allows finding property types before an item has
* been bound.
*/
@@ -2888,6 +2916,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
private EditorErrorHandler editorErrorHandler = new DefaultEditorErrorHandler();
+ private DetailsGenerator detailsGenerator = DetailsGenerator.NULL;
+
private static final Method SELECTION_CHANGE_METHOD = ReflectTools
.findMethod(SelectionListener.class, "select", SelectionEvent.class);
@@ -5093,4 +5123,65 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
public void recalculateColumnWidths() {
getRpcProxy(GridClientRpc.class).recalculateColumnWidths();
}
+
+ /**
+ * Sets a new details generator for row details.
+ * <p>
+ * The currently opened row details will be re-rendered.
+ *
+ * @since
+ * @param detailsGenerator
+ * the details generator to set
+ * @throws IllegalArgumentException
+ * if detailsGenerator is <code>null</code>;
+ */
+ public void setDetailsGenerator(DetailsGenerator detailsGenerator)
+ throws IllegalArgumentException {
+ if (detailsGenerator == null) {
+ throw new IllegalArgumentException(
+ "Details generator may not be null");
+ } else if (detailsGenerator == this.detailsGenerator) {
+ return;
+ }
+
+ this.detailsGenerator = detailsGenerator;
+
+ getLogger().warning("[[details]] update details on generator swap");
+ }
+
+ /**
+ * Gets the current details generator for row details.
+ *
+ * @since
+ * @return the detailsGenerator the current details generator
+ */
+ public DetailsGenerator getDetailsGenerator() {
+ return detailsGenerator;
+ }
+
+ /**
+ * Shows or hides the details for a specific item.
+ *
+ * @since
+ * @param itemId
+ * the id of the item for which to set details visibility
+ * @param visible
+ * <code>true</code> to show the details, or <code>false</code>
+ * to hide them
+ */
+ public void setDetailsVisible(Object itemId, boolean visible) {
+ datasourceExtension.setDetailsVisible(itemId, visible);
+ }
+
+ /**
+ * Checks whether details are visible for the given item.
+ *
+ * @since
+ * @param itemId
+ * the id of the item for which to check details visibility
+ * @return <code>true</code> iff the details are visible
+ */
+ public boolean isDetailsVisible(Object itemId) {
+ return datasourceExtension.isDetailsVisible(itemId);
+ }
}
diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java
index 7018df1413..81e1827420 100644
--- a/shared/src/com/vaadin/shared/ui/grid/GridState.java
+++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java
@@ -103,6 +103,16 @@ public class GridState extends AbstractComponentState {
public static final String JSONKEY_CELLSTYLES = "cs";
/**
+ * The key that tells whether details are visible for the row
+ *
+ * @see com.vaadin.ui.Grid#setDetailsGenerator(com.vaadin.ui.Grid.DetailsGenerator)
+ * @see com.vaadin.ui.Grid#setDetailsVisible(Object, boolean)
+ * @see com.vaadin.shared.data.DataProviderRpc#setRowData(int,
+ * elemental.json.JsonArray)
+ * */
+ public static final String JSONKEY_DETAILS_VISIBLE = "dv";
+
+ /**
* Columns in grid.
*/
public List<GridColumnState> columns = new ArrayList<GridColumnState>();
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java
index e5a46894b8..f0c4b3d9c0 100644
--- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java
@@ -248,6 +248,8 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
addFilterActions();
+ createDetailsActions();
+
this.grid = grid;
return grid;
}
@@ -1051,6 +1053,18 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}, null);
}
+ private void createDetailsActions() {
+ createBooleanAction("firstItemId", "Details", false,
+ new Command<Grid, Boolean>() {
+ @Override
+ @SuppressWarnings("boxing")
+ public void execute(Grid g, Boolean visible, Object data) {
+ g.setDetailsVisible(g.getContainerDataSource()
+ .firstItemId(), visible);
+ }
+ });
+ }
+
@Override
protected Integer getTicketNumber() {
return 12829;
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java
new file mode 100644
index 0000000000..01d2ba55eb
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.components.grid.basicfeatures.server;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.NoSuchElementException;
+
+import com.vaadin.testbench.annotations.RunLocally;
+import com.vaadin.testbench.parallel.Browser;
+import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest;
+
+@RunLocally(Browser.PHANTOMJS)
+public class GridDetailsServerTest extends GridBasicFeaturesTest {
+ private static final String[] FIRST_ITEM_DETAILS = new String[] {
+ "Component", "Details", "firstItemId" };
+
+ @Before
+ public void setUp() {
+ openTestURL();
+ }
+
+ @Test
+ public void openVisibleDetails() {
+ try {
+ getGridElement().getDetails(0);
+ fail("Expected NoSuchElementException");
+ } catch (NoSuchElementException ignore) {
+ // expected
+ }
+ selectMenuPath(FIRST_ITEM_DETAILS);
+ assertNotNull("details should've opened", getGridElement()
+ .getDetails(0));
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void closeVisibleDetails() {
+ selectMenuPath(FIRST_ITEM_DETAILS);
+ selectMenuPath(FIRST_ITEM_DETAILS);
+ getGridElement().getDetails(0);
+ }
+
+ @Test
+ public void openDetailsOutsideOfActiveRange() {
+ getGridElement().scroll(10000);
+ selectMenuPath(FIRST_ITEM_DETAILS);
+ getGridElement().scroll(0);
+ assertNotNull("details should've been opened", getGridElement()
+ .getDetails(0));
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void closeDetailsOutsideOfActiveRange() {
+ selectMenuPath(FIRST_ITEM_DETAILS);
+ getGridElement().scroll(10000);
+ selectMenuPath(FIRST_ITEM_DETAILS);
+ getGridElement().scroll(0);
+ getGridElement().getDetails(0);
+ }
+}