CellTree: add API for keyboard-selected node New public methods: CellTree.getKeyboardSelectedTreeNode() CellTree.setKeyboardSelectedTreeNode() Change-Id: I00942611e485cc1459836065c7337a0a2d097f48 Review-Link: https://gwt-review.googlesource.com/#/c/9280/
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTree.java b/user/src/com/google/gwt/user/cellview/client/CellTree.java index 1094903..5e66bc1 100644 --- a/user/src/com/google/gwt/user/cellview/client/CellTree.java +++ b/user/src/com/google/gwt/user/cellview/client/CellTree.java
@@ -820,6 +820,22 @@ keyboardSelectedNode.setKeyboardSelected(true, true); } + /** + * Sets the node that will be selected when the CellTree gains keyboard focus. + * + * @param parentNode a node in the tree that is currently open + * @param childIndex the index of the child to select + * @param stealFocus if true, also change keyboard focus to this CellTree. + */ + public void setKeyboardSelectedTreeNode(TreeNode parentNode, int childIndex, boolean stealFocus) { + CellTreeNodeView nodeView = getCellTreeNodeView(parentNode); + // Just to ensure necessary checks are done, e.g. + // assertNotDestroyed();checkChildBounds(childIndex);flush(); + nodeView.getTreeNode().getChildValue(childIndex); + + keyboardSelect(nodeView.getChildNode(childIndex), stealFocus); + } + public void setTabIndex(int index) { this.tabIndex = index; keyboardSelectedNode.setKeyboardSelected(true, false); @@ -883,6 +899,13 @@ } /** + * Returns the TreeNode that is selected when the CellTree has keyboard focus. + */ + public TreeNode getKeyboardSelectedTreeNode() { + return keyboardSelectedNode == null ? null : keyboardSelectedNode.getTreeNode(); + } + + /** * Return the HTML to render the loading image. */ SafeHtml getLoadingImageHtml() { @@ -964,6 +987,19 @@ chain.add(hElem); } + private CellTreeNodeView getCellTreeNodeView(TreeNode treeNode) { + if (!(treeNode instanceof CellTreeNodeView.TreeNodeImpl)) { + throw new UnsupportedOperationException("Operation not supported for " + treeNode.getClass()); + } + + CellTreeNodeView nodeView = ((CellTreeNodeView.TreeNodeImpl) treeNode).getNodeView(); + if (!nodeView.belongsToTree(this)) { + throw new IllegalArgumentException("The tree node does not belong to the tree."); + } + + return nodeView; + } + private CellTreeNodeView<?> findItemByChain(ArrayList<Element> chain, int idx, CellTreeNodeView<?> parent) { if (idx == chain.size()) {
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java index 8c26a8b..8a316a9 100644 --- a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java +++ b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
@@ -561,7 +561,7 @@ * might move it to a new {@link CellTreeNodeView}, and we don't want * non-static references to the old {@link CellTreeNodeView}. */ - private static class TreeNodeImpl implements TreeNode { + static class TreeNodeImpl implements TreeNode { private CellTreeNodeView<?> nodeView; @@ -590,6 +590,10 @@ return (nodeView.parentNode == null) ? 0 : nodeView.parentNode.children.indexOf(nodeView); } + final CellTreeNodeView<?> getNodeView() { + return nodeView; + } + @Override public TreeNode getParent() { assertNotDestroyed(); @@ -669,7 +673,7 @@ /** * Flush pending changes in the view. */ - private void flush() { + void flush() { if (nodeView.listView != null) { nodeView.listView.presenter.flush(); } @@ -724,7 +728,7 @@ * @param nodeElem the element that represents the node * @return the cell parent within the node */ - private static Element getSelectionElement(Element nodeElem) { + static Element getSelectionElement(Element nodeElem) { return nodeElem.getFirstChildElement(); } @@ -1139,6 +1143,10 @@ nodeInfo.setDataDisplay(view); } + boolean belongsToTree(final CellTree tree) { + return this.tree == tree; + } + /** * Ensure that the animation frame exists and return it. *
diff --git a/user/test/com/google/gwt/user/cellview/client/CellTreeTest.java b/user/test/com/google/gwt/user/cellview/client/CellTreeTest.java index 990abbf..70c1630 100644 --- a/user/test/com/google/gwt/user/cellview/client/CellTreeTest.java +++ b/user/test/com/google/gwt/user/cellview/client/CellTreeTest.java
@@ -264,6 +264,81 @@ assertEquals("5", cViewChildLast.getElement().getAttribute("aria-setsize")); } + public void testExplicitKeyboardSelection() { + CellTree cellTree = (CellTree) tree; + TreeNode root = cellTree.getRootTreeNode(); + + final String cellTreeKeyboardSelectedItemStyleName = + cellTree.getStyle().cellTreeKeyboardSelectedItem(); + + // Navigate through tree to level 4 nodes. + TreeNode l1Node = root.setChildOpen(0, true); // a + CellTreeNodeView<?> l1View = cellTree.rootNode.getChildNode(0); + TreeNode l2Node = l1Node.setChildOpen(0, true); // aa + CellTreeNodeView<?> l2View = l1View.getChildNode(0); + TreeNode l3Node = l2Node.setChildOpen(0, true); // aaa + CellTreeNodeView<?> l3View = l2View.getChildNode(0); + + ((CellTreeNodeView.TreeNodeImpl) l3Node).flush(); + CellTreeNodeView<?> l4View = l3View.getChildNode(0); + // l4Node is leaf, cannot get a reference via setChildOpen except via non-public nodeView + TreeNode l4Node = l4View.getTreeNode(); // aaaa + + // Set keyboard selected node to a subtree node, l3Node = first child of l2Node + cellTree.setKeyboardSelectedTreeNode(l2Node, 0, true); + assertEquals(l3View, cellTree.getKeyboardSelectedNode()); + assertEquals(l3Node, cellTree.getKeyboardSelectedTreeNode()); + assertTrue(CellTreeNodeView.getSelectionElement(l3View.getElement()).getClassName() + .indexOf(cellTreeKeyboardSelectedItemStyleName) >= 0); + + // Set keyboard selected node to a leaf node. + cellTree.setKeyboardSelectedTreeNode(l3Node, 0, true); + assertEquals(l4View, cellTree.getKeyboardSelectedNode()); + assertEquals(l4Node, cellTree.getKeyboardSelectedTreeNode()); + assertTrue(CellTreeNodeView.getSelectionElement(l4View.getElement()).getClassName() + .indexOf(cellTreeKeyboardSelectedItemStyleName) >= 0); + + l1Node.setChildOpen(0, false); // close l2node + ((CellTreeNodeView.TreeNodeImpl) l1Node).flush(); + + // Try to select a leaf node in closed subtree + try { + cellTree.setKeyboardSelectedTreeNode(l3Node, 0, true); + fail("should have thrown"); + } catch (IllegalStateException e) { + assertEquals(e.getMessage(), "TreeNode no longer exists."); + } + + // Try to select a subtree node in closed subtree + try { + cellTree.setKeyboardSelectedTreeNode(l2Node, 0, true); + fail("should have thrown"); + } catch (IllegalStateException e) { + assertEquals(e.getMessage(), "TreeNode no longer exists."); + } + + // Still ok to select closed subtree node + cellTree.setKeyboardSelectedTreeNode(l1Node, 0, true); + + // Create another tree of same structure + CellTree anotherTree = createAbstractCellTree(model, root.getValue()); + // Navigate to leaf + l1Node = anotherTree.getRootTreeNode().setChildOpen(0, true); // a + ((CellTreeNodeView.TreeNodeImpl) l1Node).flush(); + + // Now l1Node refers to a subtree node in anotherTree + // Select in the same cell tree is ok + anotherTree.setKeyboardSelectedTreeNode(l1Node, 0, true); + + // Select in a different tree will throw exception. + try { + cellTree.setKeyboardSelectedTreeNode(l1Node, 0, true); + fail("should have thrown"); + } catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "The tree node does not belong to the tree."); + } + } + @Override protected <T> CellTree createAbstractCellTree(TreeViewModel model, T rootValue) { return new CellTree(model, rootValue);