Change-Id: I2d1b2e4a797b2dac9ee97c832fcd40fb472edc08tags/7.2.0.beta1
/* | |||||
* 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"); | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} |
/* | |||||
* 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); | |||||
} |
/* | |||||
* 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(); | |||||
} | |||||
} |
import com.google.gwt.user.client.Element; | import com.google.gwt.user.client.Element; | ||||
import com.google.gwt.user.client.Window; | import com.google.gwt.user.client.Window; | ||||
import com.google.gwt.user.client.ui.Widget; | 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.Profiler; | ||||
import com.vaadin.client.Util; | import com.vaadin.client.Util; | ||||
import com.vaadin.client.ui.grid.Escalator.JsniUtil.TouchHandlerBundle; | import com.vaadin.client.ui.grid.Escalator.JsniUtil.TouchHandlerBundle; | ||||
moveAndUpdateEscalatorRows(strayRow, 0, topLogicalIndex); | moveAndUpdateEscalatorRows(strayRow, 0, topLogicalIndex); | ||||
} | } | ||||
} | } | ||||
fireRowVisibilityChangeEvent(); | |||||
} | } | ||||
@Override | @Override | ||||
newRowTop += ROW_HEIGHT_PX; | newRowTop += ROW_HEIGHT_PX; | ||||
} | } | ||||
} | } | ||||
fireRowVisibilityChangeEvent(); | |||||
} | } | ||||
/** | /** | ||||
* or it won't work correctly (due to setScrollTop invocation) | * or it won't work correctly (due to setScrollTop invocation) | ||||
*/ | */ | ||||
scroller.recalculateScrollbarsForVirtualViewport(); | scroller.recalculateScrollbarsForVirtualViewport(); | ||||
fireRowVisibilityChangeEvent(); | |||||
} | } | ||||
private void paintRemoveRowsAtMiddle(final Range removedLogicalInside, | private void paintRemoveRowsAtMiddle(final Range removedLogicalInside, | ||||
} | } | ||||
} | } | ||||
if (neededEscalatorRowsDiff != 0) { | |||||
fireRowVisibilityChangeEvent(); | |||||
} | |||||
Profiler.leave("Escalator.BodyRowContainer.verifyEscalatorCount"); | Profiler.leave("Escalator.BodyRowContainer.verifyEscalatorCount"); | ||||
} | } | ||||
} | } | ||||
return array; | 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)); | |||||
} | |||||
} | } |
import com.google.gwt.core.shared.GWT; | import com.google.gwt.core.shared.GWT; | ||||
import com.google.gwt.user.client.ui.Composite; | 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; | import com.vaadin.shared.util.SharedUtil; | ||||
/** | /** | ||||
*/ | */ | ||||
private final List<GridColumn<T>> columns = new ArrayList<GridColumn<T>>(); | private final List<GridColumn<T>> columns = new ArrayList<GridColumn<T>>(); | ||||
private DataSource<T> dataSource; | |||||
/** | /** | ||||
* The column groups rows added to the grid | * The column groups rows added to the grid | ||||
*/ | */ | ||||
refreshHeader(); | refreshHeader(); | ||||
refreshFooter(); | refreshFooter(); | ||||
escalator | |||||
.addRowVisibilityChangeHandler(new RowVisibilityChangeHandler() { | |||||
@Override | |||||
public void onRowVisibilityChange( | |||||
RowVisibilityChangeEvent event) { | |||||
if (dataSource != null) { | |||||
dataSource.ensureAvailability( | |||||
event.getFirstVisibleRow(), | |||||
event.getVisibleRowCount()); | |||||
} | |||||
} | |||||
}); | |||||
} | } | ||||
/** | /** | ||||
}; | }; | ||||
} | } | ||||
// TODO Should be implemented by the data sources | |||||
@SuppressWarnings("static-method") | |||||
private EscalatorUpdater createBodyUpdater() { | private EscalatorUpdater createBodyUpdater() { | ||||
return new EscalatorUpdater() { | return new EscalatorUpdater() { | ||||
@Override | @Override | ||||
public void updateCells(Row row, List<Cell> cellsToUpdate) { | 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) { | 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("..."); | |||||
} | } | ||||
} | } | ||||
}; | }; | ||||
public void setWidth(String width) { | public void setWidth(String width) { | ||||
escalator.setWidth(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); | |||||
} | |||||
} | |||||
} | } |
*/ | */ | ||||
private class CustomGridColumn extends GridColumn<String[]> { | private class CustomGridColumn extends GridColumn<String[]> { | ||||
private final int columnIndex; | |||||
public CustomGridColumn(int columnIndex) { | |||||
this.columnIndex = columnIndex; | |||||
} | |||||
@Override | @Override | ||||
public String getValue(String[] obj) { | public String getValue(String[] obj) { | ||||
// FIXME Should return something from the data source. | |||||
return null; | |||||
return obj[columnIndex]; | |||||
} | } | ||||
} | } | ||||
*/ | */ | ||||
private void addColumnFromStateChangeEvent(int columnIndex) { | private void addColumnFromStateChangeEvent(int columnIndex) { | ||||
GridColumnState state = getState().columns.get(columnIndex); | GridColumnState state = getState().columns.get(columnIndex); | ||||
CustomGridColumn column = new CustomGridColumn(); | |||||
CustomGridColumn column = new CustomGridColumn(columnIndex); | |||||
updateColumnFromState(column, state); | updateColumnFromState(column, state); | ||||
columnIdToColumn.put(state.id, column); | columnIdToColumn.put(state.id, column); |
public Range[] splitAtFromStart(final int length) { | public Range[] splitAtFromStart(final int length) { | ||||
return splitAt(getStart() + 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())); | |||||
} | |||||
} | } |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} |
assertTrue("no overlap allowed", | assertTrue("no overlap allowed", | ||||
!Range.between(0, 10).endsAfter(Range.between(5, 10))); | !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); | |||||
} | |||||
} | } |
/* | |||||
* 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); | |||||
} | |||||
} |
import com.vaadin.data.Container.PropertySetChangeEvent; | import com.vaadin.data.Container.PropertySetChangeEvent; | ||||
import com.vaadin.data.Container.PropertySetChangeListener; | import com.vaadin.data.Container.PropertySetChangeListener; | ||||
import com.vaadin.data.Container.PropertySetChangeNotifier; | import com.vaadin.data.Container.PropertySetChangeNotifier; | ||||
import com.vaadin.data.RpcDataProviderExtension; | |||||
import com.vaadin.server.KeyMapper; | import com.vaadin.server.KeyMapper; | ||||
import com.vaadin.shared.ui.grid.ColumnGroupRowState; | import com.vaadin.shared.ui.grid.ColumnGroupRowState; | ||||
import com.vaadin.shared.ui.grid.GridColumnState; | import com.vaadin.shared.ui.grid.GridColumnState; | ||||
} | } | ||||
}; | }; | ||||
private RpcDataProviderExtension datasourceExtension; | |||||
/** | /** | ||||
* Creates a new Grid using the given datasource. | * Creates a new Grid using the given datasource. | ||||
* | * | ||||
.removePropertySetChangeListener(propertyListener); | .removePropertySetChangeListener(propertyListener); | ||||
} | } | ||||
if (datasourceExtension != null) { | |||||
removeExtension(datasourceExtension); | |||||
} | |||||
datasource = container; | datasource = container; | ||||
datasourceExtension = new RpcDataProviderExtension(container); | |||||
datasourceExtension.extend(this); | |||||
// Listen to changes in properties and remove columns if needed | // Listen to changes in properties and remove columns if needed | ||||
if (datasource instanceof PropertySetChangeNotifier) { | if (datasource instanceof PropertySetChangeNotifier) { |
/* | |||||
* 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); | |||||
} |
/* | |||||
* 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; | |||||
} |
/* | |||||
* 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); | |||||
} |
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import com.vaadin.data.Item; | |||||
import com.vaadin.data.util.IndexedContainer; | import com.vaadin.data.util.IndexedContainer; | ||||
import com.vaadin.tests.components.AbstractComponentTest; | import com.vaadin.tests.components.AbstractComponentTest; | ||||
import com.vaadin.ui.components.grid.ColumnGroup; | import com.vaadin.ui.components.grid.ColumnGroup; | ||||
private int columnGroupRows = 0; | private int columnGroupRows = 0; | ||||
private final int ROWS = 1000; | |||||
@Override | @Override | ||||
protected Grid constructComponent() { | protected Grid constructComponent() { | ||||
ds.addContainerProperty("Column" + col, String.class, ""); | 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 | // Create grid | ||||
Grid grid = new Grid(ds); | Grid grid = new Grid(ds); | ||||