Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

HierarchyRenderer.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.renderers;
  17. import java.util.function.BiConsumer;
  18. import com.google.gwt.core.client.GWT;
  19. import com.google.gwt.dom.client.Element;
  20. import com.google.gwt.event.dom.client.ClickEvent;
  21. import com.google.gwt.event.dom.client.ClickHandler;
  22. import com.google.gwt.event.dom.client.HasClickHandlers;
  23. import com.google.gwt.event.shared.HandlerRegistration;
  24. import com.google.gwt.user.client.DOM;
  25. import com.google.gwt.user.client.ui.Composite;
  26. import com.google.gwt.user.client.ui.FlowPanel;
  27. import com.google.gwt.user.client.ui.HTML;
  28. import com.google.gwt.user.client.ui.Widget;
  29. import com.vaadin.client.WidgetUtil;
  30. import com.vaadin.client.widget.grid.RendererCellReference;
  31. import com.vaadin.client.widget.treegrid.HierarchyRendererCellReferenceWrapper;
  32. import com.vaadin.shared.ui.treegrid.TreeGridCommunicationConstants;
  33. import elemental.json.JsonObject;
  34. /**
  35. * A renderer for displaying hierarchical columns in TreeGrid.
  36. *
  37. * @author Vaadin Ltd
  38. * @since 8.1
  39. */
  40. public class HierarchyRenderer extends ClickableRenderer<Object, Widget> {
  41. private static final String CLASS_TREE_GRID_NODE = "v-tree-grid-node";
  42. private static final String CLASS_TREE_GRID_EXPANDER = "v-tree-grid-expander";
  43. private static final String CLASS_TREE_GRID_CELL_CONTENT = "v-tree-grid-cell-content";
  44. private static final String CLASS_COLLAPSED = "collapsed";
  45. private static final String CLASS_COLLAPSE_DISABLED = "collapse-disabled";
  46. private static final String CLASS_EXPANDED = "expanded";
  47. private static final String CLASS_DEPTH = "depth-";
  48. private Renderer innerRenderer;
  49. /**
  50. * Constructs a HierarchyRenderer with given collapse callback. Callback is
  51. * called when user clicks on the expander of a row. Callback is given the
  52. * row index and the target collapsed state.
  53. *
  54. * @param collapseCallback
  55. * the callback for collapsing nodes with row index
  56. */
  57. public HierarchyRenderer(BiConsumer<Integer, Boolean> collapseCallback) {
  58. addClickHandler(event -> {
  59. try {
  60. JsonObject row = (JsonObject) event.getRow();
  61. // Row needs to have hierarchy description
  62. if (!hasHierarchyData(row)) {
  63. return;
  64. }
  65. JsonObject hierarchyData = getHierarchyData(row);
  66. if ((!isCollapsed(hierarchyData)
  67. && !isCollapseAllowed(hierarchyData))
  68. || isLeaf(hierarchyData)) {
  69. return;
  70. }
  71. collapseCallback.accept(event.getCell().getRowIndex(),
  72. !isCollapsed(hierarchyData));
  73. } finally {
  74. event.stopPropagation();
  75. event.preventDefault();
  76. }
  77. });
  78. }
  79. @Override
  80. public Widget createWidget() {
  81. return new HierarchyItem(CLASS_TREE_GRID_NODE);
  82. }
  83. @Override
  84. public void render(RendererCellReference cell, Object data, Widget widget) {
  85. JsonObject row = (JsonObject) cell.getRow();
  86. int depth = 0;
  87. boolean leaf = false;
  88. boolean collapsed = false;
  89. boolean collapseAllowed = true;
  90. if (hasHierarchyData(row)) {
  91. JsonObject rowDescription = getHierarchyData(row);
  92. depth = getDepth(rowDescription);
  93. leaf = isLeaf(rowDescription);
  94. if (!leaf) {
  95. collapsed = isCollapsed(rowDescription);
  96. collapseAllowed = isCollapseAllowed(rowDescription);
  97. }
  98. }
  99. HierarchyItem cellWidget = (HierarchyItem) widget;
  100. cellWidget.setDepth(depth);
  101. if (leaf) {
  102. cellWidget.setExpanderState(ExpanderState.LEAF);
  103. } else if (collapsed) {
  104. cellWidget.setExpanderState(ExpanderState.COLLAPSED);
  105. } else {
  106. cellWidget.setExpanderState(ExpanderState.EXPANDED);
  107. }
  108. cellWidget.setCollapseAllowed(collapseAllowed);
  109. // Render the contents of the inner renderer. For non widget
  110. // renderers
  111. // the cell reference needs to be wrapped so that its getElement
  112. // method
  113. // returns the correct element we want to render.
  114. if (innerRenderer instanceof WidgetRenderer) {
  115. ((WidgetRenderer) innerRenderer).render(cell, data,
  116. ((HierarchyItem) widget).content);
  117. } else {
  118. innerRenderer.render(
  119. new HierarchyRendererCellReferenceWrapper(cell,
  120. ((HierarchyItem) widget).content.getElement()),
  121. data);
  122. }
  123. }
  124. private int getDepth(JsonObject rowDescription) {
  125. return (int) rowDescription
  126. .getNumber(TreeGridCommunicationConstants.ROW_DEPTH);
  127. }
  128. private JsonObject getHierarchyData(JsonObject row) {
  129. return row.getObject(
  130. TreeGridCommunicationConstants.ROW_HIERARCHY_DESCRIPTION);
  131. }
  132. private boolean hasHierarchyData(JsonObject row) {
  133. return row.hasKey(
  134. TreeGridCommunicationConstants.ROW_HIERARCHY_DESCRIPTION);
  135. }
  136. private boolean isLeaf(JsonObject rowDescription) {
  137. boolean leaf;
  138. leaf = rowDescription
  139. .getBoolean(TreeGridCommunicationConstants.ROW_LEAF);
  140. return leaf;
  141. }
  142. private boolean isCollapseAllowed(JsonObject row) {
  143. return row.getBoolean(
  144. TreeGridCommunicationConstants.ROW_COLLAPSE_ALLOWED);
  145. }
  146. private boolean isCollapsed(JsonObject rowDescription) {
  147. boolean collapsed;
  148. collapsed = rowDescription
  149. .getBoolean(TreeGridCommunicationConstants.ROW_COLLAPSED);
  150. return collapsed;
  151. }
  152. /**
  153. * Sets the renderer to be wrapped. This is the original renderer before
  154. * hierarchy is applied.
  155. *
  156. * @param innerRenderer
  157. * Renderer to be wrapped.
  158. */
  159. public void setInnerRenderer(Renderer innerRenderer) {
  160. this.innerRenderer = innerRenderer;
  161. }
  162. /**
  163. * Returns the wrapped renderer.
  164. *
  165. * @return Wrapped renderer.
  166. */
  167. public Renderer getInnerRenderer() {
  168. return this.innerRenderer;
  169. }
  170. /**
  171. * Decides whether the element was rendered by {@link HierarchyRenderer}
  172. */
  173. public static boolean isElementInHierarchyWidget(Element element) {
  174. Widget w = WidgetUtil.findWidget(element, null);
  175. while (w != null) {
  176. if (w instanceof HierarchyItem) {
  177. return true;
  178. }
  179. w = w.getParent();
  180. }
  181. return false;
  182. }
  183. private class HierarchyItem extends Composite {
  184. private FlowPanel panel;
  185. private Expander expander;
  186. private Widget content;
  187. private HierarchyItem(String className) {
  188. panel = new FlowPanel();
  189. panel.getElement().addClassName(className);
  190. expander = new Expander();
  191. expander.getElement().addClassName(CLASS_TREE_GRID_EXPANDER);
  192. if (innerRenderer instanceof WidgetRenderer) {
  193. content = ((WidgetRenderer) innerRenderer).createWidget();
  194. } else {
  195. // TODO: 20/09/16 create more general widget?
  196. content = GWT.create(HTML.class);
  197. }
  198. content.getElement().addClassName(CLASS_TREE_GRID_CELL_CONTENT);
  199. panel.add(expander);
  200. panel.add(content);
  201. expander.addClickHandler(HierarchyRenderer.this);
  202. initWidget(panel);
  203. }
  204. private void setDepth(int depth) {
  205. String classNameToBeReplaced = getFullClassName(CLASS_DEPTH,
  206. panel.getElement().getClassName());
  207. if (classNameToBeReplaced == null) {
  208. panel.getElement().addClassName(CLASS_DEPTH + depth);
  209. } else {
  210. panel.getElement().replaceClassName(classNameToBeReplaced,
  211. CLASS_DEPTH + depth);
  212. }
  213. }
  214. private String getFullClassName(String prefix, String classNameList) {
  215. int start = classNameList.indexOf(prefix);
  216. int end = start + prefix.length();
  217. if (start > -1) {
  218. while (end < classNameList.length()
  219. && classNameList.charAt(end) != ' ') {
  220. end++;
  221. }
  222. return classNameList.substring(start, end);
  223. }
  224. return null;
  225. }
  226. private void setExpanderState(ExpanderState state) {
  227. switch (state) {
  228. case EXPANDED:
  229. expander.getElement().removeClassName(CLASS_COLLAPSED);
  230. expander.getElement().addClassName(CLASS_EXPANDED);
  231. break;
  232. case COLLAPSED:
  233. expander.getElement().removeClassName(CLASS_EXPANDED);
  234. expander.getElement().addClassName(CLASS_COLLAPSED);
  235. break;
  236. case LEAF:
  237. default:
  238. expander.getElement().removeClassName(CLASS_COLLAPSED);
  239. expander.getElement().removeClassName(CLASS_EXPANDED);
  240. }
  241. }
  242. private void setCollapseAllowed(boolean collapseAllowed) {
  243. if (expander.getElement().hasClassName(CLASS_EXPANDED)
  244. && !collapseAllowed) {
  245. expander.getElement().addClassName(CLASS_COLLAPSE_DISABLED);
  246. } else {
  247. expander.getElement().removeClassName(CLASS_COLLAPSE_DISABLED);
  248. }
  249. }
  250. private class Expander extends Widget implements HasClickHandlers {
  251. private Expander() {
  252. Element span = DOM.createSpan();
  253. setElement(span);
  254. }
  255. @Override
  256. public HandlerRegistration addClickHandler(ClickHandler handler) {
  257. return addDomHandler(handler, ClickEvent.getType());
  258. }
  259. }
  260. }
  261. enum ExpanderState {
  262. EXPANDED, COLLAPSED, LEAF;
  263. }
  264. }