You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ITree.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.itmill.toolkit.terminal.gwt.client.ui;
  5. import java.util.HashMap;
  6. import java.util.HashSet;
  7. import java.util.Iterator;
  8. import java.util.Set;
  9. import com.google.gwt.user.client.DOM;
  10. import com.google.gwt.user.client.Element;
  11. import com.google.gwt.user.client.Event;
  12. import com.google.gwt.user.client.Window;
  13. import com.google.gwt.user.client.ui.FlowPanel;
  14. import com.google.gwt.user.client.ui.SimplePanel;
  15. import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
  16. import com.itmill.toolkit.terminal.gwt.client.Paintable;
  17. import com.itmill.toolkit.terminal.gwt.client.UIDL;
  18. import com.itmill.toolkit.terminal.gwt.client.Util;
  19. /**
  20. *
  21. */
  22. public class ITree extends FlowPanel implements Paintable {
  23. public static final String CLASSNAME = "i-tree";
  24. private Set selectedIds = new HashSet();
  25. private ApplicationConnection client;
  26. private String paintableId;
  27. private boolean selectable;
  28. private boolean isMultiselect;
  29. private final HashMap keyToNode = new HashMap();
  30. /**
  31. * This map contains captions and icon urls for actions like: * "33_c" ->
  32. * "Edit" * "33_i" -> "http://dom.com/edit.png"
  33. */
  34. private final HashMap actionMap = new HashMap();
  35. private boolean immediate;
  36. private boolean isNullSelectionAllowed = true;
  37. private boolean disabled = false;
  38. private boolean readonly;
  39. public ITree() {
  40. super();
  41. setStyleName(CLASSNAME);
  42. }
  43. private void updateActionMap(UIDL c) {
  44. final Iterator it = c.getChildIterator();
  45. while (it.hasNext()) {
  46. final UIDL action = (UIDL) it.next();
  47. final String key = action.getStringAttribute("key");
  48. final String caption = action.getStringAttribute("caption");
  49. actionMap.put(key + "_c", caption);
  50. if (action.hasAttribute("icon")) {
  51. // TODO need some uri handling ??
  52. actionMap.put(key + "_i", client.translateToolkitUri(action
  53. .getStringAttribute("icon")));
  54. }
  55. }
  56. }
  57. public String getActionCaption(String actionKey) {
  58. return (String) actionMap.get(actionKey + "_c");
  59. }
  60. public String getActionIcon(String actionKey) {
  61. return (String) actionMap.get(actionKey + "_i");
  62. }
  63. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  64. // Ensure correct implementation and let container manage caption
  65. if (client.updateComponent(this, uidl, true)) {
  66. return;
  67. }
  68. this.client = client;
  69. if (uidl.hasAttribute("partialUpdate")) {
  70. handleUpdate(uidl);
  71. return;
  72. }
  73. paintableId = uidl.getId();
  74. immediate = uidl.hasAttribute("immediate");
  75. disabled = uidl.getBooleanAttribute("disabled");
  76. readonly = uidl.getBooleanAttribute("readonly");
  77. isNullSelectionAllowed = uidl.getBooleanAttribute("nullselect");
  78. clear();
  79. for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {
  80. final UIDL childUidl = (UIDL) i.next();
  81. if ("actions".equals(childUidl.getTag())) {
  82. updateActionMap(childUidl);
  83. continue;
  84. }
  85. final TreeNode childTree = new TreeNode();
  86. this.add(childTree);
  87. childTree.updateFromUIDL(childUidl, client);
  88. }
  89. final String selectMode = uidl.getStringAttribute("selectmode");
  90. selectable = selectMode != null;
  91. isMultiselect = "multi".equals(selectMode);
  92. selectedIds = uidl.getStringArrayVariableAsSet("selected");
  93. }
  94. private void handleUpdate(UIDL uidl) {
  95. final TreeNode rootNode = (TreeNode) keyToNode.get(uidl
  96. .getStringAttribute("rootKey"));
  97. if (rootNode != null) {
  98. if (!rootNode.getState()) {
  99. // expanding node happened server side
  100. rootNode.setState(true, false);
  101. }
  102. rootNode.renderChildNodes(uidl.getChildIterator());
  103. }
  104. }
  105. public void setSelected(TreeNode treeNode, boolean selected) {
  106. if (selected) {
  107. if (!isMultiselect) {
  108. while (selectedIds.size() > 0) {
  109. final String id = (String) selectedIds.iterator().next();
  110. final TreeNode oldSelection = (TreeNode) keyToNode.get(id);
  111. oldSelection.setSelected(false);
  112. selectedIds.remove(id);
  113. }
  114. }
  115. treeNode.setSelected(true);
  116. selectedIds.add(treeNode.key);
  117. } else {
  118. if (!isNullSelectionAllowed) {
  119. if (!isMultiselect || selectedIds.size() == 1) {
  120. return;
  121. }
  122. }
  123. selectedIds.remove(treeNode.key);
  124. treeNode.setSelected(false);
  125. }
  126. client.updateVariable(paintableId, "selected", selectedIds.toArray(),
  127. immediate);
  128. }
  129. public boolean isSelected(TreeNode treeNode) {
  130. return selectedIds.contains(treeNode.key);
  131. }
  132. protected class TreeNode extends SimplePanel implements ActionOwner {
  133. public static final String CLASSNAME = "i-tree-node";
  134. String key;
  135. private String[] actionKeys = null;
  136. private boolean childrenLoaded;
  137. private Element nodeCaptionDiv;
  138. protected Element nodeCaptionSpan;
  139. private FlowPanel childNodeContainer;
  140. private boolean open;
  141. private Icon icon;
  142. public TreeNode() {
  143. constructDom();
  144. sinkEvents(Event.ONCLICK);
  145. }
  146. public void onBrowserEvent(Event event) {
  147. super.onBrowserEvent(event);
  148. if (disabled) {
  149. return;
  150. }
  151. final Element target = DOM.eventGetTarget(event);
  152. if (DOM.compare(getElement(), target)) {
  153. // state change
  154. toggleState();
  155. } else if (!readonly && DOM.compare(target, nodeCaptionSpan)) {
  156. // caption click = selection change
  157. toggleSelection();
  158. }
  159. }
  160. private void toggleSelection() {
  161. if (selectable) {
  162. ITree.this.setSelected(this, !isSelected());
  163. }
  164. }
  165. private void toggleState() {
  166. setState(!getState(), true);
  167. }
  168. protected void constructDom() {
  169. final Element root = DOM.createDiv();
  170. nodeCaptionDiv = DOM.createDiv();
  171. DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME
  172. + "-caption");
  173. nodeCaptionSpan = DOM.createSpan();
  174. DOM.appendChild(root, nodeCaptionDiv);
  175. DOM.appendChild(nodeCaptionDiv, nodeCaptionSpan);
  176. setElement(root);
  177. childNodeContainer = new FlowPanel();
  178. childNodeContainer.setStylePrimaryName(CLASSNAME + "-children");
  179. setWidget(childNodeContainer);
  180. }
  181. public void onDetach() {
  182. Util.removeContextMenuEvent(nodeCaptionSpan);
  183. super.onDetach();
  184. }
  185. public void onAttach() {
  186. attachContextMenuEvent(nodeCaptionSpan);
  187. super.onAttach();
  188. }
  189. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  190. setText(uidl.getStringAttribute("caption"));
  191. key = uidl.getStringAttribute("key");
  192. keyToNode.put(key, this);
  193. if (uidl.hasAttribute("al")) {
  194. actionKeys = uidl.getStringArrayAttribute("al");
  195. }
  196. if (uidl.getTag().equals("node")) {
  197. if (uidl.getChildCount() == 0) {
  198. childNodeContainer.setVisible(false);
  199. } else {
  200. renderChildNodes(uidl.getChildIterator());
  201. childrenLoaded = true;
  202. }
  203. } else {
  204. addStyleName(CLASSNAME + "-leaf");
  205. }
  206. addStyleName(CLASSNAME);
  207. if (uidl.getBooleanAttribute("expanded") && !getState()) {
  208. setState(true, false);
  209. }
  210. if (uidl.getBooleanAttribute("selected")) {
  211. setSelected(true);
  212. }
  213. if (uidl.hasAttribute("icon")) {
  214. if (icon == null) {
  215. icon = new Icon(client);
  216. DOM.insertBefore(nodeCaptionDiv, icon.getElement(),
  217. nodeCaptionSpan);
  218. }
  219. icon.setUri(uidl.getStringAttribute("icon"));
  220. } else {
  221. if (icon != null) {
  222. DOM.removeChild(nodeCaptionDiv, icon.getElement());
  223. icon = null;
  224. }
  225. }
  226. }
  227. private void setState(boolean state, boolean notifyServer) {
  228. if (open == state) {
  229. return;
  230. }
  231. if (state) {
  232. if (!childrenLoaded && notifyServer) {
  233. client.updateVariable(paintableId, "requestChildTree",
  234. true, false);
  235. }
  236. if (notifyServer) {
  237. client.updateVariable(paintableId, "expand",
  238. new String[] { key }, true);
  239. }
  240. addStyleName(CLASSNAME + "-expanded");
  241. childNodeContainer.setVisible(true);
  242. } else {
  243. removeStyleName(CLASSNAME + "-expanded");
  244. childNodeContainer.setVisible(false);
  245. if (notifyServer) {
  246. client.updateVariable(paintableId, "collapse",
  247. new String[] { key }, true);
  248. }
  249. }
  250. open = state;
  251. }
  252. private boolean getState() {
  253. return open;
  254. }
  255. private void setText(String text) {
  256. DOM.setInnerText(nodeCaptionSpan, text);
  257. }
  258. private void renderChildNodes(Iterator i) {
  259. childNodeContainer.clear();
  260. childNodeContainer.setVisible(true);
  261. while (i.hasNext()) {
  262. final UIDL childUidl = (UIDL) i.next();
  263. // actions are in bit weird place, don't mix them with children,
  264. // but current node's actions
  265. if ("actions".equals(childUidl.getTag())) {
  266. updateActionMap(childUidl);
  267. continue;
  268. }
  269. final TreeNode childTree = new TreeNode();
  270. childNodeContainer.add(childTree);
  271. childTree.updateFromUIDL(childUidl, client);
  272. }
  273. childrenLoaded = true;
  274. }
  275. public boolean isChildrenLoaded() {
  276. return childrenLoaded;
  277. }
  278. public Action[] getActions() {
  279. if (actionKeys == null) {
  280. return new Action[] {};
  281. }
  282. final Action[] actions = new Action[actionKeys.length];
  283. for (int i = 0; i < actions.length; i++) {
  284. final String actionKey = actionKeys[i];
  285. final TreeAction a = new TreeAction(this, String.valueOf(key),
  286. actionKey);
  287. a.setCaption(getActionCaption(actionKey));
  288. a.setIconUrl(getActionIcon(actionKey));
  289. actions[i] = a;
  290. }
  291. return actions;
  292. }
  293. public ApplicationConnection getClient() {
  294. return client;
  295. }
  296. public String getPaintableId() {
  297. return paintableId;
  298. }
  299. /**
  300. * Adds/removes IT Mill Toolkit spesific style name. This method ought
  301. * to be called only from Tree.
  302. *
  303. * @param selected
  304. */
  305. public void setSelected(boolean selected) {
  306. // add style name to caption dom structure only, not to subtree
  307. setStyleName(nodeCaptionDiv, "i-tree-node-selected", selected);
  308. }
  309. public boolean isSelected() {
  310. return ITree.this.isSelected(this);
  311. }
  312. public void showContextMenu(Event event) {
  313. if (!readonly && !disabled) {
  314. if (actionKeys != null) {
  315. int left = DOM.eventGetClientX(event);
  316. int top = DOM.eventGetClientY(event);
  317. top += Window.getScrollTop();
  318. left += Window.getScrollLeft();
  319. client.getContextMenu().showAt(this, left, top);
  320. }
  321. DOM.eventCancelBubble(event, true);
  322. }
  323. }
  324. private native void attachContextMenuEvent(Element el)
  325. /*-{
  326. var node = this;
  327. el.oncontextmenu = function(e) {
  328. if(!e)
  329. e = $wnd.event;
  330. node.@com.itmill.toolkit.terminal.gwt.client.ui.ITree.TreeNode::showContextMenu(Lcom/google/gwt/user/client/Event;)(e);
  331. return false;
  332. };
  333. }-*/;
  334. }
  335. }