Revision: 8557
Author: [email protected]
Date: Tue Aug 17 11:18:17 2010
Log: First cut at keyboard navigation for CellTree
Review at http://gwt-code-reviews.appspot.com/758802
Review by: [email protected]
http://code.google.com/p/google-web-toolkit/source/detail?r=8557
Modified:
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactTreeViewModel.java
/trunk/user/src/com/google/gwt/user/cellview/client/CellTree.css
/trunk/user/src/com/google/gwt/user/cellview/client/CellTree.java
/trunk/user/src/com/google/gwt/user/cellview/client/CellTreeClean.css
/trunk/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
=======================================
---
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactTreeViewModel.java
Tue Aug 17 10:14:36 2010
+++
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactTreeViewModel.java
Tue Aug 17 11:18:17 2010
@@ -21,8 +21,11 @@
import com.google.gwt.cell.client.CompositeCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.HasCell;
+import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
import
com.google.gwt.sample.showcase.client.content.cell.ContactDatabase.Category;
@@ -99,6 +102,16 @@
public int compareTo(LetterCount o) {
return (o == null) ? -1 : (firstLetter - o.firstLetter);
}
+
+ @Override
+ public boolean equals(Object o) {
+ return compareTo((LetterCount) o) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return firstLetter;
+ }
/**
* Increment the count.
@@ -185,6 +198,15 @@
}
});
contactCell = new CompositeCell<ContactInfo>(hasCells) {
+ @Override
+ public void onBrowserEvent(Element parent, ContactInfo value, Object
key,
+ NativeEvent event, ValueUpdater<ContactInfo> valueUpdater) {
+ if ("keyup".equals(event.getType())
+ && event.getKeyCode() == KeyCodes.KEY_ENTER) {
+
selectionModel.setSelected(value, !selectionModel.isSelected(value));
+ }
+ }
+
@Override
public void render(ContactInfo value, Object key, StringBuilder sb) {
sb.append("<table><tbody><tr>");
=======================================
--- /trunk/user/src/com/google/gwt/user/cellview/client/CellTree.css Mon
Jun 7 12:20:31 2010
+++ /trunk/user/src/com/google/gwt/user/cellview/client/CellTree.css Tue
Aug 17 11:18:17 2010
@@ -23,9 +23,29 @@
padding-right: 3px;
}
+/*
+div:focus { outline: none; }
+*/
+
+.keyboardSelectedItem {
+ background-color: #ffff00;
+}
+
.openItem {
}
+
+.topItem {
+
+}
+
+.topItemImage {
+
+}
+
+.topItemImageValue {
+
+}
@sprite .selectedItem {
gwt-image: 'cellTreeSelectedBackground';
@@ -39,15 +59,3 @@
padding-left: 16px;
outline: none;
}
-
-.topItem {
-
-}
-
-.topItemImage {
-
-}
-
-.topItemImageValue {
-
-}
=======================================
--- /trunk/user/src/com/google/gwt/user/cellview/client/CellTree.java Mon
Aug 2 11:31:26 2010
+++ /trunk/user/src/com/google/gwt/user/cellview/client/CellTree.java Tue
Aug 17 11:18:17 2010
@@ -21,6 +21,7 @@
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.ImageResource;
@@ -38,6 +39,33 @@
* A view of a tree.
*/
public class CellTree extends Composite implements HasAnimation {
+
+ /**
+ * A cleaner version of the table that uses less graphics.
+ */
+ public static interface CleanResources extends Resources {
+
+ @Source("cellTreeClosedArrow.png")
+ ImageResource cellTreeClosedItem();
+
+ @Source("cellTreeLoadingClean.gif")
+ ImageResource cellTreeLoading();
+
+ @Source("cellTreeOpenArrow.png")
+ ImageResource cellTreeOpenItem();
+
+ @Source("CellTreeClean.css")
+ CleanStyle cellTreeStyle();
+ }
+
+ /**
+ * A cleaner version of the table that uses less graphics.
+ */
+ public static interface CleanStyle extends Style {
+ String topItem();
+
+ String topItemImageValue();
+ }
/**
* A node animation.
@@ -310,6 +338,11 @@
*/
String itemValue();
+ /**
+ * Applied to the keyboard selected item.
+ */
+ String keyboardSelectedItem();
+
/**
* Applied to open tree items.
*/
@@ -340,33 +373,6 @@
*/
String topItemImageValue();
}
-
- /**
- * A cleaner version of the table that uses less graphics.
- */
- public static interface CleanStyle extends Style {
- String topItem();
-
- String topItemImageValue();
- }
-
- /**
- * A cleaner version of the table that uses less graphics.
- */
- public static interface CleanResources extends Resources {
-
- @Source("cellTreeClosedArrow.png")
- ImageResource cellTreeClosedItem();
-
- @Source("cellTreeLoadingClean.gif")
- ImageResource cellTreeLoading();
-
- @Source("cellTreeOpenArrow.png")
- ImageResource cellTreeOpenItem();
-
- @Source("CellTreeClean.css")
- CleanStyle cellTreeStyle();
- }
/**
* The default number of children to show under a tree node.
@@ -387,6 +393,8 @@
*/
private NodeAnimation animation;
+ private boolean cellIsEditing;
+
/**
* The HTML used to generate the closed image.
*/
@@ -412,6 +420,12 @@
*/
private boolean isAnimationEnabled;
+ /**
+ * The {...@link CellTreeNodeView} whose children are currently being
selected
+ * using the keyboard.
+ */
+ private CellTreeNodeView<?> keyboardSelectedNode;
+
/**
* The HTML used to generate the loading image.
*/
@@ -461,8 +475,8 @@
* @param rootValue the hidden root value of the tree
* @param resources the resources used to render the tree
*/
- public <T> CellTree(
- TreeViewModel viewModel, T rootValue, Resources resources) {
+ public <T> CellTree(TreeViewModel viewModel, T rootValue,
+ Resources resources) {
this.viewModel = viewModel;
this.style = resources.cellTreeStyle();
this.style.ensureInjected();
@@ -485,13 +499,14 @@
setAnimation(SlideAnimation.create());
// Add event handlers.
- sinkEvents(Event.ONCLICK);
+ sinkEvents(Event.ONCLICK | Event.ONKEYDOWN | Event.ONKEYUP);
// Associate a view with the item.
- CellTreeNodeView<T> root = new CellTreeNodeView<T>(
- this, null, null, getElement(), rootValue);
- rootNode = root;
+ CellTreeNodeView<T> root = new CellTreeNodeView<T>(this, null, null,
+ getElement(), rootValue);
+ keyboardSelectedNode = rootNode = root;
root.setOpen(true);
+ keyboardSelectedNode.keyboardEnter(0, false);
}
/**
@@ -527,8 +542,32 @@
CellBasedWidgetImpl.get().onBrowserEvent(this, event);
super.onBrowserEvent(event);
- Element target = event.getEventTarget().cast();
-
+ String eventType = event.getType();
+
+ // Keep track of whether the user has focused on the widget
+ if ("blur".equals(eventType)) {
+ keyboardSelectedNode.keyboardBlur();
+ return;
+ }
+ if ("focus".equals(eventType)) {
+ keyboardSelectedNode.keyboardFocus();
+ return;
+ }
+
+ boolean keyUp = "keyup".equals(eventType);
+ boolean keyDown = "keydown".equals(eventType);
+
+ // Ignore keydown events unless the cell is in edit mode
+ if (keyDown && !cellIsEditing) {
+ return;
+ }
+ if (keyUp && !cellIsEditing) {
+ if (handleKey(event)) {
+ return;
+ }
+ }
+
+ Element target = event.getEventTarget().cast();
ArrayList<Element> chain = new ArrayList<Element>();
collectElementChain(chain, getElement(), target);
@@ -544,10 +583,17 @@
nodeView.showMore();
return;
}
+
+ // Move the keyboard focus to the clicked item
+ keyboardSelectedNode.keyboardExit();
+ keyboardSelectedNode = nodeView.getParentNode();
+ keyboardSelectedNode.keyboardEnter(target, true);
}
- // Forward the event to the cell.
- if (nodeView.getCellParent().isOrHasChild(target)) {
+ // Forward the event to the cell
+ if (nodeView.getCellParent().isOrHasChild(target)
+ || (eventType.startsWith("key")
+ && nodeView.getCellParent().getParentElement() == target)) {
nodeView.fireEventToCell(event);
}
}
@@ -634,16 +680,16 @@
*/
void maybeAnimateTreeNode(CellTreeNodeView<?> node) {
if (animation != null) {
- animation.animate(node,
- node.consumeAnimate() && isAnimationEnabled()
&& !node.isRootNode());
+ animation.animate(node, node.consumeAnimate() && isAnimationEnabled()
+ && !node.isRootNode());
}
}
/**
* Collects parents going up the element tree, terminated at the tree
root.
*/
- private void collectElementChain(
- ArrayList<Element> chain, Element hRoot, Element hElem) {
+ private void collectElementChain(ArrayList<Element> chain, Element hRoot,
+ Element hElem) {
if ((hElem == null) || (hElem == hRoot)) {
return;
}
@@ -652,8 +698,8 @@
chain.add(hElem);
}
- private CellTreeNodeView<?> findItemByChain(
- ArrayList<Element> chain, int idx, CellTreeNodeView<?> parent) {
+ private CellTreeNodeView<?> findItemByChain(ArrayList<Element> chain,
+ int idx, CellTreeNodeView<?> parent) {
if (idx == chain.size()) {
return parent;
}
@@ -701,4 +747,80 @@
sb.append("\"></div>");
return sb.toString();
}
-}
+
+ /**
+ * @return true if the key event was consumed by navigation, false if it
+ * should be passed on to the underlying Cell.
+ */
+ private boolean handleKey(Event event) {
+ int keyCode = event.getKeyCode();
+
+ CellTreeNodeView<?> child = null;
+ int keyboardSelectedIndex =
keyboardSelectedNode.getKeyboardSelectedIndex();
+ if (keyboardSelectedIndex != -1
+ && keyboardSelectedNode.getChildCount() > keyboardSelectedIndex) {
+ child = keyboardSelectedNode.getChildNode(keyboardSelectedIndex);
+ }
+
+ CellTreeNodeView<?> parent = keyboardSelectedNode.getParentNode();
+ switch (keyCode) {
+ case KeyCodes.KEY_UP:
+ if (keyboardSelectedNode.getKeyboardSelectedIndex() == 0) {
+ if (!keyboardSelectedNode.isRootNode()) {
+ if (parent != null) {
+ keyboardSelectedNode.keyboardExit();
+ parent.keyboardEnter(parent.indexOf(keyboardSelectedNode),
true);
+ keyboardSelectedNode = parent;
+ }
+ }
+ } else {
+ keyboardSelectedNode.keyboardUp();
+ // Descend into open nodes, go to bottom of leaf node
+ int index = keyboardSelectedNode.getKeyboardSelectedIndex();
+ while ((child =
keyboardSelectedNode.getChildNode(index)).isOpen()) {
+ keyboardSelectedNode.keyboardExit();
+ index = child.getChildCount() - 1;
+ child.keyboardEnter(index, true);
+ keyboardSelectedNode = child;
+ }
+ }
+ return true;
+
+ case KeyCodes.KEY_DOWN:
+ if (child != null && child.isOpen()) {
+ keyboardSelectedNode.keyboardExit();
+ child.keyboardEnter(0, true);
+ keyboardSelectedNode = child;
+ } else if (!keyboardSelectedNode.keyboardDown()) {
+ if (parent != null) {
+ keyboardSelectedNode.keyboardExit();
+ parent.keyboardEnter(parent.indexOf(keyboardSelectedNode),
true);
+ // If already at last node of a given level, go up
+ while (!parent.keyboardDown()) {
+ CellTreeNodeView<?> newParent = parent.getParentNode();
+ if (newParent != null) {
+ parent.keyboardExit();
+ newParent.keyboardEnter(newParent.indexOf(parent) + 1,
true);
+ parent = newParent;
+ }
+ }
+ keyboardSelectedNode = parent;
+ }
+ }
+ return true;
+
+ case KeyCodes.KEY_LEFT:
+ case KeyCodes.KEY_RIGHT:
+ case KeyCodes.KEY_ENTER:
+ // TODO(rice) - try different key bahavior mappings such as
+ // left=close, right=open, enter=toggle.
+ if (child != null && !child.isLeaf()) {
+ child.setOpen(!child.isOpen());
+ return true;
+ }
+ break;
+ }
+
+ return false;
+ }
+}
=======================================
--- /trunk/user/src/com/google/gwt/user/cellview/client/CellTreeClean.css
Mon Jun 7 12:20:31 2010
+++ /trunk/user/src/com/google/gwt/user/cellview/client/CellTreeClean.css
Tue Aug 17 11:18:17 2010
@@ -22,6 +22,14 @@
padding-left: 3px;
padding-right: 3px;
}
+
+/*
+div:focus { outline: none; }
+*/
+
+.keyboardSelectedItem {
+ background-color: #ffff00;
+}
.openItem {
=======================================
---
/trunk/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
Tue Aug 17 10:14:36 2010
+++
/trunk/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
Tue Aug 17 11:18:17 2010
@@ -53,72 +53,6 @@
* @param <T> the type that this view contains
*/
class CellTreeNodeView<T> extends UIObject {
-
- /**
- * The element used in place of an image when a node has no children.
- */
- private static final String LEAF_IMAGE =
- "<div style='position:absolute;display:none;'></div>";
-
- /**
- * The temporary element used to render child items.
- */
- private static com.google.gwt.user.client.Element tmpElem;
-
- /**
- * Returns the element that parents the cell contents of the node.
- *
- * @param nodeElem the element that represents the node
- * @return the cell parent within the node
- */
- private static Element getCellParent(Element nodeElem) {
- return getSelectionElement(nodeElem).getFirstChildElement().getChild(
- 1).cast();
- }
-
- /**
- * Returns the element that selection is applied to.
- *
- * @param nodeElem the element that represents the node
- * @return the cell parent within the node
- */
- private static Element getImageElement(Element nodeElem) {
- return
getSelectionElement(nodeElem).getFirstChildElement().getFirstChildElement();
- }
-
- /**
- * Returns the element that selection is applied to.
- *
- * @param nodeElem the element that represents the node
- * @return the cell parent within the node
- */
- private static Element getSelectionElement(Element nodeElem) {
- return nodeElem.getFirstChildElement();
- }
-
- /**
- * @return the temporary element used to create elements
- */
- private static com.google.gwt.user.client.Element getTmpElem() {
- if (tmpElem == null) {
- tmpElem = Document.get().createDivElement().cast();
- }
- return tmpElem;
- }
-
- /**
- * Show or hide an element.
- *
- * @param element the element to show or hide
- * @param show true to show, false to hide
- */
- private static void showOrHide(Element element, boolean show) {
- if (show) {
- element.getStyle().clearDisplay();
- } else {
- element.getStyle().setDisplay(Display.NONE);
- }
- }
/**
* The {...@link com.google.gwt.view.client.HasData} used to show children.
@@ -127,8 +61,6 @@
*/
private static class NodeCellList<C> implements HasData<C> {
- private HandlerManager handlerManger = new HandlerManager(this);
-
/**
* The view used by the NodeCellList.
*/
@@ -140,8 +72,8 @@
this.childContainer = childContainer;
}
- public <H extends EventHandler> HandlerRegistration addHandler(
- H handler, Type<H> type) {
+ public <H extends EventHandler> HandlerRegistration addHandler(H
handler,
+ Type<H> type) {
return handlerManger.addHandler(type, handler);
}
@@ -154,8 +86,8 @@
}
public ElementIterator getChildIterator() {
- return new HasDataPresenter.DefaultElementIterator(
- this, childContainer.getFirstChildElement());
+ return new HasDataPresenter.DefaultElementIterator(this,
+ childContainer.getFirstChildElement());
}
public void onUpdateSelection() {
@@ -260,15 +192,19 @@
public void replaceChildren(List<C> values, int start, String html) {
Map<Object, CellTreeNodeView<?>> savedViews =
saveChildState(values, 0);
- Element newChildren = AbstractHasData.convertToElements(
- nodeView.tree, getTmpElem(), html);
- AbstractHasData.replaceChildren(
- nodeView.tree, childContainer, newChildren, start, html);
+ Element newChildren =
AbstractHasData.convertToElements(nodeView.tree,
+ getTmpElem(), html);
+ AbstractHasData.replaceChildren(nodeView.tree, childContainer,
+ newChildren, start, html);
loadChildState(values, 0, savedViews);
}
public void resetFocus() {
+ if (nodeView.keyboardSelectedIndex != -1) {
+ nodeView.keyboardEnter(nodeView.keyboardSelectedIndex,
+ nodeView.keyboardFocused);
+ }
}
public void setLoadingState(LoadingState state) {
@@ -294,13 +230,14 @@
int end = start + len;
int childCount = nodeView.getChildCount();
ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
- Element childElem =
nodeView.ensureChildContainer().getFirstChildElement();
+ Element childElem =
+ nodeView.ensureChildContainer().getFirstChildElement();
for (int i = start; i < end; i++) {
C childValue = values.get(i - start);
- CellTreeNodeView<C> child = nodeView.createTreeNodeView(
- nodeInfo, childElem, childValue, null);
- CellTreeNodeView<?> savedChild = savedViews.remove(
- providesKey.getKey(childValue));
+ CellTreeNodeView<C> child = nodeView.createTreeNodeView(nodeInfo,
+ childElem, childValue, null);
+ CellTreeNodeView<?> savedChild =
+ savedViews.remove(providesKey.getKey(childValue));
// Copy the saved child's state into the new child
if (savedChild != null) {
child.animationFrame = savedChild.animationFrame;
@@ -344,8 +281,8 @@
* @param start the start index
* @return the map of open nodes
*/
- private Map<Object, CellTreeNodeView<?>> saveChildState(
- List<C> values, int start) {
+ private Map<Object, CellTreeNodeView<?>> saveChildState(List<C>
values,
+ int start) {
// Ensure that we have a children array.
if (nodeView.children == null) {
nodeView.children = new ArrayList<CellTreeNodeView<?>>();
@@ -355,8 +292,8 @@
int len = values.size();
int end = start + len;
int childCount = nodeView.getChildCount();
- Map<Object, CellTreeNodeView<?>> openNodes = new HashMap<
- Object, CellTreeNodeView<?>>();
+ Map<Object, CellTreeNodeView<?>> openNodes =
+ new HashMap<Object, CellTreeNodeView<?>>();
for (int i = start; i < end && i < childCount; i++) {
CellTreeNodeView<?> child = nodeView.getChildNode(i);
// Ignore child nodes that are closed.
@@ -367,8 +304,8 @@
// Trim the saved views down to the children that still exists.
ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
- Map<Object, CellTreeNodeView<?>> savedViews = new HashMap<
- Object, CellTreeNodeView<?>>();
+ Map<Object, CellTreeNodeView<?>> savedViews =
+ new HashMap<Object, CellTreeNodeView<?>>();
for (C childValue : values) {
// Remove any child elements that correspond to prior children
// so the call to setInnerHtml will not destroy them
@@ -384,7 +321,9 @@
}
private final Cell<C> cell;
+
private final int defaultPageSize;
+ private HandlerManager handlerManger = new HandlerManager(this);
private final NodeInfo<C> nodeInfo;
private CellTreeNodeView<?> nodeView;
private final HasDataPresenter<C> presenter;
@@ -396,8 +335,8 @@
this.nodeView = nodeView;
cell = nodeInfo.getCell();
- presenter = new HasDataPresenter<C>(
- this, new View(nodeView.ensureChildContainer()), pageSize);
+ presenter = new HasDataPresenter<C>(this, new View(
+ nodeView.ensureChildContainer()), pageSize);
// Use a pager to update buttons.
presenter.addRowCountChangeHandler(new RowCountChangeEvent.Handler()
{
@@ -492,6 +431,70 @@
nodeView.listView = this;
}
}
+
+ /**
+ * The element used in place of an image when a node has no children.
+ */
+ private static final String LEAF_IMAGE = "<div
style='position:absolute;display:none;'></div>";
+
+ /**
+ * The temporary element used to render child items.
+ */
+ private static com.google.gwt.user.client.Element tmpElem;
+
+ /**
+ * Returns the element that parents the cell contents of the node.
+ *
+ * @param nodeElem the element that represents the node
+ * @return the cell parent within the node
+ */
+ private static Element getCellParent(Element nodeElem) {
+ return
getSelectionElement(nodeElem).getFirstChildElement().getChild(1).cast();
+ }
+
+ /**
+ * Returns the element that selection is applied to.
+ *
+ * @param nodeElem the element that represents the node
+ * @return the cell parent within the node
+ */
+ private static Element getImageElement(Element nodeElem) {
+ return
getSelectionElement(nodeElem).getFirstChildElement().getFirstChildElement();
+ }
+
+ /**
+ * Returns the element that selection is applied to.
+ *
+ * @param nodeElem the element that represents the node
+ * @return the cell parent within the node
+ */
+ private static Element getSelectionElement(Element nodeElem) {
+ return nodeElem.getFirstChildElement();
+ }
+
+ /**
+ * @return the temporary element used to create elements
+ */
+ private static com.google.gwt.user.client.Element getTmpElem() {
+ if (tmpElem == null) {
+ tmpElem = Document.get().createDivElement().cast();
+ }
+ return tmpElem;
+ }
+
+ /**
+ * Show or hide an element.
+ *
+ * @param element the element to show or hide
+ * @param show true to show, false to hide
+ */
+ private static void showOrHide(Element element, boolean show) {
+ if (show) {
+ element.getStyle().clearDisplay();
+ } else {
+ element.getStyle().setDisplay(Display.NONE);
+ }
+ }
/**
* True during the time a node should be animated.
@@ -531,6 +534,21 @@
*/
private Element emptyMessageElem;
+ /**
+ * True if the keyboard selection has focus.
+ */
+ private boolean keyboardFocused;
+
+ /**
+ * The index of the keyboard selection within this node's children.
+ */
+ private int keyboardSelectedIndex = -1;
+
+ /**
+ * The parent element of the current keyboard selection, or null.
+ */
+ private Element keyboardSelection;
+
/**
* The list view used to display the nodes.
*/
@@ -602,6 +620,10 @@
public CellTreeNodeView<?> getChildNode(int childIndex) {
return children.get(childIndex);
}
+
+ public boolean isLeaf() {
+ return tree.getTreeViewModel().isLeaf(value);
+ }
/**
* Check whether or not this node is open.
@@ -632,10 +654,16 @@
// Sink events for the new node.
if (nodeInfo != null) {
+ Set<String> eventsToSink = new HashSet<String>();
+ // Listen for focus and blur for keyboard navigation
+ eventsToSink.add("focus");
+ eventsToSink.add("blur");
+
Set<String> consumedEvents =
nodeInfo.getCell().getConsumedEvents();
if (consumedEvents != null) {
- CellBasedWidgetImpl.get().sinkEvents(tree, consumedEvents);
- }
+ eventsToSink.addAll(consumedEvents);
+ }
+ CellBasedWidgetImpl.get().sinkEvents(tree, eventsToSink);
}
}
@@ -658,6 +686,7 @@
cleanup();
tree.maybeAnimateTreeNode(this);
updateImage(false);
+ keyboardExit();
}
}
@@ -698,8 +727,8 @@
* @param viewData view data associated with the node
* @return a TreeNodeView of suitable type
*/
- protected <C> CellTreeNodeView<C> createTreeNodeView(
- NodeInfo<C> nodeInfo, Element childElem, C childValue, Object
viewData) {
+ protected <C> CellTreeNodeView<C> createTreeNodeView(NodeInfo<C>
nodeInfo,
+ Element childElem, C childValue, Object viewData) {
return new CellTreeNodeView<C>(tree, this, nodeInfo, childElem,
childValue);
}
@@ -712,7 +741,8 @@
if (parentNodeInfo != null) {
Cell<T> parentCell = parentNodeInfo.getCell();
String eventType = event.getType();
- SelectionModel<? super T> selectionModel =
parentNodeInfo.getSelectionModel();
+ SelectionModel<? super T> selectionModel =
+ parentNodeInfo.getSelectionModel();
// Update selection.
if (selectionModel != null && "click".equals(eventType)
@@ -726,8 +756,8 @@
Object key = getValueKey();
Set<String> consumedEvents = parentCell.getConsumedEvents();
if (consumedEvents != null && consumedEvents.contains(eventType)) {
- parentCell.onBrowserEvent(
- cellParent, value, key, event,
parentNodeInfo.getValueUpdater());
+ parentCell.onBrowserEvent(cellParent, value, key, event,
+ parentNodeInfo.getValueUpdater());
}
}
}
@@ -764,8 +794,8 @@
* @param <C> the child data type of the node
*/
protected <C> void onOpen(final NodeInfo<C> nodeInfo) {
- NodeCellList<C> view = new NodeCellList<C>(
- nodeInfo, this, tree.getDefaultNodeSize());
+ NodeCellList<C> view = new NodeCellList<C>(nodeInfo, this,
+ tree.getDefaultNodeSize());
listView = view;
view.setSelectionModel(nodeInfo.getSelectionModel());
nodeInfo.setDataDisplay(view);
@@ -826,10 +856,25 @@
}
return contentContainer;
}
+
+ int getKeyboardSelectedIndex() {
+ return keyboardSelectedIndex;
+ }
+
+ /**
+ * Return the parent node, or null if this node is the root.
+ */
+ CellTreeNodeView<?> getParentNode() {
+ return parentNode;
+ }
Element getShowMoreElement() {
return showMoreElem;
}
+
+ int indexOf(CellTreeNodeView<?> child) {
+ return children.indexOf(child);
+ }
/**
* Check if this node is a root node.
@@ -839,12 +884,122 @@
boolean isRootNode() {
return parentNode == null;
}
+
+ /**
+ * The user has "tabbed" away from the node -- don't force refocus when
+ * re-rendering.
+ */
+ void keyboardBlur() {
+ keyboardFocused = false;
+ }
+
+ /**
+ * Handle a keyboard navigation event to move down one item. Returns
true if
+ * movement was possible (the current item was not the last child of its
+ * parent).
+ *
+ * @return true if the selection moved
+ */
+ boolean keyboardDown() {
+ Element next = keyboardSelection.getNextSiblingElement();
+ if (next != null) {
+ keyboardExit();
+ keyboardEnterAtElement(next, true);
+ keyboardSelectedIndex++;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle a keyboard event to move focus into the current item list at
the
+ * child that contains the given Element.
+ *
+ * @param focus if true, focus on the element
+ */
+ void keyboardEnter(Element element, boolean focus) {
+ Element item = ensureChildContainer().getFirstChildElement();
+ int index = 0;
+ boolean found = false;
+ for (int i = 0; i < getChildCount(); i++) {
+ if (item.isOrHasChild(element)) {
+ found = true;
+ break;
+ }
+ item = item.getNextSiblingElement();
+ index++;
+ }
+ if (found) {
+ keyboardEnterAtElement(item, focus);
+ keyboardSelectedIndex = index;
+ }
+ }
+
+ /**
+ * Handle a keyboard event to move focus into the current item list at
the
+ * given child index.
+ *
+ * @param focus if true, focus on the element
+ */
+ void keyboardEnter(int index, boolean focus) {
+ if (index < 0 || index >= getChildCount()) {
+ throw new IllegalArgumentException("Index out of range: " + index);
+ }
+ Element item = ensureChildContainer().getChild(index).cast();
+ keyboardEnterAtElement(item, focus);
+ keyboardSelectedIndex = index;
+ }
+
+ /**
+ * Handle a keyboard event to move focus away from the current item.
+ */
+ void keyboardExit() {
+ if (keyboardSelection == null) {
+ return;
+ }
+ Element child =
+ keyboardSelection.getFirstChildElement().getFirstChildElement();
+ child.removeAttribute("tabIndex");
+ child.removeClassName(tree.getStyle().keyboardSelectedItem());
+ keyboardSelection = null;
+ keyboardSelectedIndex = -1;
+ keyboardFocused = false;
+ }
+
+ void keyboardFocus() {
+ keyboardFocused = true;
+ }
+
+ /**
+ * Handle a keyboard navigation event to move up one item. Returns true
if
+ * movement was possible (the current item was not the first child of its
+ * parent).
+ *
+ * @return true if the selection moved
+ */
+ boolean keyboardUp() {
+ Element prev =
+ keyboardSelection.getParentElement().getFirstChildElement();
+ Element next = prev.getNextSiblingElement();
+ while (next != null && next != keyboardSelection) {
+ prev = next;
+ next = next.getNextSiblingElement();
+ }
+ if (next == keyboardSelection) {
+ int index = keyboardSelectedIndex;
+ keyboardExit();
+ keyboardEnterAtElement(prev, true);
+ keyboardSelectedIndex = index - 1;
+ return true;
+ }
+ return false;
+ }
void showFewer() {
Range range = listView.getVisibleRange();
int defaultPageSize = listView.getDefaultPageSize();
- int maxSize = Math.max(
- defaultPageSize, range.getLength() - defaultPageSize);
+ int maxSize = Math.max(defaultPageSize,
+ range.getLength() - defaultPageSize);
listView.setVisibleRange(range.getStart(), maxSize);
}
@@ -853,6 +1008,19 @@
int pageSize = range.getLength() + listView.getDefaultPageSize();
listView.setVisibleRange(range.getStart(), pageSize);
}
+
+ private void keyboardEnterAtElement(Element item, boolean focus) {
+ if (item != null) {
+ Element child = item.getFirstChildElement().getFirstChildElement();
+ child.addClassName(tree.getStyle().keyboardSelectedItem());
+ child.setTabIndex(0);
+ if (focus) {
+ child.focus();
+ }
+ keyboardSelection = item;
+ keyboardFocused = focus;
+ }
+ }
/**
* Update the image based on the current state.
@@ -869,8 +1037,8 @@
boolean isTopLevel = parentNode.isRootNode();
String html = tree.getClosedImageHtml(isTopLevel);
if (open) {
- html = isLoading ? tree.getLoadingImageHtml() :
tree.getOpenImageHtml(
- isTopLevel);
+ html = isLoading ? tree.getLoadingImageHtml()
+ : tree.getOpenImageHtml(isTopLevel);
}
if (nodeInfoLoaded && nodeInfo == null) {
html = LEAF_IMAGE;
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors