Change-Id: I2d1b2e4a797b2dac9ee97c832fcd40fb472edc08tags/7.2.0.beta1
@@ -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"); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); |
@@ -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())); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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) { |
@@ -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); | |||
} |
@@ -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; | |||
} |
@@ -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); | |||
} |
@@ -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); | |||