summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/treegrid/TreeGridConnector.java81
-rw-r--r--client/src/main/java/com/vaadin/client/renderers/HierarchyRenderer.java10
-rw-r--r--client/src/main/java/com/vaadin/client/widget/treegrid/TreeGrid.java9
-rw-r--r--client/src/main/java/com/vaadin/client/widgets/Escalator.java62
-rwxr-xr-xclient/src/main/java/com/vaadin/client/widgets/Grid.java8
-rw-r--r--documentation/components/components-treegrid.asciidoc11
-rw-r--r--server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java40
-rw-r--r--server/src/main/java/com/vaadin/data/provider/HierarchyMapper.java16
-rw-r--r--server/src/main/java/com/vaadin/ui/TreeGrid.java29
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/treegrid/FocusParentRpc.java37
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/treegrid/FocusRpc.java38
-rw-r--r--themes/src/main/themes/VAADIN/themes/valo/components/_treegrid.scss27
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeNavigation.java59
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java103
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeNavigationTest.java169
15 files changed, 564 insertions, 135 deletions
diff --git a/client/src/main/java/com/vaadin/client/connectors/treegrid/TreeGridConnector.java b/client/src/main/java/com/vaadin/client/connectors/treegrid/TreeGridConnector.java
index 32b5a63840..d8e91ceaa0 100644
--- a/client/src/main/java/com/vaadin/client/connectors/treegrid/TreeGridConnector.java
+++ b/client/src/main/java/com/vaadin/client/connectors/treegrid/TreeGridConnector.java
@@ -33,6 +33,8 @@ import com.vaadin.client.widget.treegrid.TreeGrid;
import com.vaadin.client.widget.treegrid.events.TreeGridClickEvent;
import com.vaadin.client.widgets.Grid;
import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.treegrid.FocusParentRpc;
+import com.vaadin.shared.ui.treegrid.FocusRpc;
import com.vaadin.shared.ui.treegrid.NodeCollapseRpc;
import com.vaadin.shared.ui.treegrid.TreeGridCommunicationConstants;
import com.vaadin.shared.ui.treegrid.TreeGridState;
@@ -48,6 +50,12 @@ import elemental.json.JsonObject;
@Connect(com.vaadin.ui.TreeGrid.class)
public class TreeGridConnector extends GridConnector {
+ public TreeGridConnector() {
+ registerRpc(FocusRpc.class, (rowIndex, cellIndex) -> {
+ getWidget().focusCell(rowIndex, cellIndex);
+ });
+ }
+
private String hierarchyColumnId;
private HierarchyRenderer hierarchyRenderer;
@@ -143,7 +151,7 @@ public class TreeGridConnector extends GridConnector {
GridEventHandler<?> eventHandler)
/*-{
var browserEventHandlers = grid.@com.vaadin.client.widgets.Grid::browserEventHandlers;
-
+
// FocusEventHandler is initially 5th in the list of browser event handlers
browserEventHandlers.@java.util.List::set(*)(5, eventHandler);
}-*/;
@@ -229,40 +237,44 @@ public class TreeGridConnector extends GridConnector {
return;
}
- // Navigate within hierarchy with ALT/OPTION + ARROW KEY when
- // hierarchy column is selected
- if (isHierarchyColumn(event.getCell()) && domEvent.getAltKey()
- && (domEvent.getKeyCode() == KeyCodes.KEY_LEFT
- || domEvent.getKeyCode() == KeyCodes.KEY_RIGHT)) {
+ // Navigate within hierarchy with ARROW KEYs
+ if (domEvent.getKeyCode() == KeyCodes.KEY_LEFT
+ || domEvent.getKeyCode() == KeyCodes.KEY_RIGHT) {
+ event.setHandled(true);
+ EventCellReference<JsonObject> cell = event.getCell();
// Hierarchy metadata
- boolean collapsed, leaf;
- JsonObject rowData = event.getCell().getRow();
- if (rowData.hasKey(
+ JsonObject rowData = cell.getRow();
+ if (rowData == null) {
+ // Row data is lost from the cache, i.e. the row is at least outside the visual area,
+ // let's scroll the row into the view
+ getWidget().scrollToRow(cell.getRowIndex());
+ } else if (rowData.hasKey(
TreeGridCommunicationConstants.ROW_HIERARCHY_DESCRIPTION)) {
- collapsed = isCollapsed(rowData);
JsonObject rowDescription = rowData.getObject(
TreeGridCommunicationConstants.ROW_HIERARCHY_DESCRIPTION);
- leaf = rowDescription.getBoolean(
+ boolean leaf = rowDescription.getBoolean(
TreeGridCommunicationConstants.ROW_LEAF);
+ boolean collapsed = isCollapsed(rowData);
switch (domEvent.getKeyCode()) {
- case KeyCodes.KEY_RIGHT:
- if (!leaf && collapsed) {
- setCollapsed(event.getCell().getRowIndex(),
- !collapsed);
- }
- break;
- case KeyCodes.KEY_LEFT:
- if (!collapsed) {
- // collapse node
- setCollapsed(event.getCell().getRowIndex(),
- !collapsed);
- }
- break;
+ case KeyCodes.KEY_RIGHT:
+ if (collapsed && !leaf) {
+ setCollapsed(cell.getRowIndex(), false);
+ }
+ break;
+ case KeyCodes.KEY_LEFT:
+ if (collapsed || leaf) {
+ // navigate up
+ int columnIndex = cell.getColumnIndex();
+ getRpcProxy(FocusParentRpc.class).focusParent(
+ cell.getRowIndex(), columnIndex);
+ } else if (isCollapseAllowed(rowDescription)) {
+ setCollapsed(cell.getRowIndex(), true);
+ }
+ break;
}
+
}
- event.setHandled(true);
- return;
}
}
}
@@ -277,13 +289,14 @@ public class TreeGridConnector extends GridConnector {
.getBoolean(TreeGridCommunicationConstants.ROW_COLLAPSED);
}
- private static int getDepth(JsonObject rowData) {
- assert rowData
- .hasKey(TreeGridCommunicationConstants.ROW_HIERARCHY_DESCRIPTION) : "missing hierarchy data for row "
- + rowData.asString();
- return (int) rowData
- .getObject(
- TreeGridCommunicationConstants.ROW_HIERARCHY_DESCRIPTION)
- .getNumber(TreeGridCommunicationConstants.ROW_DEPTH);
+ /**
+ * Checks if the item can be collapsed
+ *
+ * @param row the item row
+ * @return {@code true} if the item is allowed to be collapsed, {@code false} otherwise.
+ */
+ public static boolean isCollapseAllowed(JsonObject row) {
+ return row.getBoolean(
+ TreeGridCommunicationConstants.ROW_COLLAPSE_ALLOWED);
}
}
diff --git a/client/src/main/java/com/vaadin/client/renderers/HierarchyRenderer.java b/client/src/main/java/com/vaadin/client/renderers/HierarchyRenderer.java
index 206491f75b..c03c5c1b01 100644
--- a/client/src/main/java/com/vaadin/client/renderers/HierarchyRenderer.java
+++ b/client/src/main/java/com/vaadin/client/renderers/HierarchyRenderer.java
@@ -29,6 +29,7 @@ import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.WidgetUtil;
+import com.vaadin.client.connectors.treegrid.TreeGridConnector;
import com.vaadin.client.widget.grid.RendererCellReference;
import com.vaadin.client.widget.treegrid.HierarchyRendererCellReferenceWrapper;
import com.vaadin.shared.ui.treegrid.TreeGridCommunicationConstants;
@@ -72,7 +73,7 @@ public class HierarchyRenderer extends ClickableRenderer<Object, Widget> {
JsonObject hierarchyData = getHierarchyData(row);
if ((!isCollapsed(hierarchyData)
- && !isCollapseAllowed(hierarchyData))
+ && !TreeGridConnector.isCollapseAllowed(hierarchyData))
|| isLeaf(hierarchyData)) {
return;
}
@@ -107,7 +108,7 @@ public class HierarchyRenderer extends ClickableRenderer<Object, Widget> {
leaf = isLeaf(rowDescription);
if (!leaf) {
collapsed = isCollapsed(rowDescription);
- collapseAllowed = isCollapseAllowed(rowDescription);
+ collapseAllowed = TreeGridConnector.isCollapseAllowed(rowDescription);
}
}
@@ -162,11 +163,6 @@ public class HierarchyRenderer extends ClickableRenderer<Object, Widget> {
return leaf;
}
- private boolean isCollapseAllowed(JsonObject row) {
- return row.getBoolean(
- TreeGridCommunicationConstants.ROW_COLLAPSE_ALLOWED);
- }
-
private boolean isCollapsed(JsonObject rowDescription) {
boolean collapsed;
collapsed = rowDescription
diff --git a/client/src/main/java/com/vaadin/client/widget/treegrid/TreeGrid.java b/client/src/main/java/com/vaadin/client/widget/treegrid/TreeGrid.java
index 911201b594..1e566faef6 100644
--- a/client/src/main/java/com/vaadin/client/widget/treegrid/TreeGrid.java
+++ b/client/src/main/java/com/vaadin/client/widget/treegrid/TreeGrid.java
@@ -21,11 +21,11 @@ import com.vaadin.client.widgets.Grid;
import elemental.json.JsonObject;
/**
- *
+ *
* @author Vaadin Ltd
* @since 8.1
*/
-public class TreeGrid extends Grid<JsonObject> {
+public class TreeGrid extends Grid<JsonObject> {
/**
* Method for accessing the private {@link Grid#focusCell(int, int)} method
@@ -42,4 +42,9 @@ public class TreeGrid extends Grid<JsonObject> {
public native boolean isElementInChildWidget(Element e)/*-{
return this.@com.vaadin.client.widgets.Grid::isElementInChildWidget(*)(e);
}-*/;
+
+ @Override
+ protected String getFocusPrimaryStyleName() {
+ return super.getStylePrimaryName() + "-rowmode";
+ }
}
diff --git a/client/src/main/java/com/vaadin/client/widgets/Escalator.java b/client/src/main/java/com/vaadin/client/widgets/Escalator.java
index 11bfa113cb..7bb078a94a 100644
--- a/client/src/main/java/com/vaadin/client/widgets/Escalator.java
+++ b/client/src/main/java/com/vaadin/client/widgets/Escalator.java
@@ -195,7 +195,6 @@ abstract class JsniWorkaround {
* to Java code.
*
* @see #createScrollListenerFunction(Escalator)
- * @see Escalator#onScroll()
* @see Escalator.Scroller#onScroll()
*/
protected final JavaScriptObject scrollListenerFunction;
@@ -205,7 +204,6 @@ abstract class JsniWorkaround {
* it on to Java code.
*
* @see #createMousewheelListenerFunction(Escalator)
- * @see Escalator#onScroll()
* @see Escalator.Scroller#onScroll()
*/
protected final JavaScriptObject mousewheelListenerFunction;
@@ -253,7 +251,7 @@ abstract class JsniWorkaround {
*
* @param esc
* a reference to the current instance of {@link Escalator}
- * @see Escalator#onScroll()
+ * @see Escalator.Scroller#onScroll()
*/
protected abstract JavaScriptObject createScrollListenerFunction(
Escalator esc);
@@ -264,7 +262,7 @@ abstract class JsniWorkaround {
*
* @param esc
* a reference to the current instance of {@link Escalator}
- * @see Escalator#onScroll()
+ * @see Escalator.Scroller#onScroll()
*/
protected abstract JavaScriptObject createMousewheelListenerFunction(
Escalator esc);
@@ -1139,10 +1137,10 @@ public class Escalator extends Widget
* Usually {@code "th"} or {@code "td"}.
* <p>
* <em>Note:</em> To actually <em>create</em> such an element, use
- * {@link #createCellElement(int, int)} instead.
+ * {@link #createCellElement(double)} instead.
*
* @return the tag name for the element to represent cells as
- * @see #createCellElement(int, int)
+ * @see #createCellElement(double)
*/
protected abstract String getCellElementTagName();
@@ -1207,7 +1205,7 @@ public class Escalator extends Widget
* range of logical indices. This may be fewer than {@code numberOfRows}
* , even zero, if not all the removed rows are actually visible.
* <p>
- * The implementation must call {@link #paintRemoveRow(Element, int)}
+ * The implementation must call {@link #paintRemoveRow(TableRowElement, int)}
* for each row that is removed from the DOM.
*
* @param index
@@ -3111,44 +3109,26 @@ public class Escalator extends Widget
*/
int rowsLeft = getRowCount();
if (rowsLeft < escalatorRowCount) {
- int escalatorRowsToRemove = escalatorRowCount - rowsLeft;
- for (int i = 0; i < escalatorRowsToRemove; i++) {
- final TableRowElement tr = visualRowOrder
- .remove(removedVisualInside.getStart());
-
- paintRemoveRow(tr, index);
+ /*
+ * Remove extra DOM rows and refresh contents.
+ */
+ for (int i = escalatorRowCount - 1; i >= rowsLeft; i--) {
+ final TableRowElement tr = visualRowOrder.remove(i);
+ paintRemoveRow(tr, i);
removeRowPosition(tr);
}
- escalatorRowCount -= escalatorRowsToRemove;
- /*
- * Because we're removing escalator rows, we don't have
- * anything to scroll by. Let's make sure the viewport is
- * scrolled to top, to render any rows possibly left above.
- */
- body.setBodyScrollPosition(tBodyScrollLeft, 0);
+ // Move rest of the rows to the Escalator's top
+ Range visualRange = Range.withLength(0, visualRowOrder.size());
+ moveAndUpdateEscalatorRows(visualRange, 0, 0);
- /*
- * We might have removed some rows from the middle, so let's
- * make sure we're not left with any holes. Also remember:
- * visualIndex == logicalIndex applies now.
- */
- final int dirtyRowsStart = removedLogicalInside.getStart();
- double y = getRowTop(dirtyRowsStart);
- for (int i = dirtyRowsStart; i < escalatorRowCount; i++) {
- final TableRowElement tr = visualRowOrder.get(i);
- setRowPosition(tr, 0, y);
- y += getDefaultRowHeight();
- y += spacerContainer.getSpacerHeight(i);
- }
+ sortDomElements();
+ setTopRowLogicalIndex(0);
- // #8825 update data starting from the first moved row
- final int start = dirtyRowsStart;
- final int end = escalatorRowCount;
- for (int i = start; i < end; i++) {
- final TableRowElement tr = visualRowOrder.get(i);
- refreshRow(tr, i);
- }
+ scroller.recalculateScrollbarsForVirtualViewport();
+
+ fireRowVisibilityChangeEvent();
+ return;
}
else {
@@ -6444,7 +6424,7 @@ public class Escalator extends Widget
* @param rows
* the number of rows that should be visible in Escalator's body
* @throws IllegalArgumentException
- * if {@code rows} is &leq; 0, {@link Double#isInifinite(double)
+ * if {@code rows} is &leq; 0, {@link Double#isInfinite(double)
* infinite} or {@link Double#isNaN(double) NaN}.
* @see #setHeightMode(HeightMode)
*/
diff --git a/client/src/main/java/com/vaadin/client/widgets/Grid.java b/client/src/main/java/com/vaadin/client/widgets/Grid.java
index eb5e38773b..a3b808e237 100755
--- a/client/src/main/java/com/vaadin/client/widgets/Grid.java
+++ b/client/src/main/java/com/vaadin/client/widgets/Grid.java
@@ -6198,8 +6198,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
rowSelectedStyleName = rowStyle + "-selected";
rowStripeStyleName = rowStyle + "-stripe";
- cellFocusStyleName = getStylePrimaryName() + "-cell-focused";
- rowFocusStyleName = getStylePrimaryName() + "-row-focused";
+ cellFocusStyleName = getFocusPrimaryStyleName() + "-cell-focused";
+ rowFocusStyleName = getFocusPrimaryStyleName() + "-row-focused";
if (isAttached()) {
refreshHeader();
@@ -6208,6 +6208,10 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
}
}
+ protected String getFocusPrimaryStyleName() {
+ return getStylePrimaryName();
+ }
+
/**
* Creates the escalator updater used to update the header rows in this
* grid. The updater is invoked when header rows or columns are added or
diff --git a/documentation/components/components-treegrid.asciidoc b/documentation/components/components-treegrid.asciidoc
index f3c2058f03..98d3d17839 100644
--- a/documentation/components/components-treegrid.asciidoc
+++ b/documentation/components/components-treegrid.asciidoc
@@ -93,6 +93,7 @@ treeGrid.addColumn(Project::getHoursDone).setCaption("Hours Done");
treeGrid.setHierarchyColumn("name");
----
+[[components.treegrid.node.collapsing]]
== Prevent Node Collapsing
[classname]#TreeGrid# supports setting a callback method that can allow or prevent the user from collapsing an expanded node.
@@ -105,10 +106,11 @@ Example using a predefined set of persons that can not be collapsed:
[source, java]
----
Set<Person> alwaysExpanded;
-personTreeGrid.setItemCollapseAllowedProvider(person ->
+personTreeGrid.setItemCollapseAllowedProvider(person ->
!alwaysExpanded.contains(person));
----
+[[components.treegrid.events]]
== Listening to Events
In addition to supporting all the listeners of the standard [classname]#Grid#, [classname]#TreeGrid# supports listening to the expansion and collapsing of items in its hierarchy.
@@ -122,3 +124,10 @@ treeGrid.addCollapseListener(event -> log("Item collapsed: " + event.getCollapse
The return types of the methods `getExpandedItem` and `getCollapsedItem` are the same as the type of the [classname]#TreeGrid# the events originated from.
Note that collapse listeners will not be triggered for any expanded subtrees of the collapsed item.
+
+[[components.treegrid.keyboard]]
+== Keyboard Navigation and Focus Handling in TreeGrid
+
+As opposed to [classname]#Grid#, individual cells are not focusable in [classname]#TreeGrid#, and only whole rows
+receive focus. The user can navigate through rows with kbd:[Up] and kbd:[Down], collapse rows with kbd:[Left],
+and expand them with kbd:[Right].
diff --git a/server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java b/server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java
index b93d0c3952..b986f74ea3 100644
--- a/server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java
+++ b/server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java
@@ -335,14 +335,16 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
}
/**
- * Collapses given row, removing all its subtrees.
+ * Collapses given row, removing all its subtrees. Calling this method will
+ * have no effect if the row is already collapsed.
*
* @param collapsedRowKey
* the key of the row, not {@code null}
* @param collapsedRowIndex
* the index of row to collapse
+ * @return {@code true} if the row was collapsed, {@code false} otherwise
*/
- public void doCollapse(String collapsedRowKey, int collapsedRowIndex) {
+ public boolean doCollapse(String collapsedRowKey, int collapsedRowIndex) {
if (collapsedRowIndex < 0 | collapsedRowIndex >= mapper.getTreeSize()) {
throw new IllegalArgumentException("Invalid row index "
+ collapsedRowIndex + " when tree grid size of "
@@ -353,24 +355,30 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
Objects.requireNonNull(collapsedItem,
"Cannot find item for given key " + collapsedItem);
+ if (mapper.isCollapsed(collapsedRowKey)) {
+ return false;
+ }
int collapsedSubTreeSize = mapper.collapse(collapsedRowKey,
collapsedRowIndex);
-
- getClientRpc().removeRows(collapsedRowIndex + 1, collapsedSubTreeSize);
+ getClientRpc().removeRows(collapsedRowIndex + 1,
+ collapsedSubTreeSize);
// FIXME seems like a slight overkill to do this just for refreshing
// expanded status
refresh(collapsedItem);
+ return true;
}
/**
- * Expands the given row.
+ * Expands the given row. Calling this method will have no effect if the row
+ * is already expanded.
*
* @param expandedRowKey
* the key of the row, not {@code null}
* @param expandedRowIndex
* the index of the row to expand
+ * @return {@code true} if the row was expanded, {@code false} otherwise
*/
- public void doExpand(String expandedRowKey, final int expandedRowIndex) {
+ public boolean doExpand(String expandedRowKey, final int expandedRowIndex) {
if (expandedRowIndex < 0 | expandedRowIndex >= mapper.getTreeSize()) {
throw new IllegalArgumentException("Invalid row index "
+ expandedRowIndex + " when tree grid size of "
@@ -388,16 +396,21 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
+ " returned no child nodes.");
}
+ if (!mapper.isCollapsed(expandedRowKey)) {
+ return false;
+ }
mapper.expand(expandedRowKey, expandedRowIndex, expandedNodeSize);
-
getClientRpc().insertRows(expandedRowIndex + 1, expandedNodeSize);
- // TODO optimize by sending "just enough" of the expanded items directly
- doPushRows(Range.withLength(expandedRowIndex + 1, expandedNodeSize));
+ // TODO optimize by sending "just enough" of the expanded items
+ // directly
+ doPushRows(
+ Range.withLength(expandedRowIndex + 1, expandedNodeSize));
// expanded node needs to be updated to be marked as expanded
// FIXME seems like a slight overkill to do this just for refreshing
// expanded status
refresh(expandedItem);
+ return true;
}
/**
@@ -419,4 +432,13 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
getActiveDataHandler().getActiveData().forEach(this::refresh);
}
+ /**
+ * Returns parent index for the row or {@code null}
+ *
+ * @param rowIndex the row index
+ * @return the parent index or {@code null} for top-level items
+ */
+ public Integer getParentIndex(int rowIndex) {
+ return mapper.getParentIndex(rowIndex);
+ }
}
diff --git a/server/src/main/java/com/vaadin/data/provider/HierarchyMapper.java b/server/src/main/java/com/vaadin/data/provider/HierarchyMapper.java
index 040be6cf2f..6c6f8fdaef 100644
--- a/server/src/main/java/com/vaadin/data/provider/HierarchyMapper.java
+++ b/server/src/main/java/com/vaadin/data/provider/HierarchyMapper.java
@@ -442,4 +442,20 @@ class HierarchyMapper implements Serializable {
}
}
+ /**
+ * Returns parent index for the row or {@code null}
+ *
+ * @param rowIndex the row index
+ * @return the parent index or {@code null} for top-level items
+ */
+ public Integer getParentIndex(int rowIndex) {
+ return nodes.stream()
+ .filter(treeNode -> treeNode.getParentKey() != null
+ && treeNode.getStartIndex() <= rowIndex
+ && treeNode.getEndIndex() >= rowIndex)
+ .min((a, b) -> Math.min(a.getEndIndex() - a.getStartIndex(),
+ b.getEndIndex() - b.getStartIndex()))
+ .map(treeNode -> treeNode.getStartIndex() - 1)
+ .orElse(null);
+ }
}
diff --git a/server/src/main/java/com/vaadin/ui/TreeGrid.java b/server/src/main/java/com/vaadin/ui/TreeGrid.java
index 6f961990de..d91a0f9af1 100644
--- a/server/src/main/java/com/vaadin/ui/TreeGrid.java
+++ b/server/src/main/java/com/vaadin/ui/TreeGrid.java
@@ -37,6 +37,8 @@ import com.vaadin.data.provider.HierarchicalQuery;
import com.vaadin.data.provider.InMemoryHierarchicalDataProvider;
import com.vaadin.server.SerializablePredicate;
import com.vaadin.shared.Registration;
+import com.vaadin.shared.ui.treegrid.FocusParentRpc;
+import com.vaadin.shared.ui.treegrid.FocusRpc;
import com.vaadin.shared.ui.treegrid.NodeCollapseRpc;
import com.vaadin.shared.ui.treegrid.TreeGridState;
import com.vaadin.ui.declarative.DesignAttributeHandler;
@@ -120,7 +122,7 @@ public class TreeGrid<T> extends Grid<T> {
*
* @param source
* the tree grid this event originated from
- * @param item
+ * @param expandedItem
* the item that was expanded
*/
public ExpandEvent(TreeGrid<T> source, T expandedItem) {
@@ -156,7 +158,7 @@ public class TreeGrid<T> extends Grid<T> {
*
* @param source
* the tree grid this event originated from
- * @param item
+ * @param collapsedItem
* the item that was collapsed
*/
public CollapseEvent(TreeGrid<T> source, T collapsedItem) {
@@ -182,13 +184,24 @@ public class TreeGrid<T> extends Grid<T> {
public void setNodeCollapsed(String rowKey, int rowIndex,
boolean collapse) {
if (collapse) {
- getDataCommunicator().doCollapse(rowKey, rowIndex);
- fireCollapseEvent(
- getDataCommunicator().getKeyMapper().get(rowKey));
+ if (getDataCommunicator().doCollapse(rowKey, rowIndex)) {
+ fireCollapseEvent(getDataCommunicator().getKeyMapper()
+ .get(rowKey));
+ }
} else {
- getDataCommunicator().doExpand(rowKey, rowIndex);
- fireExpandEvent(
- getDataCommunicator().getKeyMapper().get(rowKey));
+ if (getDataCommunicator().doExpand(rowKey, rowIndex)) {
+ fireExpandEvent(getDataCommunicator().getKeyMapper()
+ .get(rowKey));
+ }
+ }
+ }
+ });
+ registerRpc(new FocusParentRpc() {
+ @Override
+ public void focusParent(int rowIndex, int cellIndex) {
+ Integer parentIndex = getDataCommunicator().getParentIndex(rowIndex);
+ if (parentIndex != null) {
+ getRpcProxy(FocusRpc.class).focusCell(parentIndex, cellIndex);
}
}
});
diff --git a/shared/src/main/java/com/vaadin/shared/ui/treegrid/FocusParentRpc.java b/shared/src/main/java/com/vaadin/shared/ui/treegrid/FocusParentRpc.java
new file mode 100644
index 0000000000..856b3369db
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/treegrid/FocusParentRpc.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2016 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.ui.treegrid;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * RPC to handle client originated parent focusing in TreeGrid.
+ *
+ * @author Vaadin Ltd
+ * @since 8.1
+ */
+public interface FocusParentRpc extends ServerRpc {
+
+ /**
+ * Focuses cell in the row parent
+ *
+ * @param rowIndex
+ * the row index
+ * @param cellIndex
+ * the cell index
+ */
+ void focusParent(int rowIndex, int cellIndex);
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/treegrid/FocusRpc.java b/shared/src/main/java/com/vaadin/shared/ui/treegrid/FocusRpc.java
new file mode 100644
index 0000000000..fa02a0d712
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/treegrid/FocusRpc.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2016 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.ui.treegrid;
+
+import com.vaadin.shared.communication.ClientRpc;
+
+/**
+ * RPC to handle focusing in TreeGrid.
+ *
+ * @author Vaadin Ltd
+ * @since 8.1
+ */
+@FunctionalInterface
+public interface FocusRpc extends ClientRpc {
+
+ /**
+ * Focuses a cell
+ *
+ * @param rowIndex
+ * the row index
+ * @param columnIndex
+ * the cell index
+ */
+ void focusCell(int rowIndex, int columnIndex);
+}
diff --git a/themes/src/main/themes/VAADIN/themes/valo/components/_treegrid.scss b/themes/src/main/themes/VAADIN/themes/valo/components/_treegrid.scss
index 8255d830eb..07c8f71d0d 100644
--- a/themes/src/main/themes/VAADIN/themes/valo/components/_treegrid.scss
+++ b/themes/src/main/themes/VAADIN/themes/valo/components/_treegrid.scss
@@ -10,6 +10,9 @@ $tg-expander-width: 10px !default;
/** Expander button right side padding */
$tg-expander-padding: 10px !default;
+$v-tree-grid-row-focused-border: $v-grid-cell-focused-border !default;
+
+
@mixin treegrid {
// Expander with and item indentation constants
@@ -62,5 +65,29 @@ $tg-expander-padding: 10px !default;
.v-tree-grid-cell-content {
display: inline-block;
}
+
+ .v-grid-rowmode-row-focused {
+
+ &:before {
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ border: $v-grid-cell-focused-border;
+ display: none;
+ pointer-events: none;
+ }
+ }
+ .v-grid:focus .v-grid-rowmode-row-focused:before {
+ display: block;
+ }
+
+ .v-grid.v-disabled:focus .v-grid-rowmode-row-focused:before {
+ // Disabled Grid should not show cell focus outline
+ display: none;
+ }
+
}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeNavigation.java b/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeNavigation.java
new file mode 100644
index 0000000000..fc550a9570
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeNavigation.java
@@ -0,0 +1,59 @@
+package com.vaadin.tests.components.treegrid;
+
+import com.vaadin.annotations.Theme;
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.data.HierarchyData;
+import com.vaadin.data.provider.InMemoryHierarchicalDataProvider;
+import com.vaadin.tests.components.AbstractComponentTest;
+import com.vaadin.ui.TreeGrid;
+
+@Theme("valo")
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class TreeGridHugeTreeNavigation extends AbstractComponentTest<TreeGrid> {
+
+ private TreeGrid<String> treeGrid;
+ private InMemoryHierarchicalDataProvider<String> inMemoryDataProvider;
+
+ @Override
+ public TreeGrid getComponent() {
+ return treeGrid;
+ }
+
+ @Override
+ protected Class<TreeGrid> getTestClass() {
+ return TreeGrid.class;
+ }
+
+ @Override
+ protected void initializeComponents() {
+ initializeDataProvider();
+ treeGrid = new TreeGrid<>();
+ treeGrid.setDataProvider(inMemoryDataProvider);
+ treeGrid.setSizeFull();
+ treeGrid.addColumn(String::toString).setCaption("String")
+ .setId("string");
+ treeGrid.addColumn((i) -> "--").setCaption("Nothing");
+ treeGrid.setHierarchyColumn("string");
+ treeGrid.setId("testComponent");
+ treeGrid.setItemCollapseAllowedProvider(s -> !"Dad 2/1".equals(s));
+ addTestComponent(treeGrid);
+ }
+
+ private void initializeDataProvider() {
+ HierarchyData<String> data = new HierarchyData<>();
+ for (int i = 0; i < 3; i++) {
+ String granddad = "Granddad " + i;
+ data.addItem(null, granddad);
+ for (int j = 0; j < 3; j++) {
+ String dad = "Dad " + i + "/" + j;
+ data.addItem(granddad, dad);
+ for (int k = 0; k < 300; k++) {
+ String son = "Son " + i + "/" + j + "/" + k;
+ data.addItem(dad, son);
+ }
+ }
+ }
+ inMemoryDataProvider = new InMemoryHierarchicalDataProvider<>(data);
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java
index 8f8a224049..7fb7ddf3f3 100644
--- a/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java
@@ -2,8 +2,9 @@ package com.vaadin.tests.components.treegrid;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
-import org.junit.Assert;
+import com.vaadin.testbench.parallel.Browser;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -17,6 +18,10 @@ import com.vaadin.testbench.elements.TreeGridElement;
import com.vaadin.tests.tb3.MultiBrowserTest;
import com.vaadin.tests.tb3.ParameterizedTB3Runner;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
@RunWith(ParameterizedTB3Runner.class)
public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
@@ -35,6 +40,7 @@ public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
@Before
public void before() {
+ setDebug(true);
openTestURL("theme=valo");
grid = $(TreeGridElement.class).first();
}
@@ -43,79 +49,114 @@ public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
@Ignore // currently no implementation exists for toggling from the server
// side
public void toggle_collapse_server_side() {
- Assert.assertEquals(3, grid.getRowCount());
+ assertEquals(3, grid.getRowCount());
assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
selectMenuPath("Component", "Features", "Toggle expand", "0 | 0");
- Assert.assertEquals(6, grid.getRowCount());
+ assertEquals(6, grid.getRowCount());
assertCellTexts(1, 0, new String[] { "1 | 0", "1 | 1", "1 | 2" });
selectMenuPath("Component", "Features", "Toggle expand", "0 | 0");
- Assert.assertEquals(3, grid.getRowCount());
+ assertEquals(3, grid.getRowCount());
assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
// collapsing a leaf should have no effect
selectMenuPath("Component", "Features", "Toggle expand", "1 | 0");
- Assert.assertEquals(3, grid.getRowCount());
+ assertEquals(3, grid.getRowCount());
}
@Test
public void non_leaf_collapse_on_click() {
- Assert.assertEquals(3, grid.getRowCount());
+ assertEquals(3, grid.getRowCount());
assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
// Should expand "0 | 0"
grid.getRow(0).getCell(0)
.findElement(By.className("v-tree-grid-expander")).click();
- Assert.assertEquals(6, grid.getRowCount());
+ assertEquals(6, grid.getRowCount());
assertCellTexts(1, 0, new String[] { "1 | 0", "1 | 1", "1 | 2" });
// Should collapse "0 | 0"
grid.getRow(0).getCell(0)
.findElement(By.className("v-tree-grid-expander")).click();
- Assert.assertEquals(3, grid.getRowCount());
+ assertEquals(3, grid.getRowCount());
assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
}
@Test
- @Ignore // FIXME: remove ignore annotation once #8758 is done
public void keyboard_navigation() {
grid.getRow(0).getCell(0).click();
- // Should expand "0 | 0"
- new Actions(getDriver()).keyDown(Keys.ALT).sendKeys(Keys.RIGHT)
- .keyUp(Keys.ALT).perform();
- Assert.assertEquals(6, grid.getRowCount());
+ // Should expand "0 | 0" without moving focus
+ new Actions(getDriver()).sendKeys(Keys.RIGHT).perform();
+ assertEquals(6, grid.getRowCount());
+ assertCellTexts(1, 0, new String[] { "1 | 0", "1 | 1", "1 | 2" });
+ assertTrue(grid.getRow(0).hasClassName("v-grid-rowmode-row-focused"));
+ assertFalse(grid.getRow(1).hasClassName("v-grid-rowmode-row-focused"));
+
+ // Should navigate 2 times down to "1 | 1"
+ new Actions(getDriver()).sendKeys(Keys.DOWN, Keys.DOWN).perform();
+ assertEquals(6, grid.getRowCount());
assertCellTexts(1, 0, new String[] { "1 | 0", "1 | 1", "1 | 2" });
+ assertFalse(grid.getRow(0).hasClassName("v-grid-rowmode-row-focused"));
+ assertFalse(grid.getRow(1).hasClassName("v-grid-rowmode-row-focused"));
+ assertTrue(grid.getRow(2).hasClassName("v-grid-rowmode-row-focused"));
+
+ // Should expand "1 | 1" without moving focus
+ new Actions(getDriver()).sendKeys(Keys.RIGHT).perform();
+ assertEquals(9, grid.getRowCount());
+ assertCellTexts(2, 0, new String[] { "1 | 1", "2 | 0", "2 | 1", "2 | 2", "1 | 2"});
+ assertTrue(grid.getRow(2).hasClassName("v-grid-rowmode-row-focused"));
+
+ // Should collapse "1 | 1"
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ assertEquals(6, grid.getRowCount());
+ assertCellTexts(2, 0, new String[] { "1 | 1", "1 | 2", "0 | 1"});
+ assertTrue(grid.getRow(2).hasClassName("v-grid-rowmode-row-focused"));
+
+
+ // Should navigate to "0 | 0"
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ assertEquals(6, grid.getRowCount());
+ assertCellTexts(0, 0, new String[] { "0 | 0", "1 | 0", "1 | 1", "1 | 2" , "0 | 1" });
+ assertTrue(grid.getRow(0).hasClassName("v-grid-rowmode-row-focused"));
// Should collapse "0 | 0"
- new Actions(getDriver()).keyDown(Keys.ALT).sendKeys(Keys.LEFT)
- .keyUp(Keys.ALT).perform();
- Assert.assertEquals(3, grid.getRowCount());
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ assertEquals(3, grid.getRowCount());
assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
+ assertTrue(grid.getRow(0).hasClassName("v-grid-rowmode-row-focused"));
+
+ // Nothing should happen
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ assertEquals(3, grid.getRowCount());
+ assertCellTexts(0, 0, new String[] { "0 | 0", "0 | 1", "0 | 2" });
+ assertTrue(grid.getRow(0).hasClassName("v-grid-rowmode-row-focused"));
+
+ assertNoErrorNotifications();
}
@Test
public void changing_hierarchy_column() {
- Assert.assertTrue(grid.getRow(0).getCell(0)
+ assertTrue(grid.getRow(0).getCell(0)
.isElementPresent(By.className("v-tree-grid-expander")));
- Assert.assertFalse(grid.getRow(0).getCell(1)
+ assertFalse(grid.getRow(0).getCell(1)
.isElementPresent(By.className("v-tree-grid-expander")));
selectMenuPath("Component", "Features", "Set hierarchy column",
"depth");
- Assert.assertFalse(grid.getRow(0).getCell(0)
+ assertFalse(grid.getRow(0).getCell(0)
.isElementPresent(By.className("v-tree-grid-expander")));
- Assert.assertTrue(grid.getRow(0).getCell(1)
+ assertTrue(grid.getRow(0).getCell(1)
.isElementPresent(By.className("v-tree-grid-expander")));
selectMenuPath("Component", "Features", "Set hierarchy column",
"string");
- Assert.assertTrue(grid.getRow(0).getCell(0)
+ assertTrue(grid.getRow(0).getCell(0)
.isElementPresent(By.className("v-tree-grid-expander")));
- Assert.assertFalse(grid.getRow(0).getCell(1)
+ assertFalse(grid.getRow(0).getCell(1)
.isElementPresent(By.className("v-tree-grid-expander")));
}
@@ -130,18 +171,18 @@ public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
selectMenuPath("Component", "State", "Expand listener");
selectMenuPath("Component", "State", "Collapse listener");
- Assert.assertFalse(logContainsText("Item expanded: 0 | 0"));
- Assert.assertFalse(logContainsText("Item collapsed: 0 | 0"));
+ assertFalse(logContainsText("Item expanded: 0 | 0"));
+ assertFalse(logContainsText("Item collapsed: 0 | 0"));
grid.expandWithClick(0);
- Assert.assertTrue(logContainsText("Item expanded: 0 | 0"));
- Assert.assertFalse(logContainsText("Item collapsed: 0 | 0"));
+ assertTrue(logContainsText("Item expanded: 0 | 0"));
+ assertFalse(logContainsText("Item collapsed: 0 | 0"));
grid.collapseWithClick(0);
- Assert.assertTrue(logContainsText("Item expanded: 0 | 0"));
- Assert.assertTrue(logContainsText("Item collapsed: 0 | 0"));
+ assertTrue(logContainsText("Item expanded: 0 | 0"));
+ assertTrue(logContainsText("Item collapsed: 0 | 0"));
selectMenuPath("Component", "State", "Expand listener");
selectMenuPath("Component", "State", "Collapse listener");
@@ -149,15 +190,15 @@ public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
grid.expandWithClick(1);
grid.collapseWithClick(1);
- Assert.assertFalse(logContainsText("Item expanded: 0 | 1"));
- Assert.assertFalse(logContainsText("Item collapsed: 0 | 1"));
+ assertFalse(logContainsText("Item expanded: 0 | 1"));
+ assertFalse(logContainsText("Item collapsed: 0 | 1"));
}
private void assertCellTexts(int startRowIndex, int cellIndex,
String[] cellTexts) {
int index = startRowIndex;
for (String cellText : cellTexts) {
- Assert.assertEquals(cellText,
+ assertEquals(cellText,
grid.getRow(index).getCell(cellIndex).getText());
index++;
}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeNavigationTest.java b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeNavigationTest.java
new file mode 100644
index 0000000000..f37118a24b
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridHugeTreeNavigationTest.java
@@ -0,0 +1,169 @@
+package com.vaadin.tests.components.treegrid;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.elements.TreeGridElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class TreeGridHugeTreeNavigationTest extends MultiBrowserTest {
+
+ private TreeGridElement grid;
+
+ @Before
+ public void before() {
+ setDebug(true);
+ openTestURL();
+ grid = $(TreeGridElement.class).first();
+ }
+
+ @Test
+ public void keyboard_navigation() {
+ grid.getRow(0).getCell(0).click();
+
+ // Should navigate to "Granddad 1" and expand it
+ new Actions(getDriver()).sendKeys(Keys.DOWN, Keys.RIGHT).perform();
+ assertEquals(6, grid.getRowCount());
+ assertCellTexts(0, 0, "Granddad 0", "Granddad 1",
+ "Dad 1/0", "Dad 1/1", "Dad 1/2", "Granddad 2");
+ checkRowFocused(1);
+
+ // Should navigate to and expand "Dad 1/1"
+ new Actions(getDriver()).sendKeys(Keys.DOWN, Keys.DOWN, Keys.RIGHT)
+ .perform();
+ assertCellTexts(0, 0,
+ "Granddad 0", "Granddad 1", "Dad 1/0", "Dad 1/1",
+ "Son 1/1/0", "Son 1/1/1", "Son 1/1/2", "Son 1/1/3");
+ checkRowFocused(3);
+
+ // Should navigate 100 items down
+ Keys downKeyArr[] = new Keys[100];
+ for (int i = 0; i < 100; i++) {
+ downKeyArr[i] = Keys.DOWN;
+ }
+ new Actions(getDriver()).sendKeys(downKeyArr).perform();
+
+ WebElement son1_1_99 = findFocusedRow();
+ assertEquals("Son 1/1/99 --", son1_1_99.getText());
+
+ // Should navigate to "Dad 1/1" back
+ new Actions(getDriver())
+ .sendKeys(Keys.HOME, Keys.DOWN, Keys.DOWN, Keys.DOWN).perform();
+ WebElement dad1_1 = findFocusedRow();
+ assertEquals("Dad 1/1 --", dad1_1.getText());
+
+ // Should collapse "Dad 1/1"
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ assertCellTexts(0, 0, "Granddad 0", "Granddad 1",
+ "Dad 1/0", "Dad 1/1", "Dad 1/2", "Granddad 2");
+ checkRowFocused(3);
+
+ // Should navigate to "Granddad 1"
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ assertCellTexts(0, 0, "Granddad 0", "Granddad 1",
+ "Dad 1/0", "Dad 1/1", "Dad 1/2", "Granddad 2");
+ checkRowFocused(1);
+
+ // Should collapse "Granddad 1"
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ assertCellTexts(0, 0, "Granddad 0", "Granddad 1", "Granddad 2");
+ checkRowFocused(1);
+
+ // Nothing should happen
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ assertCellTexts(0, 0, "Granddad 0", "Granddad 1", "Granddad 2");
+ checkRowFocused(1);
+ assertNoErrorNotifications();
+ }
+
+ @Test
+ public void no_exception_when_calling_expand_or_collapse_twice() {
+
+ // Currently the collapsed state is updated in a round trip to the
+ // server, thus it is possible to trigger an expand on the same row
+ // multiple times through the UI. This should not cause exceptions, but
+ // rather ignore the redundant calls.
+
+ grid.getRow(0).getCell(0).click();
+ new Actions(getDriver()).sendKeys(Keys.RIGHT, Keys.RIGHT).perform();
+ assertNoErrorNotifications();
+ new Actions(getDriver()).sendKeys(Keys.LEFT, Keys.LEFT).perform();
+ assertNoErrorNotifications();
+ }
+
+ @Test
+ public void uncollapsible_item() {
+ grid.getRow(0).getCell(0).click();
+ new Actions(getDriver()).sendKeys(Keys.DOWN, Keys.DOWN, Keys.RIGHT).perform();
+ grid.waitForVaadin();
+ //expand Dad 2/1
+ new Actions(getDriver()).sendKeys(Keys.DOWN, Keys.DOWN, Keys.RIGHT).perform();
+ grid.waitForVaadin();
+ assertNoErrorNotifications();
+ assertCellTexts(5,0,"Son 2/1/0");
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ grid.waitForVaadin();
+ assertNoErrorNotifications();
+ assertCellTexts(5,0,"Son 2/1/0");
+ }
+ @Test
+ public void can_toggle_collapse_on_row_that_is_no_longer_in_cache() {
+ grid.getRow(0).getCell(0).click();
+
+ // Expand 2 levels
+ new Actions(getDriver()).sendKeys(Keys.RIGHT).perform();
+ grid.waitForVaadin();
+ new Actions(getDriver()).sendKeys(Keys.DOWN, Keys.RIGHT).perform();
+ grid.waitForVaadin();
+ grid.scrollToRow(200);
+ grid.waitForVaadin();
+ //Jump into view
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ grid.waitForVaadin();
+ //Collapse
+ new Actions(getDriver()).sendKeys(Keys.LEFT).perform();
+ grid.waitForVaadin();
+ assertEquals(6, grid.getRowCount());
+
+ // Expand
+ new Actions(getDriver()).sendKeys(Keys.RIGHT, Keys.UP).perform();
+ grid.waitForVaadin();
+ grid.scrollToRow(200);
+ new Actions(getDriver()).sendKeys(Keys.RIGHT).perform();
+ grid.waitForVaadin();
+ assertEquals(306, grid.getRowCount());
+ }
+
+ private WebElement findFocusedRow() {
+ return grid.findElement(By.className("v-grid-rowmode-row-focused"));
+ }
+
+ private void checkRowFocused(int index) {
+ if (index > 0) {
+ assertFalse(grid.getRow(index - 1)
+ .hasClassName("v-grid-rowmode-row-focused"));
+ }
+ assertTrue(
+ grid.getRow(index).hasClassName("v-grid-rowmode-row-focused"));
+ assertFalse(grid.getRow(index + 1)
+ .hasClassName("v-grid-rowmode-row-focused"));
+ }
+
+ private void assertCellTexts(int startRowIndex, int cellIndex,
+ String... cellTexts) {
+ int index = startRowIndex;
+ for (String cellText : cellTexts) {
+ assertEquals(cellText,
+ grid.getRow(index).getCell(cellIndex).getText());
+ index++;
+ }
+ }
+}