aboutsummaryrefslogtreecommitdiffstats
path: root/client/src
diff options
context:
space:
mode:
authorHenrik Paul <henrik@vaadin.com>2015-03-19 10:12:40 +0200
committerHenrik Paul <henrik@vaadin.com>2015-03-19 12:39:25 +0200
commit266101fc0e96dae779c0e2babfcddf627dc49f50 (patch)
treea5e0dc1c4774ca0262a987abdeb147e2b520547f /client/src
parent2be1e43d7081f0bc2c5f905d6b007fe597934ae3 (diff)
parentb06b1d68469e49e7784de342f0dcf9de64b35f5a (diff)
downloadvaadin-framework-266101fc0e96dae779c0e2babfcddf627dc49f50.tar.gz
vaadin-framework-266101fc0e96dae779c0e2babfcddf627dc49f50.zip
Merge remote-tracking branch 'origin/grid-detailsrow' into grid-7.5
Change-Id: I24df361a4f938b6ffe567aa290cc411ce194baba
Diffstat (limited to 'client/src')
-rw-r--r--client/src/com/vaadin/client/connectors/GridConnector.java197
-rw-r--r--client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java66
-rw-r--r--client/src/com/vaadin/client/data/AbstractRemoteDataSource.java15
-rw-r--r--client/src/com/vaadin/client/widget/grid/DetailsGenerator.java1
-rw-r--r--client/src/com/vaadin/client/widgets/Grid.java12
5 files changed, 280 insertions, 11 deletions
diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java
index d3045ee13b..0807690023 100644
--- a/client/src/com/vaadin/client/connectors/GridConnector.java
+++ b/client/src/com/vaadin/client/connectors/GridConnector.java
@@ -31,12 +31,15 @@ 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.Timer;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
+import com.vaadin.client.DeferredWorker;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener;
import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource;
import com.vaadin.client.data.DataSource.RowHandle;
import com.vaadin.client.renderers.Renderer;
@@ -45,6 +48,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;
@@ -72,8 +76,10 @@ import com.vaadin.client.widgets.Grid.FooterCell;
import com.vaadin.client.widgets.Grid.FooterRow;
import com.vaadin.client.widgets.Grid.HeaderCell;
import com.vaadin.client.widgets.Grid.HeaderRow;
+import com.vaadin.shared.Connector;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.DetailsConnectorChange;
import com.vaadin.shared.ui.grid.EditorClientRpc;
import com.vaadin.shared.ui.grid.EditorServerRpc;
import com.vaadin.shared.ui.grid.GridClientRpc;
@@ -103,7 +109,7 @@ import elemental.json.JsonValue;
*/
@Connect(com.vaadin.ui.Grid.class)
public class GridConnector extends AbstractHasComponentsConnector implements
- SimpleManagedLayout {
+ SimpleManagedLayout, DeferredWorker {
private static final class CustomCellStyleGenerator implements
CellStyleGenerator<JsonObject> {
@@ -382,6 +388,127 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
};
+ private static class CustomDetailsGenerator implements DetailsGenerator {
+
+ private final Map<Integer, ComponentConnector> indexToDetailsMap = new HashMap<Integer, ComponentConnector>();
+
+ @Override
+ @SuppressWarnings("boxing")
+ public Widget getDetails(int rowIndex) {
+ ComponentConnector componentConnector = indexToDetailsMap
+ .get(rowIndex);
+ if (componentConnector != null) {
+ return componentConnector.getWidget();
+ } else {
+ return null;
+ }
+ }
+
+ public void setDetailsConnectorChanges(
+ Set<DetailsConnectorChange> changes) {
+ /*
+ * To avoid overwriting connectors while moving them about, we'll
+ * take all the affected connectors, first all remove those that are
+ * removed or moved, then we add back those that are moved or added.
+ */
+
+ /* Remove moved/removed connectors from bookkeeping */
+ for (DetailsConnectorChange change : changes) {
+ Integer oldIndex = change.getOldIndex();
+ Connector removedConnector = indexToDetailsMap.remove(oldIndex);
+
+ Connector connector = change.getConnector();
+ assert removedConnector == null || connector == null
+ || removedConnector.equals(connector) : "Index "
+ + oldIndex + " points to " + removedConnector
+ + " while " + connector + " was expected";
+ }
+
+ /* Add moved/added connectors to bookkeeping */
+ for (DetailsConnectorChange change : changes) {
+ Integer newIndex = change.getNewIndex();
+ ComponentConnector connector = (ComponentConnector) change
+ .getConnector();
+
+ if (connector != null) {
+ assert newIndex != null : "An existing connector has a missing new index.";
+
+ ComponentConnector prevConnector = indexToDetailsMap.put(
+ newIndex, connector);
+
+ assert prevConnector == null : "Connector collision at index "
+ + newIndex
+ + " between old "
+ + prevConnector
+ + " and new " + connector;
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ private class DetailsConnectorFetcher implements DeferredWorker {
+
+ /** A flag making sure that we don't call scheduleFinally many times. */
+ private boolean fetcherHasBeenCalled = false;
+
+ /** A rolling counter for unique values. */
+ private int detailsFetchCounter = 0;
+
+ /** A collection that tracks the amount of requests currently underway. */
+ private Set<Integer> pendingFetches = new HashSet<Integer>(5);
+
+ private final ScheduledCommand lazyDetailsFetcher = new ScheduledCommand() {
+ @Override
+ public void execute() {
+ int currentFetchId = detailsFetchCounter++;
+ pendingFetches.add(currentFetchId);
+ getRpcProxy(GridServerRpc.class).sendDetailsComponents(
+ currentFetchId);
+ fetcherHasBeenCalled = false;
+
+ assert assertRequestDoesNotTimeout(currentFetchId);
+ }
+ };
+
+ public void schedule() {
+ if (!fetcherHasBeenCalled) {
+ Scheduler.get().scheduleFinally(lazyDetailsFetcher);
+ fetcherHasBeenCalled = true;
+ }
+ }
+
+ public void responseReceived(int fetchId) {
+ /* Ignore negative fetchIds (they're pushed, not fetched) */
+ if (fetchId >= 0) {
+ boolean success = pendingFetches.remove(fetchId);
+ assert success : "Received a response with an unidentified fetch id";
+ }
+ }
+
+ @Override
+ public boolean isWorkPending() {
+ return fetcherHasBeenCalled || !pendingFetches.isEmpty();
+ }
+
+ private boolean assertRequestDoesNotTimeout(final int fetchId) {
+ /*
+ * This method will not be compiled without asserts enabled. This
+ * only makes sure that any request does not time out.
+ *
+ * TODO Should this be an explicit check? Is it worth the overhead?
+ */
+ new Timer() {
+ @Override
+ public void run() {
+ assert !pendingFetches.contains(fetchId) : "Fetch id "
+ + fetchId + " timed out.";
+ }
+ }.schedule(1000);
+ return true;
+ }
+ }
+
/**
* Maps a generated column id to a grid column instance
*/
@@ -438,6 +565,29 @@ public class GridConnector extends AbstractHasComponentsConnector implements
private String lastKnownTheme = null;
+ private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator();
+
+ private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher();
+
+ private final DetailsListener detailsListener = new DetailsListener() {
+ @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);
+ }
+
+ detailsConnectorFetcher.schedule();
+ }
+
+ @Override
+ public void closeDetails(int rowIndex) {
+ getWidget().setDetailsVisible(rowIndex, false);
+ }
+ };
+
@Override
@SuppressWarnings("unchecked")
public Grid<JsonObject> getWidget() {
@@ -490,6 +640,36 @@ public class GridConnector extends AbstractHasComponentsConnector implements
public void recalculateColumnWidths() {
getWidget().recalculateColumnWidths();
}
+
+ @Override
+ @SuppressWarnings("boxing")
+ public void setDetailsConnectorChanges(
+ Set<DetailsConnectorChange> connectorChanges, int fetchId) {
+ customDetailsGenerator
+ .setDetailsConnectorChanges(connectorChanges);
+
+ // refresh moved/added details rows
+ for (DetailsConnectorChange change : connectorChanges) {
+ Integer oldIndex = change.getOldIndex();
+ Integer newIndex = change.getNewIndex();
+
+ assert oldIndex == null || oldIndex >= 0 : "Got an "
+ + "invalid old index: " + oldIndex
+ + " (connector: " + change.getConnector() + ")";
+ assert newIndex == null || newIndex >= 0 : "Got an "
+ + "invalid new index: " + newIndex
+ + " (connector: " + change.getConnector() + ")";
+
+ Integer index = newIndex;
+ if (index == null) {
+ index = oldIndex;
+ }
+
+ getWidget().setDetailsVisible(index, false);
+ getWidget().setDetailsVisible(index, true);
+ }
+ detailsConnectorFetcher.responseReceived(fetchId);
+ }
});
getWidget().addSelectionHandler(internalSelectionChangeHandler);
@@ -532,10 +712,10 @@ public class GridConnector extends AbstractHasComponentsConnector implements
});
getWidget().setEditorHandler(new CustomEditorHandler());
-
getWidget().addColumnReorderHandler(columnReorderHandler);
-
+ getWidget().setDetailsGenerator(customDetailsGenerator);
getLayoutManager().registerDependency(this, getWidget().getElement());
+
layout();
}
@@ -826,7 +1006,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements
column.setSortable(state.sortable);
column.setHidden(state.hidden);
- column.setHideable(state.hidable);
+ column.setHidable(state.hidable);
column.setEditable(state.editable);
column.setEditorConnector((AbstractFieldConnector) state.editorConnector);
@@ -1034,4 +1214,13 @@ public class GridConnector extends AbstractHasComponentsConnector implements
public void layout() {
getWidget().onResize();
}
+
+ @Override
+ public boolean isWorkPending() {
+ return detailsConnectorFetcher.isWorkPending();
+ }
+
+ public DetailsListener getDetailsListener() {
+ return detailsListener;
+ }
}
diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
index f8d6ebcb62..e8c7ee5286 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,36 @@ 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);
+
+ /**
+ * Closes details for a row.
+ *
+ * @param rowIndex
+ * the index of the row for which to close details
+ */
+ void closeDetails(int rowIndex);
+ }
+
public class RpcDataSource extends AbstractRemoteDataSource<JsonObject> {
protected RpcDataSource() {
@@ -56,27 +87,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 +202,29 @@ 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));
+ }
+ }
+
+ @Override
+ protected void onDropFromCache(int rowIndex) {
+ detailsListener.closeDetails(rowIndex);
}
}
@@ -178,6 +232,8 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
@Override
protected void extend(ServerConnector target) {
- ((GridConnector) target).setDataSource(dataSource);
+ GridConnector gridConnector = (GridConnector) target;
+ dataSource.setDetailsListener(gridConnector.getDetailsListener());
+ gridConnector.setDataSource(dataSource);
}
}
diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
index 1de271c646..152b66f2ca 100644
--- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
+++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
@@ -332,9 +332,23 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
for (int i = range.getStart(); i < range.getEnd(); i++) {
T removed = indexToRowMap.remove(Integer.valueOf(i));
keyToIndexMap.remove(getRowKey(removed));
+
+ onDropFromCache(i);
}
}
+ /**
+ * A hook that can be overridden to do something whenever a row is dropped
+ * from the cache.
+ *
+ * @since
+ * @param rowIndex
+ * the index of the dropped row
+ */
+ protected void onDropFromCache(int rowIndex) {
+ // noop
+ }
+
private void handleMissingRows(Range range) {
if (range.isEmpty()) {
return;
@@ -570,6 +584,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
Profiler.leave("AbstractRemoteDataSource.insertRowData");
}
+ @SuppressWarnings("boxing")
private void moveRowFromIndexToIndex(int oldIndex, int newIndex) {
T row = indexToRowMap.remove(oldIndex);
if (indexToRowMap.containsKey(newIndex)) {
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 c6ac35bba0..174f2dde38 100644
--- a/client/src/com/vaadin/client/widgets/Grid.java
+++ b/client/src/com/vaadin/client/widgets/Grid.java
@@ -7263,10 +7263,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
@@ -7317,12 +7324,13 @@ public class Grid<T> extends ResizeComposite implements
* see GridSpacerUpdater.init for implementation details.
*/
- if (visible && !isDetailsVisible(rowIndex)) {
+ boolean isVisible = isDetailsVisible(rowIndex);
+ if (visible && !isVisible) {
escalator.getBody().setSpacer(rowIndex, DETAILS_ROW_INITIAL_HEIGHT);
visibleDetails.add(rowIndexInteger);
}
- else if (!visible && isDetailsVisible(rowIndex)) {
+ else if (!visible && isVisible) {
escalator.getBody().setSpacer(rowIndex, -1);
visibleDetails.remove(rowIndexInteger);
}