aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenrik Paul <henrik@vaadin.com>2014-10-01 15:19:27 +0300
committerJohannes Dahlström <johannesd@vaadin.com>2014-10-20 15:06:25 +0000
commit07c1bdfa889fe14048e8dcfd103d424d15c75bb3 (patch)
tree0fde4cd3dde27e5ba6ff6a63e85901ca7baca5a1
parentc1de8966d4b9bc7fa50eb27d19cc5142205a167d (diff)
downloadvaadin-framework-07c1bdfa889fe14048e8dcfd103d424d15c75bb3.tar.gz
vaadin-framework-07c1bdfa889fe14048e8dcfd103d424d15c75bb3.zip
REST-like data source use case. (#13334)
Change-Id: Ib708f3d18ff38c2c293f179640b85baebaf69550
-rw-r--r--client/src/com/vaadin/client/data/AbstractRemoteDataSource.java11
-rw-r--r--client/src/com/vaadin/client/ui/grid/Grid.java26
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridClientDataSources.java36
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridClientDataSourcesTest.java176
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSortingTest.java9
-rw-r--r--uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesConnector.java28
-rw-r--r--uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesWidget.java222
7 files changed, 489 insertions, 19 deletions
diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
index cec956817b..45e1533662 100644
--- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
+++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
@@ -399,6 +399,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
cached = remainsBefore.combineWith(transposedRemainsAfter);
}
+ assertDataChangeHandlerIsInjected();
dataChangeHandler.dataRemoved(firstRowIndex, count);
checkCacheCoverage();
@@ -445,6 +446,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
}
}
+ assertDataChangeHandlerIsInjected();
dataChangeHandler.dataAdded(firstRowIndex, count);
checkCacheCoverage();
@@ -586,6 +588,15 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
protected void resetDataAndSize(int newSize) {
dropFromCache(getCachedRange());
cached = Range.withLength(0, 0);
+ assertDataChangeHandlerIsInjected();
dataChangeHandler.resetDataAndSize(newSize);
}
+
+ private void assertDataChangeHandlerIsInjected() {
+ assert dataChangeHandler != null : "The dataChangeHandler was "
+ + "called before it was injected. Maybe you tried "
+ + "to manipulate the data in the DataSource's "
+ + "constructor instead of in overriding onAttach() "
+ + "and doing it there?";
+ }
}
diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java
index 04e050fb2f..c19f5a7c09 100644
--- a/client/src/com/vaadin/client/ui/grid/Grid.java
+++ b/client/src/com/vaadin/client/ui/grid/Grid.java
@@ -1889,7 +1889,7 @@ public class Grid<T> extends ResizeComposite implements
* @throws IllegalArgumentException
* if <code>dataSource</code> is <code>null</code>
*/
- public void setDataSource(DataSource<T> dataSource)
+ public void setDataSource(final DataSource<T> dataSource)
throws IllegalArgumentException {
if (dataSource == null) {
throw new IllegalArgumentException("dataSource can't be null.");
@@ -1932,23 +1932,19 @@ public class Grid<T> extends ResizeComposite implements
@Override
public void resetDataAndSize(int newSize) {
RowContainer body = escalator.getBody();
+ int oldSize = body.getRowCount();
- /*
- * Because the data has simply changed and we don't really know
- * what, we'll simply remove everything and redraw everything.
- */
+ if (newSize > oldSize) {
+ body.insertRows(oldSize, newSize - oldSize);
+ } else if (newSize < oldSize) {
+ body.removeRows(newSize, oldSize - newSize);
+ }
- double prevScroll = escalator.getScrollTop();
- body.removeRows(0, body.getRowCount());
- body.insertRows(0, newSize);
+ Range visibleRowRange = escalator.getVisibleRowRange();
+ dataSource.ensureAvailability(visibleRowRange.getStart(),
+ visibleRowRange.length());
- /*
- * 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);
+ assert body.getRowCount() == newSize;
}
});
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridClientDataSources.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridClientDataSources.java
new file mode 100644
index 0000000000..a307d4ce07
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridClientDataSources.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2014 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.tests.components.grid.basicfeatures;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.widgetset.TestingWidgetSet;
+import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.UI;
+
+@Widgetset(TestingWidgetSet.NAME)
+public class GridClientDataSources extends UI {
+
+ public static class GridClientDataSourcesComponent extends
+ AbstractComponent {
+ // empty
+ }
+
+ @Override
+ protected void init(VaadinRequest request) {
+ setContent(new GridClientDataSourcesComponent());
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridClientDataSourcesTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridClientDataSourcesTest.java
new file mode 100644
index 0000000000..71173c3a4a
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridClientDataSourcesTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2000-2014 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.tests.components.grid.basicfeatures;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class GridClientDataSourcesTest extends MultiBrowserTest {
+
+ @Before
+ public void before() {
+ openTestURL();
+ }
+
+ @Test
+ public void normalRestishDatasource() throws Exception {
+ selectMenuPath("DataSources", "RESTish", "Use");
+ assertCellPresent("cell 0 #0");
+
+ scrollToBottom();
+ assertCellPresent("cell 99 #0");
+ assertCellNotPresent("cell 100 #0");
+ }
+
+ @Test
+ public void growOnRequestRestishDatasource() throws Exception {
+ selectMenuPath("DataSources", "RESTish", "Use");
+ selectMenuPath("DataSources", "RESTish", "Next request +10");
+
+ scrollToBottom();
+ /* second scroll needed because of scrollsize change after scrolling */
+ scrollToBottom();
+
+ assertCellPresent("cell 109 #1");
+ assertCellNotPresent("cell 110 #1");
+ }
+
+ @Test
+ public void shrinkOnRequestRestishDatasource() throws Exception {
+ selectMenuPath("DataSources", "RESTish", "Use");
+ scrollToBottom();
+
+ selectMenuPath("DataSources", "RESTish", "Next request -10");
+ scrollToTop();
+
+ assertCellPresent("cell 0 #1");
+ }
+
+ @Test
+ public void pushChangeRestishDatasource() throws Exception {
+ selectMenuPath("DataSources", "RESTish", "Use");
+ selectMenuPath("DataSources", "RESTish", "Push data change");
+ assertCellPresent("cell 0 #1");
+ assertCellNotPresent("cell 0 #0");
+ }
+
+ @Test
+ public void growOnPushRestishDatasource() throws Exception {
+ selectMenuPath("DataSources", "RESTish", "Use");
+ selectMenuPath("DataSources", "RESTish", "Push data change +10");
+ assertCellPresent("cell 0 #1");
+ assertCellNotPresent("cell 0 #0");
+ scrollToBottom();
+ assertCellPresent("cell 109 #1");
+ }
+
+ @Test
+ public void shrinkOnPushRestishDatasource() throws Exception {
+ selectMenuPath("DataSources", "RESTish", "Use");
+ scrollToBottom();
+
+ selectMenuPath("DataSources", "RESTish", "Push data change -10");
+ assertCellPresent("cell 89 #1");
+ assertCellNotPresent("cell 89 #0");
+ assertCellNotPresent("cell 99 #1");
+ assertCellNotPresent("cell 99 #0");
+ }
+
+ private void assertCellPresent(String content) {
+ assertNotNull(findByXPath("//td[text()='" + content + "']"));
+ }
+
+ private void assertCellNotPresent(String content) {
+ assertNull(findByXPath("//td[text()='" + content + "']"));
+ }
+
+ private void scrollToTop() {
+ scrollVerticallyTo(0);
+ }
+
+ private void scrollToBottom() {
+ scrollVerticallyTo(9999);
+ }
+
+ private WebElement findByXPath(String string) {
+ try {
+ return findElement(By.xpath(string));
+ } catch (NoSuchElementException e) {
+ return null;
+ }
+ }
+
+ private void scrollVerticallyTo(int px) {
+ executeScript("arguments[0].scrollTop = " + px, findVerticalScrollbar());
+ }
+
+ private Object executeScript(String script, Object args) {
+ @SuppressWarnings("hiding")
+ final WebDriver driver = getDriver();
+ if (driver instanceof JavascriptExecutor) {
+ final JavascriptExecutor je = (JavascriptExecutor) driver;
+ return je.executeScript(script, args);
+ } else {
+ throw new IllegalStateException("current driver "
+ + getDriver().getClass().getName() + " is not a "
+ + JavascriptExecutor.class.getSimpleName());
+ }
+ }
+
+ private WebElement findVerticalScrollbar() {
+ return getDriver().findElement(
+ By.xpath("//div[contains(@class, "
+ + "\"v-grid-scroller-vertical\")]"));
+ }
+
+ private void selectMenu(String menuCaption) {
+ WebElement menuElement = getMenuElement(menuCaption);
+ Dimension size = menuElement.getSize();
+ new Actions(getDriver()).moveToElement(menuElement, size.width - 10,
+ size.height / 2).perform();
+ }
+
+ private WebElement getMenuElement(String menuCaption) {
+ return getDriver().findElement(
+ By.xpath("//td[text() = '" + menuCaption + "']"));
+ }
+
+ private void selectMenuPath(String... menuCaptions) {
+ new Actions(getDriver()).moveToElement(getMenuElement(menuCaptions[0]))
+ .click().perform();
+ for (int i = 1; i < menuCaptions.length - 1; ++i) {
+ selectMenu(menuCaptions[i]);
+ new Actions(getDriver()).moveByOffset(20, 0).perform();
+ }
+ new Actions(getDriver())
+ .moveToElement(
+ getMenuElement(menuCaptions[menuCaptions.length - 1]))
+ .click().perform();
+ }
+
+}
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSortingTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSortingTest.java
index e66b8b36d6..7b3e0b0dd4 100644
--- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSortingTest.java
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSortingTest.java
@@ -128,7 +128,6 @@ public class GridSortingTest extends GridBasicFeaturesTest {
// Sorting by column 9 is sorting by row index that is represented as a
// String.
- // First cells for first 3 rows are (9, 0), (99, 0) and (999, 0)
// Click header twice to sort descending
grid.getHeaderCell(0, 9).click();
@@ -136,12 +135,14 @@ public class GridSortingTest extends GridBasicFeaturesTest {
grid.getHeaderCell(0, 9).click();
assertColumnsAreSortedAs(_(9, 1, SortDirection.DESCENDING));
+ // First cells for first 3 rows are (9, 0), (99, 0) and (999, 0)
String row = "";
for (int i = 0; i < 3; ++i) {
row += "9";
- assertEquals(
- "Grid is not sorted by Column 9 using descending direction.",
- "(" + row + ", 0)", grid.getCell(i, 0).getText());
+ String expected = "(" + row + ", 0)";
+ String actual = grid.getCell(i, 0).getText();
+ assertEquals("Grid is not sorted by Column 9"
+ + " using descending direction.", expected, actual);
}
// Column 10 is random numbers from Random with seed 13334
diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesConnector.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesConnector.java
new file mode 100644
index 0000000000..157fa18b0e
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesConnector.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2000-2014 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.tests.widgetset.client.grid;
+
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.tests.components.grid.basicfeatures.GridClientDataSources.GridClientDataSourcesComponent;
+
+@Connect(GridClientDataSourcesComponent.class)
+public class GridClientDataSourcesConnector extends AbstractComponentConnector {
+ @Override
+ public GridClientDataSourcesWidget getWidget() {
+ return (GridClientDataSourcesWidget) super.getWidget();
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesWidget.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesWidget.java
new file mode 100644
index 0000000000..2126d2d335
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesWidget.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2000-2014 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.tests.widgetset.client.grid;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.vaadin.client.data.AbstractRemoteDataSource;
+import com.vaadin.client.ui.grid.Grid;
+import com.vaadin.client.ui.grid.Grid.SelectionMode;
+import com.vaadin.client.ui.grid.GridColumn;
+import com.vaadin.client.ui.grid.renderers.TextRenderer;
+
+public class GridClientDataSourcesWidget extends
+ PureGWTTestApplication<Grid<String[]>> {
+
+ /**
+ * This is an emulated datasource that has a back-end that changes size
+ * constantly. The back-end is unable to actively push data to Grid.
+ * Instead, with each row request, in addition to its row payload it tells
+ * how many rows it contains in total.
+ *
+ * A plausible response from this REST-like api would be:
+ *
+ * <pre>
+ * <code>
+ * GET /foos/4..8
+ *
+ * {
+ * "resultsize": 4,
+ * "data": [
+ * [4, "foo IV"],
+ * [5, "foo V"],
+ * [6, "foo VI"]
+ * [7, "foo VII"]
+ * ],
+ * "totalrows": 100
+ * }
+ * </code>
+ * </pre>
+ *
+ * In this case, the size of Grid needs to be updated to be able to show 100
+ * rows in total (no more, no less).
+ *
+ * This class
+ * <ol>
+ * <li>gets initialized
+ * <li>asks for its size
+ * <li>updates Grid once the reply is received
+ * <li>as the Grid fetches more data, the total row count is dynamically
+ * updated.
+ * </ol>
+ */
+ private class RestishDataSource extends AbstractRemoteDataSource<String[]> {
+ private int currentSize = 0;
+
+ /**
+ * Pretend like this class doesn't exist. It just simulates a backend
+ * somewhere.
+ * <p>
+ * It's scoped inside the RDS class only because it's tied to that.
+ * */
+ private class Backend {
+ public class Result {
+ public int size;
+ public List<String[]> rows;
+ }
+
+ private int size = 100;
+ private int modCount = 0;
+
+ public Result query(int firstRowIndex, int numberOfRows) {
+ Result result = new Result();
+ result.size = size;
+ result.rows = getRows(firstRowIndex, numberOfRows);
+ return result;
+ }
+
+ private List<String[]> getRows(int firstRowIndex, int numberOfRows) {
+ List<String[]> rows = new ArrayList<String[]>();
+ for (int i = 0; i < numberOfRows; i++) {
+ String id = String.valueOf(firstRowIndex + i);
+ rows.add(new String[] { id, "cell " + id + " #" + modCount });
+ }
+ return rows;
+ }
+
+ public void pushRowChanges(int rows) {
+ size += rows;
+ pushRowChanges();
+ }
+
+ public void pushRowChanges() {
+ modCount++;
+
+ // push "something happened" to datasource "over the wire":
+ resetDataAndSize(size);
+ }
+
+ public void addRows(int rowcount) {
+ modCount++;
+ size += rowcount;
+ }
+ }
+
+ final Backend backend;
+
+ public RestishDataSource() {
+ backend = new Backend();
+ currentSize = backend.size;
+ }
+
+ @Override
+ public int size() {
+ return currentSize;
+ }
+
+ @Override
+ protected void requestRows(int firstRowIndex, int numberOfRows) {
+ Backend.Result result = backend.query(firstRowIndex, numberOfRows);
+ final List<String[]> newRows = result.rows;
+
+ // order matters: first set row data, only then modify size.
+
+ /* Here's the requested data. Process it, please. */
+ setRowData(firstRowIndex, newRows);
+
+ /* Let's check whether the backend size changed. */
+ if (currentSize != result.size) {
+ currentSize = result.size;
+ resetDataAndSize(currentSize);
+ }
+ }
+
+ @Override
+ public Object getRowKey(String[] row) {
+ return row[0];
+ }
+ }
+
+ private final Grid<String[]> grid;
+
+ private RestishDataSource restishDataSource;
+
+ private final ScheduledCommand setRestishCommand = new ScheduledCommand() {
+ @Override
+ public void execute() {
+ for (GridColumn<?, String[]> column : grid.getColumns()) {
+ grid.removeColumn(column);
+ }
+
+ restishDataSource = new RestishDataSource();
+ grid.setDataSource(restishDataSource);
+ grid.addColumn(new GridColumn<String, String[]>(new TextRenderer()) {
+ {
+ setHeader("column");
+ }
+
+ @Override
+ public String getValue(String[] row) {
+ return row[1];
+ }
+ });
+ }
+ };
+
+ public GridClientDataSourcesWidget() {
+ super(new Grid<String[]>());
+ grid = getTestedWidget();
+
+ grid.getElement().getStyle().setZIndex(0);
+ grid.setHeight("400px");
+ grid.setSelectionMode(SelectionMode.NONE);
+ addNorth(grid, 400);
+
+ addMenuCommand("Use", setRestishCommand, "DataSources", "RESTish");
+ addMenuCommand("Next request +10", new ScheduledCommand() {
+ @Override
+ public void execute() {
+ restishDataSource.backend.addRows(10);
+ }
+ }, "DataSources", "RESTish");
+ addMenuCommand("Next request -10", new ScheduledCommand() {
+ @Override
+ public void execute() {
+ restishDataSource.backend.addRows(-10);
+ }
+ }, "DataSources", "RESTish");
+ addMenuCommand("Push data change", new ScheduledCommand() {
+ @Override
+ public void execute() {
+ restishDataSource.backend.pushRowChanges();
+ }
+ }, "DataSources", "RESTish");
+ addMenuCommand("Push data change +10", new ScheduledCommand() {
+ @Override
+ public void execute() {
+ restishDataSource.backend.pushRowChanges(10);
+ }
+ }, "DataSources", "RESTish");
+ addMenuCommand("Push data change -10", new ScheduledCommand() {
+ @Override
+ public void execute() {
+ restishDataSource.backend.pushRowChanges(-10);
+ }
+ }, "DataSources", "RESTish");
+ }
+}