Change-Id: Id87b2d7f50720bbfd5011520ea0be32fead48635tags/7.4.0.beta1
@@ -550,4 +550,10 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { | |||
} | |||
temporarilyPinnedRows = rows; | |||
} | |||
protected void resetDataAndSize(int newSize) { | |||
dropFromCache(getCachedRange()); | |||
cached = Range.withLength(0, 0); | |||
dataChangeHandler.resetDataAndSize(newSize); | |||
} | |||
} |
@@ -67,4 +67,16 @@ public interface DataChangeHandler { | |||
* the number of available rows | |||
*/ | |||
public void dataAvailable(int firstRowIndex, int numberOfRows); | |||
/** | |||
* Resets all data and defines a new size for the data. | |||
* <p> | |||
* This should be used in the cases where the data has changed in some | |||
* unverifiable way. I.e. "something happened". This will lead to a | |||
* re-rendering of the current Grid viewport | |||
* | |||
* @param estimatedNewDataSize | |||
* the estimated size of the new data set | |||
*/ | |||
public void resetDataAndSize(int estimatedNewDataSize); | |||
} |
@@ -142,6 +142,11 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { | |||
public void insertRowData(int firstRow, int count) { | |||
dataSource.insertRowData(firstRow, count); | |||
} | |||
@Override | |||
public void resetDataAndSize(int size) { | |||
dataSource.resetDataAndSize(size); | |||
} | |||
}); | |||
} | |||
@@ -1847,6 +1847,28 @@ public class Grid<T> extends ResizeComposite implements | |||
numberOfItems); | |||
fireEvent(new DataAvailableEvent(currentDataAvailable)); | |||
} | |||
@Override | |||
public void resetDataAndSize(int newSize) { | |||
RowContainer body = escalator.getBody(); | |||
/* | |||
* Because the data has simply changed and we don't really know | |||
* what, we'll simply remove everything and redraw everything. | |||
*/ | |||
double prevScroll = escalator.getScrollTop(); | |||
body.removeRows(0, body.getRowCount()); | |||
body.insertRows(0, newSize); | |||
/* | |||
* If data was removed or inserted above the scroll top, the | |||
* scroll position is kept locked, leading to data | |||
* "sliding under us". But we can't do anything about that, | |||
* since simply _something_ happened. | |||
*/ | |||
escalator.setScrollTop(prevScroll); | |||
} | |||
}); | |||
int previousRowCount = escalator.getBody().getRowCount(); |
@@ -567,6 +567,8 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
private final ActiveRowHandler activeRowHandler = new ActiveRowHandler(); | |||
private DataProviderRpc rpc; | |||
private final ItemSetChangeListener itemListener = new ItemSetChangeListener() { | |||
@Override | |||
public void containerItemSetChange(ItemSetChangeEvent event) { | |||
@@ -586,17 +588,49 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
} | |||
else { | |||
Range visibleRows = activeRowHandler.activeRange; | |||
List<?> itemIds = container.getItemIds(visibleRows.getStart(), | |||
visibleRows.length()); | |||
keyMapper.removeActiveRows(keyMapper.activeRange); | |||
keyMapper.addActiveRows(visibleRows, visibleRows.getStart(), | |||
itemIds); | |||
/* | |||
* Clear everything we have in view, and let the client | |||
* re-request for whatever it needs. | |||
* | |||
* Why this shortcut? Well, since anything could've happened, we | |||
* don't know what has happened. There are a lot of use-cases we | |||
* can cover at once with this carte blanche operation: | |||
* | |||
* 1) Grid is scrolled somewhere in the middle and all the | |||
* rows-inview are removed. We need a new pageful. | |||
* | |||
* 2) Grid is scrolled somewhere in the middle and none of the | |||
* visible rows are removed. We need no new rows. | |||
* | |||
* 3) Grid is scrolled all the way to the bottom, and the last | |||
* rows are being removed. Grid needs to scroll up and request | |||
* for more rows at the top. | |||
* | |||
* 4) Grid is scrolled pretty much to the bottom, and the last | |||
* rows are being removed. Grid needs to be aware that some | |||
* scrolling is needed, but not to compensate for all the | |||
* removed rows. And it also needs to request for some more rows | |||
* to the top. | |||
* | |||
* 5) Some ranges of rows are removed from view. We need to | |||
* collapse the gaps with existing rows and load the missing | |||
* rows. | |||
* | |||
* 6) The ultimate use case! Grid has 1.5 pages of rows and | |||
* scrolled a bit down. One page of rows is removed. We need to | |||
* make sure that new rows are loaded, but not all old slots are | |||
* occupied, since the page can't be filled with new row data. | |||
* It also needs to be scrolled to the top. | |||
* | |||
* So, it's easier (and safer) to do the simple thing instead of | |||
* taking all the corner cases into account. | |||
*/ | |||
pushRows(visibleRows.getStart(), itemIds); | |||
activeRowHandler.setActiveRows(visibleRows.getStart(), | |||
visibleRows.length()); | |||
activeRowHandler.activeRange = Range.withLength(0, 0); | |||
activeRowHandler.valueChangeListeners.clear(); | |||
rpc.resetDataAndSize(event.getContainer().size()); | |||
getState().containerSize = event.getContainer().size(); | |||
} | |||
} | |||
}; | |||
@@ -613,6 +647,7 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
*/ | |||
public RpcDataProviderExtension(Indexed container) { | |||
this.container = container; | |||
rpc = getRpcProxy(DataProviderRpc.class); | |||
registerRpc(new DataRequestRpc() { | |||
private Collection<String> allTemporarilyPinnedKeys = new ArrayList<String>(); | |||
@@ -711,7 +746,7 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
for (int i = 0; i < itemIds.size(); ++i) { | |||
rows.set(i, getRowData(propertyIds, itemIds.get(i))); | |||
} | |||
getRpcProxy(DataProviderRpc.class).setRowData(firstRow, rows.toJson()); | |||
rpc.setRowData(firstRow, rows.toJson()); | |||
} | |||
private JsonValue getRowData(Collection<?> propertyIds, Object itemId) { | |||
@@ -766,7 +801,7 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
*/ | |||
private void insertRowData(int index, int count) { | |||
getState().containerSize += count; | |||
getRpcProxy(DataProviderRpc.class).insertRowData(index, count); | |||
rpc.insertRowData(index, count); | |||
activeRowHandler.insertRows(index, count); | |||
} | |||
@@ -783,7 +818,7 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
*/ | |||
private void removeRowData(int firstIndex, int count) { | |||
getState().containerSize -= count; | |||
getRpcProxy(DataProviderRpc.class).removeRowData(firstIndex, count); | |||
rpc.removeRowData(firstIndex, count); | |||
for (int i = 0; i < count; i++) { | |||
Object itemId = keyMapper.itemIdAtIndex(firstIndex + i); | |||
@@ -809,7 +844,7 @@ public class RpcDataProviderExtension extends AbstractExtension { | |||
JsonValue row = getRowData(container.getContainerPropertyIds(), itemId); | |||
JsonArray rowArray = Json.createArray(); | |||
rowArray.set(0, row); | |||
getRpcProxy(DataProviderRpc.class).setRowData(index, rowArray.toJson()); | |||
rpc.setRowData(index, rowArray.toJson()); | |||
} | |||
@Override |
@@ -73,4 +73,16 @@ public interface DataProviderRpc extends ClientRpc { | |||
* the number of rows inserted at <code>firstRowIndex</code> | |||
*/ | |||
public void insertRowData(int firstRowIndex, int count); | |||
/** | |||
* Resets all data and defines a new size for the data. | |||
* <p> | |||
* This should be used in the cases where the data has changed in some | |||
* unverifiable way. I.e. "something happened". This will lead to a | |||
* re-rendering of the current Grid viewport | |||
* | |||
* @param size | |||
* the size of the new data set | |||
*/ | |||
public void resetDataAndSize(int size); | |||
} |
@@ -25,6 +25,7 @@ import java.util.List; | |||
import java.util.Locale; | |||
import java.util.Random; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.Item; | |||
import com.vaadin.data.Property; | |||
import com.vaadin.data.util.IndexedContainer; | |||
@@ -209,6 +210,28 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { | |||
addHeightActions(); | |||
createClickAction("Column 1 starts with \"(23\"", "Filter", | |||
new Command<Grid, Void>() { | |||
@Override | |||
public void execute(Grid grid, Void value, Object data) { | |||
ds.addContainerFilter(new Filter() { | |||
@Override | |||
public boolean passesFilter(Object itemId, Item item) | |||
throws UnsupportedOperationException { | |||
return item.getItemProperty("Column 1") | |||
.getValue().toString() | |||
.startsWith("(23"); | |||
} | |||
@Override | |||
public boolean appliesToProperty(Object propertyId) { | |||
return propertyId.equals("Column 1"); | |||
} | |||
}); | |||
} | |||
}, null); | |||
return grid; | |||
} | |||
@@ -20,14 +20,17 @@ import static org.hamcrest.core.IsNot.not; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import java.util.List; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.NoSuchElementException; | |||
import org.openqa.selenium.WebElement; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.tests.components.grid.GridElement.GridCellElement; | |||
import com.vaadin.testbench.elements.NotificationElement; | |||
import com.vaadin.tests.components.grid.GridElement; | |||
import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeatures; | |||
@@ -229,6 +232,29 @@ public class GridStructureTest extends GridBasicFeaturesTest { | |||
assertTrue(verticalScrollbarIsPresent()); | |||
} | |||
@Test | |||
public void testBareItemSetChange() throws Exception { | |||
openTestURL(); | |||
selectMenuPath("Component", "Filter", "Column 1 starts with \"(23\""); | |||
boolean foundElements = false; | |||
for (int row = 0; row < 100; row++) { | |||
try { | |||
GridCellElement cell = getGridElement().getCell(row, 1); | |||
foundElements = true; | |||
assertTrue("Unexpected cell contents. " | |||
+ "Did the ItemSetChange work after all?", cell | |||
.getText().startsWith("(23")); | |||
} catch (NoSuchElementException e) { | |||
assertTrue("No rows were found", foundElements); | |||
return; | |||
} | |||
} | |||
fail("unexpected amount of rows post-filter. Did the ItemSetChange work after all?"); | |||
} | |||
@Test | |||
public void testRemoveLastColumn() { | |||
setDebug(true); |