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

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