您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

TreeGrid.java 23KB

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