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.

TreeGrid.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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.ui;
  17. import java.util.ArrayList;
  18. import java.util.Collection;
  19. import java.util.List;
  20. import java.util.Objects;
  21. import java.util.Optional;
  22. import java.util.stream.Stream;
  23. import org.jsoup.nodes.Attributes;
  24. import org.jsoup.nodes.Element;
  25. import org.jsoup.select.Elements;
  26. import com.vaadin.data.HierarchyData;
  27. import com.vaadin.data.ValueProvider;
  28. import com.vaadin.data.provider.DataProvider;
  29. import com.vaadin.data.provider.HierarchicalDataCommunicator;
  30. import com.vaadin.data.provider.HierarchicalDataProvider;
  31. import com.vaadin.data.provider.HierarchicalQuery;
  32. import com.vaadin.data.provider.InMemoryHierarchicalDataProvider;
  33. import com.vaadin.shared.ui.treegrid.NodeCollapseRpc;
  34. import com.vaadin.shared.ui.treegrid.TreeGridState;
  35. import com.vaadin.ui.declarative.DesignAttributeHandler;
  36. import com.vaadin.ui.declarative.DesignContext;
  37. import com.vaadin.ui.declarative.DesignFormatter;
  38. import com.vaadin.ui.renderers.AbstractRenderer;
  39. import com.vaadin.ui.renderers.Renderer;
  40. /**
  41. * A grid component for displaying hierarchical tabular data.
  42. *
  43. * @author Vaadin Ltd
  44. * @since 8.1
  45. *
  46. * @param <T>
  47. * the grid bean type
  48. */
  49. public class TreeGrid<T> extends Grid<T> {
  50. public TreeGrid() {
  51. super(new HierarchicalDataCommunicator<>());
  52. registerRpc(new NodeCollapseRpc() {
  53. @Override
  54. public void setNodeCollapsed(String rowKey, int rowIndex,
  55. boolean collapse) {
  56. if (collapse) {
  57. getDataCommunicator().doCollapse(rowKey, rowIndex);
  58. } else {
  59. getDataCommunicator().doExpand(rowKey, rowIndex);
  60. }
  61. }
  62. });
  63. }
  64. /**
  65. * Sets the data items of this component provided as a collection.
  66. * <p>
  67. * The provided items are wrapped into a
  68. * {@link InMemoryHierarchicalDataProvider} backed by a flat
  69. * {@link HierarchyData} structure. The data provider instance is used as a
  70. * parameter for the {@link #setDataProvider(DataProvider)} method. It means
  71. * that the items collection can be accessed later on via
  72. * {@link InMemoryHierarchicalDataProvider#getData()}:
  73. *
  74. * <pre>
  75. * <code>
  76. * TreeGrid<String> treeGrid = new TreeGrid<>();
  77. * treeGrid.setItems(Arrays.asList("a","b"));
  78. * ...
  79. *
  80. * HierarchyData<String> data = ((InMemoryHierarchicalDataProvider<String>)treeGrid.getDataProvider()).getData();
  81. * </code>
  82. * </pre>
  83. * <p>
  84. * The returned HierarchyData instance may be used as-is to add, remove or
  85. * modify items in the hierarchy. These modifications to the object are not
  86. * automatically reflected back to the TreeGrid. Items modified should be
  87. * refreshed with {@link HierarchicalDataProvider#refreshItem(Object)} and
  88. * when adding or removing items
  89. * {@link HierarchicalDataProvider#refreshAll()} should be called.
  90. *
  91. * @param items
  92. * the data items to display, not null
  93. */
  94. @Override
  95. public void setItems(Collection<T> items) {
  96. Objects.requireNonNull(items, "Given collection may not be null");
  97. setDataProvider(new InMemoryHierarchicalDataProvider<>(
  98. new HierarchyData<T>().addItems(null, items)));
  99. }
  100. /**
  101. * Sets the data items of this component provided as a stream.
  102. * <p>
  103. * The provided items are wrapped into a
  104. * {@link InMemoryHierarchicalDataProvider} backed by a flat
  105. * {@link HierarchyData} structure. The data provider instance is used as a
  106. * parameter for the {@link #setDataProvider(DataProvider)} method. It means
  107. * that the items collection can be accessed later on via
  108. * {@link InMemoryHierarchicalDataProvider#getData()}:
  109. *
  110. * <pre>
  111. * <code>
  112. * TreeGrid<String> treeGrid = new TreeGrid<>();
  113. * treeGrid.setItems(Stream.of("a","b"));
  114. * ...
  115. *
  116. * HierarchyData<String> data = ((InMemoryHierarchicalDataProvider<String>)treeGrid.getDataProvider()).getData();
  117. * </code>
  118. * </pre>
  119. * <p>
  120. * The returned HierarchyData instance may be used as-is to add, remove or
  121. * modify items in the hierarchy. These modifications to the object are not
  122. * automatically reflected back to the TreeGrid. Items modified should be
  123. * refreshed with {@link HierarchicalDataProvider#refreshItem(Object)} and
  124. * when adding or removing items
  125. * {@link HierarchicalDataProvider#refreshAll()} should be called.
  126. *
  127. * @param items
  128. * the data items to display, not null
  129. */
  130. @Override
  131. public void setItems(Stream<T> items) {
  132. Objects.requireNonNull(items, "Given stream may not be null");
  133. setDataProvider(new InMemoryHierarchicalDataProvider<>(
  134. new HierarchyData<T>().addItems(null, items)));
  135. }
  136. /**
  137. * Sets the data items of this listing.
  138. * <p>
  139. * The provided items are wrapped into a
  140. * {@link InMemoryHierarchicalDataProvider} backed by a flat
  141. * {@link HierarchyData} structure. The data provider instance is used as a
  142. * parameter for the {@link #setDataProvider(DataProvider)} method. It means
  143. * that the items collection can be accessed later on via
  144. * {@link InMemoryHierarchicalDataProvider#getData()}:
  145. *
  146. * <pre>
  147. * <code>
  148. * TreeGrid<String> treeGrid = new TreeGrid<>();
  149. * treeGrid.setItems("a","b");
  150. * ...
  151. *
  152. * HierarchyData<String> data = ((InMemoryHierarchicalDataProvider<String>)treeGrid.getDataProvider()).getData();
  153. * </code>
  154. * </pre>
  155. * <p>
  156. * The returned HierarchyData instance may be used as-is to add, remove or
  157. * modify items in the hierarchy. These modifications to the object are not
  158. * automatically reflected back to the TreeGrid. Items modified should be
  159. * refreshed with {@link HierarchicalDataProvider#refreshItem(Object)} and
  160. * when adding or removing items
  161. * {@link HierarchicalDataProvider#refreshAll()} should be called.
  162. *
  163. * @param items
  164. * the data items to display, not null
  165. */
  166. @Override
  167. public void setItems(@SuppressWarnings("unchecked") T... items) {
  168. Objects.requireNonNull(items, "Given items may not be null");
  169. setDataProvider(new InMemoryHierarchicalDataProvider<>(
  170. new HierarchyData<T>().addItems(null, items)));
  171. }
  172. @Override
  173. public void setDataProvider(DataProvider<T, ?> dataProvider) {
  174. if (!(dataProvider instanceof HierarchicalDataProvider)) {
  175. throw new IllegalArgumentException(
  176. "TreeGrid only accepts hierarchical data providers");
  177. }
  178. super.setDataProvider(dataProvider);
  179. }
  180. /**
  181. * Set the column that displays the hierarchy of this grid's data. By
  182. * default the hierarchy will be displayed in the first column.
  183. * <p>
  184. * Setting a hierarchy column by calling this method also sets the column to
  185. * be visible and not hidable.
  186. * <p>
  187. * <strong>Note:</strong> Changing the Renderer of the hierarchy column is
  188. * not supported.
  189. *
  190. * @see Column#setId(String)
  191. *
  192. * @param id
  193. * id of the column to use for displaying hierarchy
  194. */
  195. public void setHierarchyColumn(String id) {
  196. Objects.requireNonNull(id, "id may not be null");
  197. if (getColumn(id) == null) {
  198. throw new IllegalArgumentException("No column found for given id");
  199. }
  200. getColumn(id).setHidden(false);
  201. getColumn(id).setHidable(false);
  202. getState().hierarchyColumnId = getInternalIdForColumn(getColumn(id));
  203. }
  204. @Override
  205. protected TreeGridState getState() {
  206. return (TreeGridState) super.getState();
  207. }
  208. @Override
  209. protected TreeGridState getState(boolean markAsDirty) {
  210. return (TreeGridState) super.getState(markAsDirty);
  211. }
  212. @Override
  213. public HierarchicalDataCommunicator<T> getDataCommunicator() {
  214. return (HierarchicalDataCommunicator<T>) super.getDataCommunicator();
  215. }
  216. @Override
  217. public HierarchicalDataProvider<T, ?> getDataProvider() {
  218. if (!(super.getDataProvider() instanceof HierarchicalDataProvider)) {
  219. return null;
  220. }
  221. return (HierarchicalDataProvider<T, ?>) super.getDataProvider();
  222. }
  223. @Override
  224. protected void doReadDesign(Element design, DesignContext context) {
  225. super.doReadDesign(design, context);
  226. Attributes attrs = design.attributes();
  227. if (attrs.hasKey("hierarchy-column")) {
  228. setHierarchyColumn(DesignAttributeHandler
  229. .readAttribute("hierarchy-column", attrs, String.class));
  230. }
  231. }
  232. @Override
  233. protected void readData(Element body,
  234. List<DeclarativeValueProvider<T>> providers) {
  235. getSelectionModel().deselectAll();
  236. List<T> selectedItems = new ArrayList<>();
  237. HierarchyData<T> data = new HierarchyData<T>();
  238. for (Element row : body.children()) {
  239. T item = deserializeDeclarativeRepresentation(row.attr("item"));
  240. T parent = null;
  241. if (row.hasAttr("parent")) {
  242. parent = deserializeDeclarativeRepresentation(
  243. row.attr("parent"));
  244. }
  245. data.addItem(parent, item);
  246. if (row.hasAttr("selected")) {
  247. selectedItems.add(item);
  248. }
  249. Elements cells = row.children();
  250. int i = 0;
  251. for (Element cell : cells) {
  252. providers.get(i).addValue(item, cell.html());
  253. i++;
  254. }
  255. }
  256. setDataProvider(new InMemoryHierarchicalDataProvider<>(data));
  257. selectedItems.forEach(getSelectionModel()::select);
  258. }
  259. @Override
  260. protected void doWriteDesign(Element design, DesignContext designContext) {
  261. super.doWriteDesign(design, designContext);
  262. if (getColumnByInternalId(getState(false).hierarchyColumnId) != null) {
  263. String hierarchyColumn = getColumnByInternalId(
  264. getState(false).hierarchyColumnId).getId();
  265. DesignAttributeHandler.writeAttribute("hierarchy-column",
  266. design.attributes(), hierarchyColumn, null, String.class,
  267. designContext);
  268. }
  269. }
  270. @Override
  271. protected void writeData(Element body, DesignContext designContext) {
  272. getDataProvider().fetch(new HierarchicalQuery<>(null, null))
  273. .forEach(item -> writeRow(body, item, null, designContext));
  274. }
  275. private void writeRow(Element container, T item, T parent,
  276. DesignContext context) {
  277. Element tableRow = container.appendElement("tr");
  278. tableRow.attr("item", serializeDeclarativeRepresentation(item));
  279. if (parent != null) {
  280. tableRow.attr("parent", serializeDeclarativeRepresentation(parent));
  281. }
  282. if (getSelectionModel().isSelected(item)) {
  283. tableRow.attr("selected", "");
  284. }
  285. for (Column<T, ?> column : getColumns()) {
  286. Object value = column.getValueProvider().apply(item);
  287. tableRow.appendElement("td")
  288. .append(Optional.ofNullable(value).map(Object::toString)
  289. .map(DesignFormatter::encodeForTextNode)
  290. .orElse(""));
  291. }
  292. getDataProvider().fetch(new HierarchicalQuery<>(null, item))
  293. .forEach(childItem -> writeRow(container, childItem, item,
  294. context));
  295. }
  296. @Override
  297. protected <V> Column<T, V> createColumn(ValueProvider<T, V> valueProvider,
  298. AbstractRenderer<? super T, ? super V> renderer) {
  299. return new Column<T, V>(valueProvider, renderer) {
  300. @Override
  301. public com.vaadin.ui.Grid.Column<T, V> setRenderer(
  302. Renderer<? super V> renderer) {
  303. // Disallow changing renderer for the hierarchy column
  304. if (getInternalIdForColumn(this).equals(
  305. TreeGrid.this.getState(false).hierarchyColumnId)) {
  306. throw new IllegalStateException(
  307. "Changing the renderer of the hierarchy column is not allowed.");
  308. }
  309. return super.setRenderer(renderer);
  310. }
  311. };
  312. }
  313. }