Bläddra i källkod

Introduce initial data source support for Grid (#12878)

Change-Id: I2d1b2e4a797b2dac9ee97c832fcd40fb472edc08
tags/7.2.0.beta1
Leif Åstrand 10 år sedan
förälder
incheckning
d54b02e31d

+ 232
- 0
client/src/com/vaadin/client/data/AbstractRemoteDataSource.java Visa fil

@@ -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");
}
}

+ 59
- 0
client/src/com/vaadin/client/data/DataChangeHandler.java Visa fil

@@ -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);
}

+ 76
- 0
client/src/com/vaadin/client/data/DataSource.java Visa fil

@@ -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);

}

+ 71
- 0
client/src/com/vaadin/client/data/RpcDataSourceConnector.java Visa fil

@@ -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();
}
}

+ 37
- 0
client/src/com/vaadin/client/ui/grid/Escalator.java Visa fil

@@ -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));
}
}

+ 86
- 3
client/src/com/vaadin/client/ui/grid/Grid.java Visa fil

@@ -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);
}
}
}

+ 8
- 3
client/src/com/vaadin/client/ui/grid/GridConnector.java Visa fil

@@ -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);

+ 22
- 0
client/src/com/vaadin/client/ui/grid/Range.java Visa fil

@@ -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()));
}
}

+ 90
- 0
client/src/com/vaadin/client/ui/grid/RowVisibilityChangeEvent.java Visa fil

@@ -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);
}

}

+ 38
- 0
client/src/com/vaadin/client/ui/grid/RowVisibilityChangeHandler.java Visa fil

@@ -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);

}

+ 76
- 0
client/tests/src/com/vaadin/client/ui/grid/RangeTest.java Visa fil

@@ -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);
}

}

+ 101
- 0
server/src/com/vaadin/data/RpcDataProviderExtension.java Visa fil

@@ -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);
}

}

+ 9
- 0
server/src/com/vaadin/ui/components/grid/Grid.java Visa fil

@@ -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) {

+ 40
- 0
shared/src/com/vaadin/shared/data/DataProviderRpc.java Visa fil

@@ -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);
}

+ 32
- 0
shared/src/com/vaadin/shared/data/DataProviderState.java Visa fil

@@ -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;
}

+ 38
- 0
shared/src/com/vaadin/shared/data/DataRequestRpc.java Visa fil

@@ -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);
}

+ 11
- 0
uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java Visa fil

@@ -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);


Laddar…
Avbryt
Spara