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 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  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.io.Serializable;
  18. import java.lang.reflect.Method;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.List;
  22. import java.util.Objects;
  23. import java.util.Optional;
  24. import java.util.stream.Stream;
  25. import org.jsoup.nodes.Attributes;
  26. import org.jsoup.nodes.Element;
  27. import org.jsoup.select.Elements;
  28. import com.vaadin.data.HierarchyData;
  29. import com.vaadin.data.ValueProvider;
  30. import com.vaadin.data.provider.DataProvider;
  31. import com.vaadin.data.provider.HierarchicalDataCommunicator;
  32. import com.vaadin.data.provider.HierarchicalDataProvider;
  33. import com.vaadin.data.provider.HierarchicalQuery;
  34. import com.vaadin.data.provider.InMemoryHierarchicalDataProvider;
  35. import com.vaadin.server.SerializablePredicate;
  36. import com.vaadin.shared.Registration;
  37. import com.vaadin.shared.ui.treegrid.NodeCollapseRpc;
  38. import com.vaadin.shared.ui.treegrid.TreeGridState;
  39. import com.vaadin.ui.declarative.DesignAttributeHandler;
  40. import com.vaadin.ui.declarative.DesignContext;
  41. import com.vaadin.ui.declarative.DesignFormatter;
  42. import com.vaadin.ui.renderers.AbstractRenderer;
  43. import com.vaadin.ui.renderers.Renderer;
  44. import com.vaadin.util.ReflectTools;
  45. /**
  46. * A grid component for displaying hierarchical tabular data.
  47. *
  48. * @author Vaadin Ltd
  49. * @since 8.1
  50. *
  51. * @param <T>
  52. * the grid bean type
  53. */
  54. public class TreeGrid<T> extends Grid<T> {
  55. /**
  56. * Item expand event listener.
  57. *
  58. * @author Vaadin Ltd
  59. * @since 8.1
  60. * @param <T>
  61. * the expanded item's type
  62. */
  63. @FunctionalInterface
  64. public interface ExpandListener<T> extends Serializable {
  65. public static final Method EXPAND_METHOD = ReflectTools.findMethod(
  66. ExpandListener.class, "itemExpand", ExpandEvent.class);
  67. /**
  68. * Callback method for when an item has been expanded.
  69. *
  70. * @param event
  71. * the expand event
  72. */
  73. public void itemExpand(ExpandEvent<T> event);
  74. }
  75. /**
  76. * Item collapse event listener.
  77. *
  78. * @author Vaadin Ltd
  79. * @since 8.1
  80. * @param <T>
  81. * the collapsed item's type
  82. */
  83. @FunctionalInterface
  84. public interface CollapseListener<T> extends Serializable {
  85. public static final Method COLLAPSE_METHOD = ReflectTools.findMethod(
  86. CollapseListener.class, "itemCollapse", CollapseEvent.class);
  87. /**
  88. * Callback method for when an item has been collapsed.
  89. *
  90. * @param event
  91. * the collapse event
  92. */
  93. public void itemCollapse(CollapseEvent<T> event);
  94. }
  95. /**
  96. * An event that is fired when an item is expanded.
  97. *
  98. * @author Vaadin Ltd
  99. * @since 8.1
  100. * @param <T>
  101. * the expanded item's type
  102. */
  103. public static class ExpandEvent<T> extends Component.Event {
  104. private final T expandedItem;
  105. /**
  106. * Construct an expand event.
  107. *
  108. * @param source
  109. * the tree grid this event originated from
  110. * @param item
  111. * the item that was expanded
  112. */
  113. public ExpandEvent(TreeGrid<T> source, T expandedItem) {
  114. super(source);
  115. this.expandedItem = expandedItem;
  116. }
  117. /**
  118. * Get the expanded item that triggered this event.
  119. *
  120. * @return the expanded item
  121. */
  122. public T getExpandedItem() {
  123. return expandedItem;
  124. }
  125. }
  126. /**
  127. * An event that is fired when an item is collapsed. Note that expanded
  128. * subtrees of the collapsed item will not trigger collapse events.
  129. *
  130. * @author Vaadin Ltd
  131. * @since 8.1
  132. * @param <T>
  133. * collapsed item type
  134. */
  135. public static class CollapseEvent<T> extends Component.Event {
  136. private final T collapsedItem;
  137. /**
  138. * Construct a collapse event.
  139. *
  140. * @param source
  141. * the tree grid this event originated from
  142. * @param item
  143. * the item that was collapsed
  144. */
  145. public CollapseEvent(TreeGrid<T> source, T collapsedItem) {
  146. super(source);
  147. this.collapsedItem = collapsedItem;
  148. }
  149. /**
  150. * Get the collapsed item that triggered this event.
  151. *
  152. * @return the collapsed item
  153. */
  154. public T getCollapsedItem() {
  155. return collapsedItem;
  156. }
  157. }
  158. public TreeGrid() {
  159. super(new HierarchicalDataCommunicator<>());
  160. registerRpc(new NodeCollapseRpc() {
  161. @Override
  162. public void setNodeCollapsed(String rowKey, int rowIndex,
  163. boolean collapse) {
  164. if (collapse) {
  165. getDataCommunicator().doCollapse(rowKey, rowIndex);
  166. fireCollapseEvent(
  167. getDataCommunicator().getKeyMapper().get(rowKey));
  168. } else {
  169. getDataCommunicator().doExpand(rowKey, rowIndex);
  170. fireExpandEvent(
  171. getDataCommunicator().getKeyMapper().get(rowKey));
  172. }
  173. }
  174. });
  175. }
  176. /**
  177. * Adds an ExpandListener to this TreeGrid.
  178. *
  179. * @see ExpandEvent
  180. *
  181. * @param listener
  182. * the listener to add
  183. * @return a registration for the listener
  184. */
  185. public Registration addExpandListener(ExpandListener<T> listener) {
  186. return addListener(ExpandEvent.class, listener,
  187. ExpandListener.EXPAND_METHOD);
  188. }
  189. /**
  190. * Adds a CollapseListener to this TreeGrid.
  191. *
  192. * @see CollapseEvent
  193. *
  194. * @param listener
  195. * the listener to add
  196. * @return a registration for the listener
  197. */
  198. public Registration addCollapseListener(CollapseListener<T> listener) {
  199. return addListener(CollapseEvent.class, listener,
  200. CollapseListener.COLLAPSE_METHOD);
  201. }
  202. /**
  203. * Sets the data items of this component provided as a collection.
  204. * <p>
  205. * The provided items are wrapped into a
  206. * {@link InMemoryHierarchicalDataProvider} backed by a flat
  207. * {@link HierarchyData} structure. The data provider instance is used as a
  208. * parameter for the {@link #setDataProvider(DataProvider)} method. It means
  209. * that the items collection can be accessed later on via
  210. * {@link InMemoryHierarchicalDataProvider#getData()}:
  211. *
  212. * <pre>
  213. * <code>
  214. * TreeGrid<String> treeGrid = new TreeGrid<>();
  215. * treeGrid.setItems(Arrays.asList("a","b"));
  216. * ...
  217. *
  218. * HierarchyData<String> data = ((InMemoryHierarchicalDataProvider<String>)treeGrid.getDataProvider()).getData();
  219. * </code>
  220. * </pre>
  221. * <p>
  222. * The returned HierarchyData instance may be used as-is to add, remove or
  223. * modify items in the hierarchy. These modifications to the object are not
  224. * automatically reflected back to the TreeGrid. Items modified should be
  225. * refreshed with {@link HierarchicalDataProvider#refreshItem(Object)} and
  226. * when adding or removing items
  227. * {@link HierarchicalDataProvider#refreshAll()} should be called.
  228. *
  229. * @param items
  230. * the data items to display, not null
  231. */
  232. @Override
  233. public void setItems(Collection<T> items) {
  234. Objects.requireNonNull(items, "Given collection may not be null");
  235. setDataProvider(new InMemoryHierarchicalDataProvider<>(
  236. new HierarchyData<T>().addItems(null, items)));
  237. }
  238. /**
  239. * Sets the data items of this component provided as a stream.
  240. * <p>
  241. * The provided items are wrapped into a
  242. * {@link InMemoryHierarchicalDataProvider} backed by a flat
  243. * {@link HierarchyData} structure. The data provider instance is used as a
  244. * parameter for the {@link #setDataProvider(DataProvider)} method. It means
  245. * that the items collection can be accessed later on via
  246. * {@link InMemoryHierarchicalDataProvider#getData()}:
  247. *
  248. * <pre>
  249. * <code>
  250. * TreeGrid<String> treeGrid = new TreeGrid<>();
  251. * treeGrid.setItems(Stream.of("a","b"));
  252. * ...
  253. *
  254. * HierarchyData<String> data = ((InMemoryHierarchicalDataProvider<String>)treeGrid.getDataProvider()).getData();
  255. * </code>
  256. * </pre>
  257. * <p>
  258. * The returned HierarchyData instance may be used as-is to add, remove or
  259. * modify items in the hierarchy. These modifications to the object are not
  260. * automatically reflected back to the TreeGrid. Items modified should be
  261. * refreshed with {@link HierarchicalDataProvider#refreshItem(Object)} and
  262. * when adding or removing items
  263. * {@link HierarchicalDataProvider#refreshAll()} should be called.
  264. *
  265. * @param items
  266. * the data items to display, not null
  267. */
  268. @Override
  269. public void setItems(Stream<T> items) {
  270. Objects.requireNonNull(items, "Given stream may not be null");
  271. setDataProvider(new InMemoryHierarchicalDataProvider<>(
  272. new HierarchyData<T>().addItems(null, items)));
  273. }
  274. /**
  275. * Sets the data items of this listing.
  276. * <p>
  277. * The provided items are wrapped into a
  278. * {@link InMemoryHierarchicalDataProvider} backed by a flat
  279. * {@link HierarchyData} structure. The data provider instance is used as a
  280. * parameter for the {@link #setDataProvider(DataProvider)} method. It means
  281. * that the items collection can be accessed later on via
  282. * {@link InMemoryHierarchicalDataProvider#getData()}:
  283. *
  284. * <pre>
  285. * <code>
  286. * TreeGrid<String> treeGrid = new TreeGrid<>();
  287. * treeGrid.setItems("a","b");
  288. * ...
  289. *
  290. * HierarchyData<String> data = ((InMemoryHierarchicalDataProvider<String>)treeGrid.getDataProvider()).getData();
  291. * </code>
  292. * </pre>
  293. * <p>
  294. * The returned HierarchyData instance may be used as-is to add, remove or
  295. * modify items in the hierarchy. These modifications to the object are not
  296. * automatically reflected back to the TreeGrid. Items modified should be
  297. * refreshed with {@link HierarchicalDataProvider#refreshItem(Object)} and
  298. * when adding or removing items
  299. * {@link HierarchicalDataProvider#refreshAll()} should be called.
  300. *
  301. * @param items
  302. * the data items to display, not null
  303. */
  304. @Override
  305. public void setItems(@SuppressWarnings("unchecked") T... items) {
  306. Objects.requireNonNull(items, "Given items may not be null");
  307. setDataProvider(new InMemoryHierarchicalDataProvider<>(
  308. new HierarchyData<T>().addItems(null, items)));
  309. }
  310. @Override
  311. public void setDataProvider(DataProvider<T, ?> dataProvider) {
  312. if (!(dataProvider instanceof HierarchicalDataProvider)) {
  313. throw new IllegalArgumentException(
  314. "TreeGrid only accepts hierarchical data providers");
  315. }
  316. super.setDataProvider(dataProvider);
  317. }
  318. /**
  319. * Set the column that displays the hierarchy of this grid's data. By
  320. * default the hierarchy will be displayed in the first column.
  321. * <p>
  322. * Setting a hierarchy column by calling this method also sets the column to
  323. * be visible and not hidable.
  324. * <p>
  325. * <strong>Note:</strong> Changing the Renderer of the hierarchy column is
  326. * not supported.
  327. *
  328. * @see Column#setId(String)
  329. *
  330. * @param id
  331. * id of the column to use for displaying hierarchy
  332. */
  333. public void setHierarchyColumn(String id) {
  334. Objects.requireNonNull(id, "id may not be null");
  335. if (getColumn(id) == null) {
  336. throw new IllegalArgumentException("No column found for given id");
  337. }
  338. getColumn(id).setHidden(false);
  339. getColumn(id).setHidable(false);
  340. getState().hierarchyColumnId = getInternalIdForColumn(getColumn(id));
  341. }
  342. /**
  343. * Sets the item collapse allowed provider for this TreeGrid. The provider
  344. * should return {@code true} for any item that the user can collapse.
  345. * <p>
  346. * <strong>Note:</strong> This callback will be accessed often when sending
  347. * data to the client. The callback should not do any costly operations.
  348. * <p>
  349. * This method is a shortcut to method with the same name in
  350. * {@link HierarchicalDataCommunicator}.
  351. *
  352. * @param provider
  353. * the item collapse allowed provider, not {@code null}
  354. *
  355. * @see HierarchicalDataCommunicator#setItemCollapseAllowedProvider(SerializablePredicate)
  356. */
  357. public void setItemCollapseAllowedProvider(
  358. SerializablePredicate<T> provider) {
  359. getDataCommunicator().setItemCollapseAllowedProvider(provider);
  360. }
  361. @Override
  362. protected TreeGridState getState() {
  363. return (TreeGridState) super.getState();
  364. }
  365. @Override
  366. protected TreeGridState getState(boolean markAsDirty) {
  367. return (TreeGridState) super.getState(markAsDirty);
  368. }
  369. @Override
  370. public HierarchicalDataCommunicator<T> getDataCommunicator() {
  371. return (HierarchicalDataCommunicator<T>) super.getDataCommunicator();
  372. }
  373. @Override
  374. public HierarchicalDataProvider<T, ?> getDataProvider() {
  375. if (!(super.getDataProvider() instanceof HierarchicalDataProvider)) {
  376. return null;
  377. }
  378. return (HierarchicalDataProvider<T, ?>) super.getDataProvider();
  379. }
  380. @Override
  381. protected void doReadDesign(Element design, DesignContext context) {
  382. super.doReadDesign(design, context);
  383. Attributes attrs = design.attributes();
  384. if (attrs.hasKey("hierarchy-column")) {
  385. setHierarchyColumn(DesignAttributeHandler
  386. .readAttribute("hierarchy-column", attrs, String.class));
  387. }
  388. }
  389. @Override
  390. protected void readData(Element body,
  391. List<DeclarativeValueProvider<T>> providers) {
  392. getSelectionModel().deselectAll();
  393. List<T> selectedItems = new ArrayList<>();
  394. HierarchyData<T> data = new HierarchyData<T>();
  395. for (Element row : body.children()) {
  396. T item = deserializeDeclarativeRepresentation(row.attr("item"));
  397. T parent = null;
  398. if (row.hasAttr("parent")) {
  399. parent = deserializeDeclarativeRepresentation(
  400. row.attr("parent"));
  401. }
  402. data.addItem(parent, item);
  403. if (row.hasAttr("selected")) {
  404. selectedItems.add(item);
  405. }
  406. Elements cells = row.children();
  407. int i = 0;
  408. for (Element cell : cells) {
  409. providers.get(i).addValue(item, cell.html());
  410. i++;
  411. }
  412. }
  413. setDataProvider(new InMemoryHierarchicalDataProvider<>(data));
  414. selectedItems.forEach(getSelectionModel()::select);
  415. }
  416. @Override
  417. protected void doWriteDesign(Element design, DesignContext designContext) {
  418. super.doWriteDesign(design, designContext);
  419. if (getColumnByInternalId(getState(false).hierarchyColumnId) != null) {
  420. String hierarchyColumn = getColumnByInternalId(
  421. getState(false).hierarchyColumnId).getId();
  422. DesignAttributeHandler.writeAttribute("hierarchy-column",
  423. design.attributes(), hierarchyColumn, null, String.class,
  424. designContext);
  425. }
  426. }
  427. @Override
  428. protected void writeData(Element body, DesignContext designContext) {
  429. getDataProvider().fetch(new HierarchicalQuery<>(null, null))
  430. .forEach(item -> writeRow(body, item, null, designContext));
  431. }
  432. private void writeRow(Element container, T item, T parent,
  433. DesignContext context) {
  434. Element tableRow = container.appendElement("tr");
  435. tableRow.attr("item", serializeDeclarativeRepresentation(item));
  436. if (parent != null) {
  437. tableRow.attr("parent", serializeDeclarativeRepresentation(parent));
  438. }
  439. if (getSelectionModel().isSelected(item)) {
  440. tableRow.attr("selected", "");
  441. }
  442. for (Column<T, ?> column : getColumns()) {
  443. Object value = column.getValueProvider().apply(item);
  444. tableRow.appendElement("td")
  445. .append(Optional.ofNullable(value).map(Object::toString)
  446. .map(DesignFormatter::encodeForTextNode)
  447. .orElse(""));
  448. }
  449. getDataProvider().fetch(new HierarchicalQuery<>(null, item)).forEach(
  450. childItem -> writeRow(container, childItem, item, context));
  451. }
  452. @Override
  453. protected <V> Column<T, V> createColumn(ValueProvider<T, V> valueProvider,
  454. AbstractRenderer<? super T, ? super V> renderer) {
  455. return new Column<T, V>(valueProvider, renderer) {
  456. @Override
  457. public com.vaadin.ui.Grid.Column<T, V> setRenderer(
  458. Renderer<? super V> renderer) {
  459. // Disallow changing renderer for the hierarchy column
  460. if (getInternalIdForColumn(this).equals(
  461. TreeGrid.this.getState(false).hierarchyColumnId)) {
  462. throw new IllegalStateException(
  463. "Changing the renderer of the hierarchy column is not allowed.");
  464. }
  465. return super.setRenderer(renderer);
  466. }
  467. };
  468. }
  469. /**
  470. * Emit an expand event.
  471. *
  472. * @param item
  473. * the item that was expanded
  474. */
  475. private void fireExpandEvent(T item) {
  476. fireEvent(new ExpandEvent<>(this, item));
  477. }
  478. /**
  479. * Emit a collapse event.
  480. *
  481. * @param item
  482. * the item that was collapsed
  483. */
  484. private void fireCollapseEvent(T item) {
  485. fireEvent(new CollapseEvent<>(this, item));
  486. }
  487. }