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.

HierarchyRenderer.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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.dom.client.TableCellElement;
  21. import com.google.gwt.event.dom.client.ClickEvent;
  22. import com.google.gwt.event.dom.client.ClickHandler;
  23. import com.google.gwt.event.dom.client.HasClickHandlers;
  24. import com.google.gwt.event.shared.HandlerRegistration;
  25. import com.google.gwt.user.client.DOM;
  26. import com.google.gwt.user.client.ui.Composite;
  27. import com.google.gwt.user.client.ui.FlowPanel;
  28. import com.google.gwt.user.client.ui.HTML;
  29. import com.google.gwt.user.client.ui.Widget;
  30. import com.vaadin.client.WidgetUtil;
  31. import com.vaadin.client.ui.treegrid.TreeGridConnector;
  32. import com.vaadin.client.widget.escalator.FlyweightCell;
  33. import com.vaadin.client.widget.grid.RendererCellReference;
  34. import com.vaadin.client.widget.grid.RowReference;
  35. import com.vaadin.shared.data.HierarchicalDataCommunicatorConstants;
  36. import elemental.json.JsonObject;
  37. /**
  38. * A renderer for displaying hierarchical columns in TreeGrid.
  39. *
  40. * @author Vaadin Ltd
  41. * @since 8.1
  42. */
  43. public class HierarchyRenderer extends ClickableRenderer<Object, Widget> {
  44. /**
  45. * Wrapper for cell references. Used to get the correct inner element to
  46. * render.
  47. *
  48. * @author Vaadin Ltd
  49. * @since 8.1
  50. */
  51. private static class HierarchyRendererCellReferenceWrapper
  52. extends RendererCellReference {
  53. private Element element;
  54. public HierarchyRendererCellReferenceWrapper(RendererCellReference cell,
  55. Element element) {
  56. super(getRowReference(cell));
  57. set(getFlyweightCell(cell), cell.getColumnIndex(),
  58. cell.getColumn());
  59. this.element = element;
  60. }
  61. @Override
  62. public TableCellElement getElement() {
  63. return (TableCellElement) element;
  64. }
  65. private static native RowReference<Object> getRowReference(
  66. RendererCellReference cell) /*-{
  67. return cell.@com.vaadin.client.widget.grid.CellReference::getRowReference()();
  68. }-*/;
  69. private static native FlyweightCell getFlyweightCell(
  70. RendererCellReference cell) /*-{
  71. return cell.@com.vaadin.client.widget.grid.RendererCellReference::cell;
  72. }-*/;
  73. }
  74. private String nodeStyleName;
  75. private String expanderStyleName;
  76. private String cellContentStyleName;
  77. private static final String CLASS_COLLAPSED = "collapsed";
  78. private static final String CLASS_COLLAPSE_DISABLED = "collapse-disabled";
  79. private static final String CLASS_EXPANDED = "expanded";
  80. private static final String CLASS_DEPTH = "depth-";
  81. private Renderer innerRenderer;
  82. /**
  83. * Constructs a HierarchyRenderer with given collapse callback. Callback is
  84. * called when user clicks on the expander of a row. Callback is given the
  85. * row index and the target collapsed state.
  86. *
  87. * @param collapseCallback
  88. * the callback for collapsing nodes with row index
  89. * @param styleName
  90. * the style name of the widget this renderer is used in
  91. */
  92. public HierarchyRenderer(BiConsumer<Integer, Boolean> collapseCallback,
  93. String styleName) {
  94. addClickHandler(event -> {
  95. try {
  96. JsonObject row = (JsonObject) event.getRow();
  97. // Row needs to have hierarchy description
  98. if (!hasHierarchyData(row)) {
  99. return;
  100. }
  101. JsonObject hierarchyData = getHierarchyData(row);
  102. if ((!isCollapsed(hierarchyData)
  103. && !TreeGridConnector.isCollapseAllowed(hierarchyData))
  104. || isLeaf(hierarchyData)) {
  105. return;
  106. }
  107. collapseCallback.accept(event.getCell().getRowIndex(),
  108. !isCollapsed(hierarchyData));
  109. } finally {
  110. event.stopPropagation();
  111. event.preventDefault();
  112. }
  113. });
  114. setStyleNames(styleName);
  115. }
  116. /**
  117. * Set the style name prefix for the node, expander and cell-content
  118. * elements.
  119. *
  120. * @param styleName
  121. * the style name to set
  122. */
  123. public void setStyleNames(String styleName) {
  124. nodeStyleName = styleName + "-node";
  125. expanderStyleName = styleName + "-expander";
  126. cellContentStyleName = styleName + "-cell-content";
  127. }
  128. @Override
  129. public Widget createWidget() {
  130. return new HierarchyItem(nodeStyleName);
  131. }
  132. @Override
  133. public void render(RendererCellReference cell, Object data, Widget widget) {
  134. JsonObject row = (JsonObject) cell.getRow();
  135. int depth = 0;
  136. boolean leaf = false;
  137. boolean collapsed = false;
  138. boolean collapseAllowed = true;
  139. if (hasHierarchyData(row)) {
  140. JsonObject rowDescription = getHierarchyData(row);
  141. depth = getDepth(rowDescription);
  142. leaf = isLeaf(rowDescription);
  143. if (!leaf) {
  144. collapsed = isCollapsed(rowDescription);
  145. collapseAllowed = TreeGridConnector
  146. .isCollapseAllowed(rowDescription);
  147. }
  148. }
  149. HierarchyItem cellWidget = (HierarchyItem) widget;
  150. cellWidget.setDepth(depth);
  151. if (leaf) {
  152. cellWidget.setExpanderState(ExpanderState.LEAF);
  153. } else if (collapsed) {
  154. cellWidget.setExpanderState(ExpanderState.COLLAPSED);
  155. } else {
  156. cellWidget.setExpanderState(ExpanderState.EXPANDED);
  157. }
  158. cellWidget.setCollapseAllowed(collapseAllowed);
  159. // Render the contents of the inner renderer. For non widget
  160. // renderers
  161. // the cell reference needs to be wrapped so that its getElement
  162. // method
  163. // returns the correct element we want to render.
  164. if (innerRenderer instanceof WidgetRenderer) {
  165. ((WidgetRenderer) innerRenderer).render(cell, data,
  166. ((HierarchyItem) widget).content);
  167. } else {
  168. innerRenderer.render(
  169. new HierarchyRendererCellReferenceWrapper(cell,
  170. ((HierarchyItem) widget).content.getElement()),
  171. data);
  172. }
  173. }
  174. private int getDepth(JsonObject rowDescription) {
  175. return (int) rowDescription
  176. .getNumber(HierarchicalDataCommunicatorConstants.ROW_DEPTH);
  177. }
  178. private JsonObject getHierarchyData(JsonObject row) {
  179. return row.getObject(
  180. HierarchicalDataCommunicatorConstants.ROW_HIERARCHY_DESCRIPTION);
  181. }
  182. private boolean hasHierarchyData(JsonObject row) {
  183. return row.hasKey(
  184. HierarchicalDataCommunicatorConstants.ROW_HIERARCHY_DESCRIPTION);
  185. }
  186. private boolean isLeaf(JsonObject rowDescription) {
  187. boolean leaf;
  188. leaf = rowDescription
  189. .getBoolean(HierarchicalDataCommunicatorConstants.ROW_LEAF);
  190. return leaf;
  191. }
  192. private boolean isCollapsed(JsonObject rowDescription) {
  193. boolean collapsed;
  194. collapsed = rowDescription
  195. .getBoolean(HierarchicalDataCommunicatorConstants.ROW_COLLAPSED);
  196. return collapsed;
  197. }
  198. /**
  199. * Sets the renderer to be wrapped. This is the original renderer before
  200. * hierarchy is applied.
  201. *
  202. * @param innerRenderer
  203. * Renderer to be wrapped.
  204. */
  205. public void setInnerRenderer(Renderer innerRenderer) {
  206. this.innerRenderer = innerRenderer;
  207. }
  208. /**
  209. * Returns the wrapped renderer.
  210. *
  211. * @return Wrapped renderer.
  212. */
  213. public Renderer getInnerRenderer() {
  214. return innerRenderer;
  215. }
  216. /**
  217. * Decides whether the element was rendered by {@link HierarchyRenderer}
  218. */
  219. public static boolean isElementInHierarchyWidget(Element element) {
  220. Widget w = WidgetUtil.findWidget(element);
  221. while (w != null) {
  222. if (w instanceof HierarchyItem) {
  223. return true;
  224. }
  225. w = w.getParent();
  226. }
  227. return false;
  228. }
  229. private class HierarchyItem extends Composite {
  230. private FlowPanel panel;
  231. private Expander expander;
  232. private Widget content;
  233. private HierarchyItem(String className) {
  234. panel = new FlowPanel();
  235. panel.getElement().addClassName(className);
  236. expander = new Expander();
  237. expander.getElement().addClassName(expanderStyleName);
  238. if (innerRenderer instanceof WidgetRenderer) {
  239. content = ((WidgetRenderer) innerRenderer).createWidget();
  240. } else {
  241. // TODO: 20/09/16 create more general widget?
  242. content = GWT.create(HTML.class);
  243. }
  244. content.getElement().addClassName(cellContentStyleName);
  245. panel.add(expander);
  246. panel.add(content);
  247. expander.addClickHandler(HierarchyRenderer.this);
  248. initWidget(panel);
  249. }
  250. private void setDepth(int depth) {
  251. String classNameToBeReplaced = getFullClassName(CLASS_DEPTH,
  252. panel.getElement().getClassName());
  253. if (classNameToBeReplaced == null) {
  254. panel.getElement().addClassName(CLASS_DEPTH + depth);
  255. } else {
  256. panel.getElement().replaceClassName(classNameToBeReplaced,
  257. CLASS_DEPTH + depth);
  258. }
  259. }
  260. private String getFullClassName(String prefix, String classNameList) {
  261. int start = classNameList.indexOf(prefix);
  262. int end = start + prefix.length();
  263. if (start > -1) {
  264. while (end < classNameList.length()
  265. && classNameList.charAt(end) != ' ') {
  266. end++;
  267. }
  268. return classNameList.substring(start, end);
  269. }
  270. return null;
  271. }
  272. private void setExpanderState(ExpanderState state) {
  273. switch (state) {
  274. case EXPANDED:
  275. expander.getElement().removeClassName(CLASS_COLLAPSED);
  276. expander.getElement().addClassName(CLASS_EXPANDED);
  277. break;
  278. case COLLAPSED:
  279. expander.getElement().removeClassName(CLASS_EXPANDED);
  280. expander.getElement().addClassName(CLASS_COLLAPSED);
  281. break;
  282. case LEAF:
  283. default:
  284. expander.getElement().removeClassName(CLASS_COLLAPSED);
  285. expander.getElement().removeClassName(CLASS_EXPANDED);
  286. }
  287. }
  288. private void setCollapseAllowed(boolean collapseAllowed) {
  289. if (expander.getElement().hasClassName(CLASS_EXPANDED)
  290. && !collapseAllowed) {
  291. expander.getElement().addClassName(CLASS_COLLAPSE_DISABLED);
  292. } else {
  293. expander.getElement().removeClassName(CLASS_COLLAPSE_DISABLED);
  294. }
  295. }
  296. private class Expander extends Widget implements HasClickHandlers {
  297. private Expander() {
  298. Element span = DOM.createSpan();
  299. setElement(span);
  300. }
  301. @Override
  302. public HandlerRegistration addClickHandler(ClickHandler handler) {
  303. return addDomHandler(handler, ClickEvent.getType());
  304. }
  305. }
  306. }
  307. private enum ExpanderState {
  308. EXPANDED, COLLAPSED, LEAF;
  309. }
  310. }