diff options
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); |