com.databasesandlife.util.gwt.dropdownhierarchy.client.DropDownHierarchy Maven / Gradle / Ivy
Show all versions of java-common Show documentation
package com.databasesandlife.util.gwt.dropdownhierarchy.client;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import java.util.ArrayList;
import java.util.List;
/**
Displays a hierarchical choice as a series of drop-downs.
For example consider the tree of options
+-- A (can be selected)
+-- B
+-- C (can be selected)
+-- D (can be selected)
Only leaf nodes can be selected. In the above example only A, C and D can be selected. If D is selected then two drop-downs are displayed:
Changing options in the drop-down:
- Changing the top drop-down to A would select the leaf node A, and the second drop-down would disappear.
- Thereafter, selecting B from the single (top) drop-down would select C (the first leaf node accessible under B) and display
two drop-downs: the first displaying B (parent node) and the second displaying C (the selected leaf node).
The interfaces {@link Node Node}, {@link LeafNode LeafNode} and {@link NonLeafNode NonLeafNode} must be implemented
by the application program. They have methods such as getParent and getChildren etc.
The application should register a {@link ChangeListener ChangeListener} with the object to be notified when the user makes a selection.
A node has an identity. For example, in the above example the nodes are objects, but the client may request that
node with an internal String id, "D", be selected. In this case "D" is the identity of the node. The identity may be any object type;
The NODE_ID generic parameter specifies what type identifies nodes.
A DropDownHierarchy is a GWT widget which may be included in any GWT application. Make sure that the source is available to the
GWT compiler (the source is included in the databasesandlife-util.jar) and add the following line to the application's GWT XML file:
<inherits name="com.databasesandlife.util.gwt.dropdownhierarchy.DropdownHierarchy"/>
@param Node Identifier
* @author This source is copyright Adrian Smith and licensed under the LGPL 3.
* @see Project on GitHub
*/
@SuppressWarnings("serial")
public class DropDownHierarchy extends Composite {
// ------------------------------------------------------------------------------------------------------------
// Interfaces, that the client must implement
// ------------------------------------------------------------------------------------------------------------
public interface Node {
public NonLeafNode getParent();
public String getDisplayName();
}
public interface LeafNode extends Node {
N getId();
}
public interface NonLeafNode extends Node {
public Node[] getChildren();
}
public interface ChangeListener {
/** The implementation does not need to call hier.setSelected(newId) with the new node */
public void onDropDownHierarchyChange(DropDownHierarchy source, N newId, LeafNode newSelected);
}
// ------------------------------------------------------------------------------------------------------------
// Exceptions
// ------------------------------------------------------------------------------------------------------------
public static class NodeNotFoundException extends RuntimeException {
public NodeNotFoundException(String nodeId) { super(nodeId); }
}
// ------------------------------------------------------------------------------------------------------------
// Attributes
// ------------------------------------------------------------------------------------------------------------
final NonLeafNode rootNode;
ChangeListener changeListener = null;
final VerticalPanel container = new VerticalPanel();
// ------------------------------------------------------------------------------------------------------------
// Constructors & Methods
// ------------------------------------------------------------------------------------------------------------
protected LeafNode findLeafForId(NonLeafNode node, N id) throws NodeNotFoundException {
for (var child : node.getChildren()) {
if (child instanceof LeafNode)
if (((LeafNode) child).getId().equals(id)) return (LeafNode) child;
if (child instanceof NonLeafNode)
try { return findLeafForId((NonLeafNode) child, id); }
catch (NodeNotFoundException e) { }
}
throw new NodeNotFoundException("" + id);
}
protected static LeafNode findAnyLeafNodeUnder(Node parent) {
if (parent instanceof LeafNode) return (LeafNode) parent;
if (parent instanceof NonLeafNode) return findAnyLeafNodeUnder(((NonLeafNode) parent).getChildren()[0]);
throw new RuntimeException();
}
protected boolean isChildOf(Node child, Node potentialParent) {
if (potentialParent.equals(child)) return true;
else if (child.getParent() == null) return false;
else return isChildOf(child.getParent(), potentialParent);
}
public DropDownHierarchy(NonLeafNode rootNode, N selectedNodeId) throws NodeNotFoundException {
this.rootNode = rootNode;
setSelected(selectedNodeId);
initWidget(container);
}
public static DropDownHierarchy newIgnoreNotFound(NonLeafNode rootNode, N selectedNodeId) {
try {
return new DropDownHierarchy(rootNode, selectedNodeId);
}
catch (NodeNotFoundException e) {
return new DropDownHierarchy(rootNode, findAnyLeafNodeUnder(rootNode).getId());
}
}
/**
* When the user makes a selection, this change listener should be called.
* Any previous change listener is deleted.
*/
public void setChangeListener(ChangeListener c) {
changeListener = c;
}
/**
* Changes which node is selected.
* Updates the user-interface to reflect the newly selected node.
* The change listener is not called.
*/
public void setSelected(N newSelectedNodeId) throws NodeNotFoundException {
var selectedNode = findLeafForId(rootNode, newSelectedNodeId);
final List> fromRootToLeaf = new ArrayList<>();
for (var n = selectedNode.getParent(); n != null; n = n.getParent()) fromRootToLeaf.add(0, n);
container.clear();
for (var n : fromRootToLeaf) {
final ListBox dropdown = new ListBox();
final Node[] children = n.getChildren();
var selectedIndex = -1;
for (var idx = 0; idx < children.length; idx++) {
var optionNode = children[idx];
dropdown.addItem(optionNode.getDisplayName());
if (isChildOf(selectedNode, optionNode)) selectedIndex = idx;
}
assert selectedIndex != -1;
dropdown.setSelectedIndex(selectedIndex);
dropdown.addChangeHandler(new ChangeHandler() {
public void onChange(ChangeEvent event) {
var selectedAtThisLevel = children[dropdown.getSelectedIndex()];
var leafNodeForNewOption = findAnyLeafNodeUnder(selectedAtThisLevel);
setSelected(leafNodeForNewOption.getId());
((ListBox) container.getWidget(fromRootToLeaf.indexOf(n))).setFocus(true);
if (changeListener != null) changeListener.onDropDownHierarchyChange(DropDownHierarchy.this,
leafNodeForNewOption.getId(), leafNodeForNewOption);
}
});
container.add(dropdown);
}
}
}