summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/src/com/vaadin/client/data/AbstractRemoteDataSource.java232
-rw-r--r--client/src/com/vaadin/client/data/DataChangeHandler.java59
-rw-r--r--client/src/com/vaadin/client/data/DataSource.java76
-rw-r--r--client/src/com/vaadin/client/data/RpcDataSourceConnector.java71
-rw-r--r--client/src/com/vaadin/client/ui/grid/Escalator.java37
-rw-r--r--client/src/com/vaadin/client/ui/grid/Grid.java89
-rw-r--r--client/src/com/vaadin/client/ui/grid/GridConnector.java11
-rw-r--r--client/src/com/vaadin/client/ui/grid/Range.java22
-rw-r--r--client/src/com/vaadin/client/ui/grid/RowVisibilityChangeEvent.java90
-rw-r--r--client/src/com/vaadin/client/ui/grid/RowVisibilityChangeHandler.java38
-rw-r--r--client/tests/src/com/vaadin/client/ui/grid/RangeTest.java76
-rw-r--r--server/src/com/vaadin/data/RpcDataProviderExtension.java101
-rw-r--r--server/src/com/vaadin/ui/components/grid/Grid.java9
-rw-r--r--shared/src/com/vaadin/shared/data/DataProviderRpc.java40
-rw-r--r--shared/src/com/vaadin/shared/data/DataProviderState.java32
-rw-r--r--shared/src/com/vaadin/shared/data/DataRequestRpc.java38
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java11
17 files changed, 1026 insertions, 6 deletions
diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
new file mode 100644
index 0000000000..5790adada9
--- /dev/null
+++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2000-2013 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.client.data;
+
+import java.util.HashMap;
+import java.util.List;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.vaadin.client.Profiler;
+import com.vaadin.client.ui.grid.Range;
+
+/**
+ * Base implementation for data sources that fetch data from a remote system.
+ * This class takes care of caching data and communicating with the data source
+ * user. An implementation of this class should override
+ * {@link #requestRows(int, int)} to trigger asynchronously loading of data.
+ * When data is received from the server, new row data should be passed to
+ * {@link #setRowData(int, List)}. {@link #setEstimatedSize(int)} should be used
+ * based on estimations of how many rows are available.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ * @param <T>
+ * the row type
+ */
+public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
+
+ private boolean requestPending = false;
+
+ private boolean coverageCheckPending = false;
+
+ private Range requestedAvailability = Range.between(0, 0);
+
+ private Range cached = Range.between(0, 0);
+
+ private final HashMap<Integer, T> rowCache = new HashMap<Integer, T>();
+
+ private DataChangeHandler dataChangeHandler;
+
+ private int estimatedSize;
+
+ private final ScheduledCommand coverageChecker = new ScheduledCommand() {
+ @Override
+ public void execute() {
+ coverageCheckPending = false;
+ checkCacheCoverage();
+ }
+ };
+
+ /**
+ * Sets the estimated number of rows in the data source.
+ *
+ * @param estimatedSize
+ * the estimated number of available rows
+ */
+ protected void setEstimatedSize(int estimatedSize) {
+ // TODO update dataChangeHandler if size changes
+ this.estimatedSize = estimatedSize;
+ }
+
+ private void ensureCoverageCheck() {
+ if (!coverageCheckPending) {
+ coverageCheckPending = true;
+ Scheduler.get().scheduleDeferred(coverageChecker);
+ }
+ }
+
+ @Override
+ public void ensureAvailability(int firstRowIndex, int numberOfRows) {
+ requestedAvailability = Range.withLength(firstRowIndex, numberOfRows);
+
+ /*
+ * Don't request any data right away since the data might be included in
+ * a message that has been received but not yet fully processed.
+ */
+ ensureCoverageCheck();
+ }
+
+ private void checkCacheCoverage() {
+ if (requestPending) {
+ // Anyone clearing requestPending should run this method again
+ return;
+ }
+
+ Profiler.enter("AbstractRemoteDataSource.checkCacheCoverage");
+
+ if (!requestedAvailability.intersects(cached)) {
+ /*
+ * Simple case: no overlap between cached data and needed data.
+ * Clear the cache and request new data
+ */
+ rowCache.clear();
+ cached = Range.between(0, 0);
+
+ handleMissingRows(requestedAvailability);
+ } else {
+ discardStaleCacheEntries();
+
+ // Might need more rows -> request them
+ Range[] availabilityPartition = requestedAvailability
+ .partitionWith(cached);
+ handleMissingRows(availabilityPartition[0]);
+ handleMissingRows(availabilityPartition[2]);
+ }
+
+ Profiler.leave("AbstractRemoteDataSource.checkCacheCoverage");
+ }
+
+ private void discardStaleCacheEntries() {
+ Range[] cacheParition = cached.partitionWith(requestedAvailability);
+ dropFromCache(cacheParition[0]);
+ cached = cacheParition[1];
+ dropFromCache(cacheParition[2]);
+ }
+
+ private void dropFromCache(Range range) {
+ for (int i = range.getStart(); i < range.getEnd(); i++) {
+ rowCache.remove(Integer.valueOf(i));
+ }
+ }
+
+ private void handleMissingRows(Range range) {
+ if (range.isEmpty()) {
+ return;
+ }
+ requestPending = true;
+ requestRows(range.getStart(), range.length());
+ }
+
+ /**
+ * Triggers fetching rows from the remote data source.
+ * {@link #setRowData(int, List)} should be invoked with data for the
+ * requested rows when they have been received.
+ *
+ * @param firstRowIndex
+ * the index of the first row to fetch
+ * @param numberOfRows
+ * the number of rows to fetch
+ */
+ protected abstract void requestRows(int firstRowIndex, int numberOfRows);
+
+ @Override
+ public int getEstimatedSize() {
+ return estimatedSize;
+ }
+
+ @Override
+ public T getRow(int rowIndex) {
+ return rowCache.get(Integer.valueOf(rowIndex));
+ }
+
+ @Override
+ public void setDataChangeHandler(DataChangeHandler dataChangeHandler) {
+ this.dataChangeHandler = dataChangeHandler;
+
+ if (dataChangeHandler != null && !cached.isEmpty()) {
+ // Push currently cached data to the implementation
+ dataChangeHandler.dataUpdated(cached.getStart(), cached.length());
+ }
+ }
+
+ /**
+ * Informs this data source that updated data has been sent from the server.
+ *
+ * @param firstRowIndex
+ * the index of the first received row
+ * @param rowData
+ * a list of rows, starting from <code>firstRowIndex</code>
+ */
+ protected void setRowData(int firstRowIndex, List<T> rowData) {
+ requestPending = false;
+
+ Profiler.enter("AbstractRemoteDataSource.setRowData");
+
+ Range received = Range.withLength(firstRowIndex, rowData.size());
+
+ Range[] partition = received.partitionWith(requestedAvailability);
+
+ Range newUsefulData = partition[1];
+ if (!newUsefulData.isEmpty()) {
+ // Update the parts that are actually inside
+ for (int i = newUsefulData.getStart(); i < newUsefulData.getEnd(); i++) {
+ rowCache.put(Integer.valueOf(i), rowData.get(i - firstRowIndex));
+ }
+
+ if (dataChangeHandler != null) {
+ Profiler.enter("AbstractRemoteDataSource.setRowData notify dataChangeHandler");
+ dataChangeHandler.dataUpdated(newUsefulData.getStart(),
+ newUsefulData.length());
+ Profiler.leave("AbstractRemoteDataSource.setRowData notify dataChangeHandler");
+ }
+
+ // Potentially extend the range
+ if (cached.isEmpty()) {
+ cached = newUsefulData;
+ } else {
+ discardStaleCacheEntries();
+ cached = cached.combineWith(newUsefulData);
+ }
+ }
+
+ if (!partition[0].isEmpty() || !partition[2].isEmpty()) {
+ /*
+ * FIXME
+ *
+ * Got data that we might need in a moment if the container is
+ * updated before the widget settings. Support for this will be
+ * implemented later on.
+ */
+ }
+
+ // Eventually check whether all needed rows are now available
+ ensureCoverageCheck();
+
+ Profiler.leave("AbstractRemoteDataSource.setRowData");
+ }
+}
diff --git a/client/src/com/vaadin/client/data/DataChangeHandler.java b/client/src/com/vaadin/client/data/DataChangeHandler.java
new file mode 100644
index 0000000000..4c4cc7656d
--- /dev/null
+++ b/client/src/com/vaadin/client/data/DataChangeHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2000-2013 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.client.data;
+
+/**
+ * Callback interface used by {@link DataSource} to inform its user about
+ * updates to the data.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public interface DataChangeHandler {
+ /**
+ * Called when the contents of the data source has changed. If the number of
+ * rows has changed or if rows have been moved around,
+ * {@link #dataAdded(int, int)} or {@link #dataRemoved(int, int)} should
+ * ideally be used instead.
+ *
+ * @param firstRowIndex
+ * the index of the first changed row
+ * @param numberOfRows
+ * the number of changed rows
+ */
+ public void dataUpdated(int firstRowIndex, int numberOfRows);
+
+ /**
+ * Called when rows have been removed from the data source.
+ *
+ * @param firstRowIndex
+ * the index that the first removed row had prior to removal
+ * @param numberOfRows
+ * the number of removed rows
+ */
+ public void dataRemoved(int firstRowIndex, int numberOfRows);
+
+ /**
+ * Called when the new rows have been added to the container.
+ *
+ * @param firstRowIndex
+ * the index of the first added row
+ * @param numberOfRows
+ * the number of added rows
+ */
+ public void dataAdded(int firstRowIndex, int numberOfRows);
+}
diff --git a/client/src/com/vaadin/client/data/DataSource.java b/client/src/com/vaadin/client/data/DataSource.java
new file mode 100644
index 0000000000..9179b6d03d
--- /dev/null
+++ b/client/src/com/vaadin/client/data/DataSource.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2000-2013 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.client.data;
+
+/**
+ * Source of data for widgets showing lazily loaded data based on indexable
+ * items (e.g. rows) of a specified type. The data source is a lazy view into a
+ * larger data set.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ * @param <T>
+ * the row type
+ */
+public interface DataSource<T> {
+ /**
+ * Informs the data source that data for the given range is needed. A data
+ * source only has one active region at a time, so calling this method
+ * discards the previously set range.
+ * <p>
+ * This method triggers lazy loading of data if necessary. The change
+ * handler registered using {@link #setDataChangeHandler(DataChangeHandler)}
+ * is informed when new data has been loaded.
+ *
+ * @param firstRowIndex
+ * the index of the first needed row
+ * @param numberOfRows
+ * the number of needed rows
+ */
+ public void ensureAvailability(int firstRowIndex, int numberOfRows);
+
+ /**
+ * Retrieves the data for the row at the given index. If the row data is not
+ * available, returns <code>null</code>.
+ * <p>
+ * This method does not trigger loading of unavailable data.
+ * {@link #ensureAvailability(int, int)} should be used to signal what data
+ * will be needed.
+ *
+ * @param rowIndex
+ * the index of the row to retrieve data for
+ * @return data for the row; or <code>null</code> if no data is available
+ */
+ public T getRow(int rowIndex);
+
+ /**
+ * Returns the current best guess for the number of rows in the container.
+ *
+ * @return the current estimation of the container size
+ */
+ public int getEstimatedSize();
+
+ /**
+ * Sets a data change handler to inform when data is updated, added or
+ * removed.
+ *
+ * @param dataChangeHandler
+ * the data change handler
+ */
+ public void setDataChangeHandler(DataChangeHandler dataChangeHandler);
+
+}
diff --git a/client/src/com/vaadin/client/data/RpcDataSourceConnector.java b/client/src/com/vaadin/client/data/RpcDataSourceConnector.java
new file mode 100644
index 0000000000..1785fc62c2
--- /dev/null
+++ b/client/src/com/vaadin/client/data/RpcDataSourceConnector.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2000-2013 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.client.data;
+
+import java.util.List;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.client.ui.grid.GridConnector;
+import com.vaadin.shared.data.DataProviderRpc;
+import com.vaadin.shared.data.DataProviderState;
+import com.vaadin.shared.data.DataRequestRpc;
+import com.vaadin.shared.ui.Connect;
+
+/**
+ * Connects a Vaadin server-side container data source to a Grid. This is
+ * currently implemented as an Extension hardcoded to support a specific
+ * connector type. This will be changed once framework support for something
+ * more flexible has been implemented.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.data.RpcDataProviderExtension.class)
+public class RpcDataSourceConnector extends AbstractExtensionConnector {
+
+ private final AbstractRemoteDataSource<String[]> dataSource = new AbstractRemoteDataSource<String[]>() {
+ @Override
+ protected void requestRows(int firstRowIndex, int numberOfRows) {
+ getRpcProxy(DataRequestRpc.class).requestRows(firstRowIndex,
+ numberOfRows);
+ }
+ };
+
+ @Override
+ protected void extend(ServerConnector target) {
+ dataSource.setEstimatedSize(getState().containerSize);
+ ((GridConnector) target).getWidget().setDataSource(dataSource);
+
+ registerRpc(DataProviderRpc.class, new DataProviderRpc() {
+ @Override
+ public void setRowData(int firstRow, List<String[]> rows) {
+ dataSource.setRowData(firstRow, rows);
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.client.ui.AbstractConnector#getState()
+ */
+ @Override
+ public DataProviderState getState() {
+ return (DataProviderState) super.getState();
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/grid/Escalator.java b/client/src/com/vaadin/client/ui/grid/Escalator.java
index 769a109569..540676653e 100644
--- a/client/src/com/vaadin/client/ui/grid/Escalator.java
+++ b/client/src/com/vaadin/client/ui/grid/Escalator.java
@@ -39,6 +39,7 @@ import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Widget;
+import com.google.web.bindery.event.shared.HandlerRegistration;
import com.vaadin.client.Profiler;
import com.vaadin.client.Util;
import com.vaadin.client.ui.grid.Escalator.JsniUtil.TouchHandlerBundle;
@@ -1522,6 +1523,8 @@ public class Escalator extends Widget {
moveAndUpdateEscalatorRows(strayRow, 0, topLogicalIndex);
}
}
+
+ fireRowVisibilityChangeEvent();
}
@Override
@@ -1715,6 +1718,8 @@ public class Escalator extends Widget {
newRowTop += ROW_HEIGHT_PX;
}
}
+
+ fireRowVisibilityChangeEvent();
}
/**
@@ -2101,6 +2106,8 @@ public class Escalator extends Widget {
* or it won't work correctly (due to setScrollTop invocation)
*/
scroller.recalculateScrollbarsForVirtualViewport();
+
+ fireRowVisibilityChangeEvent();
}
private void paintRemoveRowsAtMiddle(final Range removedLogicalInside,
@@ -2420,6 +2427,10 @@ public class Escalator extends Widget {
}
}
+ if (neededEscalatorRowsDiff != 0) {
+ fireRowVisibilityChangeEvent();
+ }
+
Profiler.leave("Escalator.BodyRowContainer.verifyEscalatorCount");
}
}
@@ -3116,4 +3127,30 @@ public class Escalator extends Widget {
return array;
}
+
+ /**
+ * Adds an event handler that gets notified when the range of visible rows
+ * changes e.g. because of scrolling.
+ *
+ * @param rowVisibilityChangeHadler
+ * the event handler
+ * @return a handler registration for the added handler
+ */
+ public HandlerRegistration addRowVisibilityChangeHandler(
+ RowVisibilityChangeHandler rowVisibilityChangeHadler) {
+ return addHandler(rowVisibilityChangeHadler,
+ RowVisibilityChangeEvent.TYPE);
+ }
+
+ private void fireRowVisibilityChangeEvent() {
+ int visibleRangeStart = body.getLogicalRowIndex(body.visualRowOrder
+ .getFirst());
+ int visibleRangeEnd = body.getLogicalRowIndex(body.visualRowOrder
+ .getLast()) + 1;
+
+ int visibleRowCount = visibleRangeEnd - visibleRangeStart;
+
+ fireEvent(new RowVisibilityChangeEvent(visibleRangeStart,
+ visibleRowCount));
+ }
}
diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java
index 67f14301f0..d76424ae31 100644
--- a/client/src/com/vaadin/client/ui/grid/Grid.java
+++ b/client/src/com/vaadin/client/ui/grid/Grid.java
@@ -21,6 +21,8 @@ import java.util.List;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.user.client.ui.Composite;
+import com.vaadin.client.data.DataChangeHandler;
+import com.vaadin.client.data.DataSource;
import com.vaadin.shared.util.SharedUtil;
/**
@@ -64,6 +66,8 @@ public class Grid<T> extends Composite {
*/
private final List<GridColumn<T>> columns = new ArrayList<GridColumn<T>>();
+ private DataSource<T> dataSource;
+
/**
* The column groups rows added to the grid
*/
@@ -366,6 +370,20 @@ public class Grid<T> extends Composite {
refreshHeader();
refreshFooter();
+
+ escalator
+ .addRowVisibilityChangeHandler(new RowVisibilityChangeHandler() {
+ @Override
+ public void onRowVisibilityChange(
+ RowVisibilityChangeEvent event) {
+ if (dataSource != null) {
+ dataSource.ensureAvailability(
+ event.getFirstVisibleRow(),
+ event.getVisibleRowCount());
+ }
+ }
+ });
+
}
/**
@@ -399,15 +417,33 @@ public class Grid<T> extends Composite {
};
}
- // TODO Should be implemented by the data sources
- @SuppressWarnings("static-method")
private EscalatorUpdater createBodyUpdater() {
return new EscalatorUpdater() {
@Override
public void updateCells(Row row, List<Cell> cellsToUpdate) {
+ int rowIndex = row.getRow();
+ if (dataSource == null) {
+ setCellsLoading(cellsToUpdate);
+ return;
+ }
+
+ T rowData = dataSource.getRow(rowIndex);
+ if (rowData == null) {
+ setCellsLoading(cellsToUpdate);
+ return;
+ }
+
for (Cell cell : cellsToUpdate) {
- cell.getElement().setInnerHTML("-");
+ String value = getColumn(cell.getColumn())
+ .getValue(rowData);
+ cell.getElement().setInnerText(value);
+ }
+ }
+
+ private void setCellsLoading(List<Cell> cellsToUpdate) {
+ for (Cell cell : cellsToUpdate) {
+ cell.getElement().setInnerText("...");
}
}
};
@@ -778,4 +814,51 @@ public class Grid<T> extends Composite {
public void setWidth(String width) {
escalator.setWidth(width);
}
+
+ /**
+ * Sets the data source used by this grid.
+ *
+ * @param dataSource
+ * the data source to use, not null
+ * @throws IllegalArgumentException
+ * if <code>dataSource</code> is <code>null</code>
+ */
+ public void setDataSource(DataSource<T> dataSource)
+ throws IllegalArgumentException {
+ if (dataSource == null) {
+ throw new IllegalArgumentException("dataSource can't be null.");
+ }
+
+ if (this.dataSource != null) {
+ this.dataSource.setDataChangeHandler(null);
+ }
+
+ this.dataSource = dataSource;
+ dataSource.setDataChangeHandler(new DataChangeHandler() {
+ @Override
+ public void dataUpdated(int firstIndex, int numberOfItems) {
+ escalator.getBody().refreshRows(firstIndex, numberOfItems);
+ }
+
+ @Override
+ public void dataRemoved(int firstIndex, int numberOfItems) {
+ escalator.getBody().removeRows(firstIndex, numberOfItems);
+ }
+
+ @Override
+ public void dataAdded(int firstIndex, int numberOfItems) {
+ escalator.getBody().insertRows(firstIndex, numberOfItems);
+ }
+ });
+
+ int previousRowCount = escalator.getBody().getRowCount();
+ if (previousRowCount != 0) {
+ escalator.getBody().removeRows(0, previousRowCount);
+ }
+
+ int estimatedSize = dataSource.getEstimatedSize();
+ if (estimatedSize > 0) {
+ escalator.getBody().insertRows(0, estimatedSize);
+ }
+ }
}
diff --git a/client/src/com/vaadin/client/ui/grid/GridConnector.java b/client/src/com/vaadin/client/ui/grid/GridConnector.java
index 32907e1e29..896a9998fb 100644
--- a/client/src/com/vaadin/client/ui/grid/GridConnector.java
+++ b/client/src/com/vaadin/client/ui/grid/GridConnector.java
@@ -48,10 +48,15 @@ public class GridConnector extends AbstractComponentConnector {
*/
private class CustomGridColumn extends GridColumn<String[]> {
+ private final int columnIndex;
+
+ public CustomGridColumn(int columnIndex) {
+ this.columnIndex = columnIndex;
+ }
+
@Override
public String getValue(String[] obj) {
- // FIXME Should return something from the data source.
- return null;
+ return obj[columnIndex];
}
}
@@ -142,7 +147,7 @@ public class GridConnector extends AbstractComponentConnector {
*/
private void addColumnFromStateChangeEvent(int columnIndex) {
GridColumnState state = getState().columns.get(columnIndex);
- CustomGridColumn column = new CustomGridColumn();
+ CustomGridColumn column = new CustomGridColumn(columnIndex);
updateColumnFromState(column, state);
columnIdToColumn.put(state.id, column);
diff --git a/client/src/com/vaadin/client/ui/grid/Range.java b/client/src/com/vaadin/client/ui/grid/Range.java
index 6dbb287e57..d3ae3c3753 100644
--- a/client/src/com/vaadin/client/ui/grid/Range.java
+++ b/client/src/com/vaadin/client/ui/grid/Range.java
@@ -359,4 +359,26 @@ public final class Range {
public Range[] splitAtFromStart(final int length) {
return splitAt(getStart() + length);
}
+
+ /**
+ * Combines two ranges to create a range containing all values in both
+ * ranges, provided there are no gaps between the ranges.
+ *
+ * @param other
+ * the range to combine with this range
+ *
+ * @return the combined range
+ *
+ * @throws IllegalArgumentException
+ * if the two ranges aren't connected
+ */
+ public Range combineWith(Range other) throws IllegalArgumentException {
+ if (getStart() > other.getEnd() || other.getStart() > getEnd()) {
+ throw new IllegalArgumentException("There is a gap between " + this
+ + " and " + other);
+ }
+
+ return Range.between(Math.min(getStart(), other.getStart()),
+ Math.max(getEnd(), other.getEnd()));
+ }
}
diff --git a/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeEvent.java b/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeEvent.java
new file mode 100644
index 0000000000..0e9652e215
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeEvent.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2000-2013 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.client.ui.grid;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+/**
+ * Event fired when the range of visible rows changes e.g. because of scrolling.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class RowVisibilityChangeEvent extends
+ GwtEvent<RowVisibilityChangeHandler> {
+ /**
+ * The type of this event.
+ */
+ public static final Type<RowVisibilityChangeHandler> TYPE = new Type<RowVisibilityChangeHandler>();
+
+ private final int firstVisibleRow;
+ private final int visibleRowCount;
+
+ /**
+ * Creates a new row visibility change event
+ *
+ * @param firstVisibleRow
+ * the index of the first visible row
+ * @param visibleRowCount
+ * the number of visible rows
+ */
+ public RowVisibilityChangeEvent(int firstVisibleRow, int visibleRowCount) {
+ this.firstVisibleRow = firstVisibleRow;
+ this.visibleRowCount = visibleRowCount;
+ }
+
+ /**
+ * Gets the index of the first row that is at least partially visible.
+ *
+ * @return the index of the first visible row
+ */
+ public int getFirstVisibleRow() {
+ return firstVisibleRow;
+ }
+
+ /**
+ * Gets the number of at least partially visible rows.
+ *
+ * @return the number of visible rows
+ */
+ public int getVisibleRowCount() {
+ return visibleRowCount;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.event.shared.GwtEvent#getAssociatedType()
+ */
+ @Override
+ public Type<RowVisibilityChangeHandler> getAssociatedType() {
+ return TYPE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.shared.GwtEvent#dispatch(com.google.gwt.event.shared
+ * .EventHandler)
+ */
+ @Override
+ protected void dispatch(RowVisibilityChangeHandler handler) {
+ handler.onRowVisibilityChange(this);
+ }
+
+}
diff --git a/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeHandler.java b/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeHandler.java
new file mode 100644
index 0000000000..dd24521499
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeHandler.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2013 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.client.ui.grid;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Event handler that gets notified when the range of visible rows changes e.g.
+ * because of scrolling.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public interface RowVisibilityChangeHandler extends EventHandler {
+
+ /**
+ * Called when the range of visible rows changes e.g. because of scrolling.
+ *
+ * @param event
+ * the row visibility change event describing the change
+ */
+ void onRowVisibilityChange(RowVisibilityChangeEvent event);
+
+}
diff --git a/client/tests/src/com/vaadin/client/ui/grid/RangeTest.java b/client/tests/src/com/vaadin/client/ui/grid/RangeTest.java
index a4715924b4..4441ee901d 100644
--- a/client/tests/src/com/vaadin/client/ui/grid/RangeTest.java
+++ b/client/tests/src/com/vaadin/client/ui/grid/RangeTest.java
@@ -209,4 +209,80 @@ public class RangeTest {
assertTrue("no overlap allowed",
!Range.between(0, 10).endsAfter(Range.between(5, 10)));
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void combine_notOverlappingFirstSmaller() {
+ Range.between(0, 10).combineWith(Range.between(11, 20));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void combine_notOverlappingSecondLarger() {
+ Range.between(11, 20).combineWith(Range.between(0, 10));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void combine_firstEmptyNotOverlapping() {
+ Range.between(15, 15).combineWith(Range.between(0, 10));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void combine_secondEmptyNotOverlapping() {
+ Range.between(0, 10).combineWith(Range.between(15, 15));
+ }
+
+ @Test
+ public void combine_barelyOverlapping() {
+ Range r1 = Range.between(0, 10);
+ Range r2 = Range.between(10, 20);
+
+ // Test both ways, should give the same result
+ Range combined1 = r1.combineWith(r2);
+ Range combined2 = r2.combineWith(r1);
+ assertEquals(combined1, combined2);
+
+ assertEquals(0, combined1.getStart());
+ assertEquals(20, combined1.getEnd());
+ }
+
+ @Test
+ public void combine_subRange() {
+ Range r1 = Range.between(0, 10);
+ Range r2 = Range.between(2, 8);
+
+ // Test both ways, should give the same result
+ Range combined1 = r1.combineWith(r2);
+ Range combined2 = r2.combineWith(r1);
+ assertEquals(combined1, combined2);
+
+ assertEquals(r1, combined1);
+ }
+
+ @Test
+ public void combine_intersecting() {
+ Range r1 = Range.between(0, 10);
+ Range r2 = Range.between(5, 15);
+
+ // Test both ways, should give the same result
+ Range combined1 = r1.combineWith(r2);
+ Range combined2 = r2.combineWith(r1);
+ assertEquals(combined1, combined2);
+
+ assertEquals(0, combined1.getStart());
+ assertEquals(15, combined1.getEnd());
+
+ }
+
+ @Test
+ public void combine_emptyInside() {
+ Range r1 = Range.between(0, 10);
+ Range r2 = Range.between(5, 5);
+
+ // Test both ways, should give the same result
+ Range combined1 = r1.combineWith(r2);
+ Range combined2 = r2.combineWith(r1);
+ assertEquals(combined1, combined2);
+
+ assertEquals(r1, combined1);
+ }
+
}
diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java
new file mode 100644
index 0000000000..48f03b98c0
--- /dev/null
+++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2000-2013 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.data;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import com.vaadin.data.Container.Indexed;
+import com.vaadin.server.AbstractExtension;
+import com.vaadin.shared.data.DataProviderRpc;
+import com.vaadin.shared.data.DataProviderState;
+import com.vaadin.shared.data.DataRequestRpc;
+import com.vaadin.ui.components.grid.Grid;
+
+/**
+ * Provides Vaadin server-side container data source to a
+ * {@link com.vaadin.client.ui.grid.GridConnector}. This is currently
+ * implemented as an Extension hardcoded to support a specific connector type.
+ * This will be changed once framework support for something more flexible has
+ * been implemented.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class RpcDataProviderExtension extends AbstractExtension {
+
+ private final Indexed container;
+
+ /**
+ * Creates a new data provider using the given container.
+ *
+ * @param container
+ * the container to make available
+ */
+ public RpcDataProviderExtension(Indexed container) {
+ this.container = container;
+
+ // TODO support for reacting to events from the container added later
+
+ registerRpc(new DataRequestRpc() {
+ @Override
+ public void requestRows(int firstRow, int numberOfRows) {
+ pushRows(firstRow, numberOfRows);
+ }
+ });
+
+ getState().containerSize = container.size();
+ }
+
+ private void pushRows(int firstRow, int numberOfRows) {
+ List<?> itemIds = container.getItemIds(firstRow, numberOfRows);
+ Collection<?> propertyIds = container.getContainerPropertyIds();
+ List<String[]> rows = new ArrayList<String[]>(itemIds.size());
+ for (Object itemId : itemIds) {
+ Item item = container.getItem(itemId);
+ String[] row = new String[propertyIds.size()];
+
+ int i = 0;
+ for (Object propertyId : propertyIds) {
+ Object value = item.getItemProperty(propertyId).getValue();
+ String stringValue = String.valueOf(value);
+ row[i++] = stringValue;
+ }
+
+ rows.add(row);
+ }
+
+ getRpcProxy(DataProviderRpc.class).setRowData(firstRow, rows);
+ }
+
+ @Override
+ protected DataProviderState getState() {
+ return (DataProviderState) super.getState();
+ }
+
+ /**
+ * Makes the data source available to the given {@link Grid} component.
+ *
+ * @param component
+ * the remote data grid component to extend
+ */
+ public void extend(Grid component) {
+ super.extend(component);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/components/grid/Grid.java b/server/src/com/vaadin/ui/components/grid/Grid.java
index 2b19043d93..79cc05e1a0 100644
--- a/server/src/com/vaadin/ui/components/grid/Grid.java
+++ b/server/src/com/vaadin/ui/components/grid/Grid.java
@@ -29,6 +29,7 @@ import com.vaadin.data.Container;
import com.vaadin.data.Container.PropertySetChangeEvent;
import com.vaadin.data.Container.PropertySetChangeListener;
import com.vaadin.data.Container.PropertySetChangeNotifier;
+import com.vaadin.data.RpcDataProviderExtension;
import com.vaadin.server.KeyMapper;
import com.vaadin.shared.ui.grid.ColumnGroupRowState;
import com.vaadin.shared.ui.grid.GridColumnState;
@@ -107,6 +108,8 @@ public class Grid extends AbstractComponent {
}
};
+ private RpcDataProviderExtension datasourceExtension;
+
/**
* Creates a new Grid using the given datasource.
*
@@ -140,7 +143,13 @@ public class Grid extends AbstractComponent {
.removePropertySetChangeListener(propertyListener);
}
+ if (datasourceExtension != null) {
+ removeExtension(datasourceExtension);
+ }
+
datasource = container;
+ datasourceExtension = new RpcDataProviderExtension(container);
+ datasourceExtension.extend(this);
// Listen to changes in properties and remove columns if needed
if (datasource instanceof PropertySetChangeNotifier) {
diff --git a/shared/src/com/vaadin/shared/data/DataProviderRpc.java b/shared/src/com/vaadin/shared/data/DataProviderRpc.java
new file mode 100644
index 0000000000..7d82ecc342
--- /dev/null
+++ b/shared/src/com/vaadin/shared/data/DataProviderRpc.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2000-2013 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.data;
+
+import java.util.List;
+
+import com.vaadin.shared.communication.ClientRpc;
+
+/**
+ * RPC interface used for pushing container data to the client.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public interface DataProviderRpc extends ClientRpc {
+
+ /**
+ * Sends updated row data to a client.
+ *
+ * @param firstRowIndex
+ * the index of the first updated row
+ * @param rowData
+ * the updated row data
+ */
+ public void setRowData(int firstRowIndex, List<String[]> rowData);
+}
diff --git a/shared/src/com/vaadin/shared/data/DataProviderState.java b/shared/src/com/vaadin/shared/data/DataProviderState.java
new file mode 100644
index 0000000000..2eabe0b0e1
--- /dev/null
+++ b/shared/src/com/vaadin/shared/data/DataProviderState.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2013 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.data;
+
+import com.vaadin.shared.communication.SharedState;
+
+/**
+ * Shared state used by client-side data sources.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class DataProviderState extends SharedState {
+ /**
+ * The size of the container.
+ */
+ public int containerSize;
+}
diff --git a/shared/src/com/vaadin/shared/data/DataRequestRpc.java b/shared/src/com/vaadin/shared/data/DataRequestRpc.java
new file mode 100644
index 0000000000..eaf17df8f6
--- /dev/null
+++ b/shared/src/com/vaadin/shared/data/DataRequestRpc.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2013 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.data;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * RPC interface used for requesting container data to the client.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public interface DataRequestRpc extends ServerRpc {
+
+ /**
+ * Request rows from the server.
+ *
+ * @param firstRowIndex
+ * the index of the first requested row
+ * @param numberOfRows
+ * the number of requested rows
+ */
+ public void requestRows(int firstRowIndex, int numberOfRows);
+}
diff --git a/uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java
index bd3e96f84a..7bf5d65e8b 100644
--- a/uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java
+++ b/uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java
@@ -17,6 +17,7 @@ package com.vaadin.tests.components.grid;
import java.util.ArrayList;
+import com.vaadin.data.Item;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.tests.components.AbstractComponentTest;
import com.vaadin.ui.components.grid.ColumnGroup;
@@ -36,6 +37,8 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
private int columnGroupRows = 0;
+ private final int ROWS = 1000;
+
@Override
protected Grid constructComponent() {
@@ -46,6 +49,14 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
ds.addContainerProperty("Column" + col, String.class, "");
}
+ for (int row = 0; row < ROWS; row++) {
+ Item item = ds.addItem(Integer.valueOf(row));
+ for (int col = 0; col < COLUMNS; col++) {
+ item.getItemProperty("Column" + col).setValue(
+ "(" + row + ", " + col + ")");
+ }
+ }
+
// Create grid
Grid grid = new Grid(ds);