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.

TreeTable.java 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  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.v7.ui;
  17. import java.io.Serializable;
  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashSet;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.Set;
  25. import java.util.Stack;
  26. import java.util.logging.Level;
  27. import java.util.logging.Logger;
  28. import org.jsoup.nodes.Element;
  29. import com.vaadin.server.PaintException;
  30. import com.vaadin.server.PaintTarget;
  31. import com.vaadin.server.Resource;
  32. import com.vaadin.ui.declarative.DesignAttributeHandler;
  33. import com.vaadin.ui.declarative.DesignContext;
  34. import com.vaadin.ui.declarative.DesignException;
  35. import com.vaadin.v7.data.Collapsible;
  36. import com.vaadin.v7.data.Container;
  37. import com.vaadin.v7.data.Container.Hierarchical;
  38. import com.vaadin.v7.data.util.ContainerHierarchicalWrapper;
  39. import com.vaadin.v7.data.util.HierarchicalContainer;
  40. import com.vaadin.v7.data.util.HierarchicalContainerOrderedWrapper;
  41. import com.vaadin.v7.shared.ui.treetable.TreeTableConstants;
  42. import com.vaadin.v7.shared.ui.treetable.TreeTableState;
  43. import com.vaadin.v7.ui.Tree.CollapseEvent;
  44. import com.vaadin.v7.ui.Tree.CollapseListener;
  45. import com.vaadin.v7.ui.Tree.ExpandEvent;
  46. import com.vaadin.v7.ui.Tree.ExpandListener;
  47. /**
  48. * TreeTable extends the {@link Table} component so that it can also visualize a
  49. * hierarchy of its Items in a similar manner that {@link Tree} does. The tree
  50. * hierarchy is always displayed in the first actual column of the TreeTable.
  51. * <p>
  52. * The TreeTable supports the usual {@link Table} features like lazy loading, so
  53. * it should be no problem to display lots of items at once. Only required rows
  54. * and some cache rows are sent to the client.
  55. * <p>
  56. * TreeTable supports standard {@link Hierarchical} container interfaces, but
  57. * also a more fine tuned version - {@link Collapsible}. A container
  58. * implementing the {@link Collapsible} interface stores the collapsed/expanded
  59. * state internally and can this way scale better on the server side than with
  60. * standard Hierarchical implementations. Developer must however note that
  61. * {@link Collapsible} containers can not be shared among several users as they
  62. * share UI state in the container.
  63. *
  64. * @deprecated See {@code TreeGrid}.
  65. */
  66. @SuppressWarnings({ "serial" })
  67. @Deprecated
  68. public class TreeTable extends Table implements Hierarchical {
  69. private interface ContainerStrategy extends Serializable {
  70. public int size();
  71. public boolean isNodeOpen(Object itemId);
  72. public int getDepth(Object itemId);
  73. public void toggleChildVisibility(Object itemId);
  74. public Object getIdByIndex(int index);
  75. public int indexOfId(Object id);
  76. public Object nextItemId(Object itemId);
  77. public Object lastItemId();
  78. public Object prevItemId(Object itemId);
  79. public boolean isLastId(Object itemId);
  80. public Collection<?> getItemIds();
  81. public void containerItemSetChange(ItemSetChangeEvent event);
  82. }
  83. private abstract class AbstractStrategy implements ContainerStrategy {
  84. /**
  85. * Consider adding getDepth to {@link Collapsible}, might help
  86. * scalability with some container implementations.
  87. */
  88. @Override
  89. public int getDepth(Object itemId) {
  90. int depth = 0;
  91. Hierarchical hierarchicalContainer = getContainerDataSource();
  92. while (!hierarchicalContainer.isRoot(itemId)) {
  93. depth++;
  94. itemId = hierarchicalContainer.getParent(itemId);
  95. }
  96. return depth;
  97. }
  98. @Override
  99. public void containerItemSetChange(ItemSetChangeEvent event) {
  100. }
  101. }
  102. /**
  103. * This strategy is used if current container implements {@link Collapsible}
  104. * .
  105. *
  106. * open-collapsed logic diverted to container, otherwise use default
  107. * implementations.
  108. */
  109. private class CollapsibleStrategy extends AbstractStrategy {
  110. private Collapsible c() {
  111. return (Collapsible) getContainerDataSource();
  112. }
  113. @Override
  114. public void toggleChildVisibility(Object itemId) {
  115. c().setCollapsed(itemId, !c().isCollapsed(itemId));
  116. }
  117. @Override
  118. public boolean isNodeOpen(Object itemId) {
  119. return !c().isCollapsed(itemId);
  120. }
  121. @Override
  122. public int size() {
  123. return TreeTable.super.size();
  124. }
  125. @Override
  126. public Object getIdByIndex(int index) {
  127. return TreeTable.super.getIdByIndex(index);
  128. }
  129. @Override
  130. public int indexOfId(Object id) {
  131. return TreeTable.super.indexOfId(id);
  132. }
  133. @Override
  134. public boolean isLastId(Object itemId) {
  135. // using the default impl
  136. return TreeTable.super.isLastId(itemId);
  137. }
  138. @Override
  139. public Object lastItemId() {
  140. // using the default impl
  141. return TreeTable.super.lastItemId();
  142. }
  143. @Override
  144. public Object nextItemId(Object itemId) {
  145. return TreeTable.super.nextItemId(itemId);
  146. }
  147. @Override
  148. public Object prevItemId(Object itemId) {
  149. return TreeTable.super.prevItemId(itemId);
  150. }
  151. @Override
  152. public Collection<?> getItemIds() {
  153. return TreeTable.super.getItemIds();
  154. }
  155. }
  156. /**
  157. * Strategy for Hierarchical but not Collapsible container like
  158. * {@link HierarchicalContainer}.
  159. *
  160. * Store collapsed/open states internally, fool Table to use preorder when
  161. * accessing items from container via Ordered/Indexed methods.
  162. */
  163. private class HierarchicalStrategy extends AbstractStrategy {
  164. private final HashSet<Object> openItems = new HashSet<Object>();
  165. @Override
  166. public boolean isNodeOpen(Object itemId) {
  167. return openItems.contains(itemId);
  168. }
  169. @Override
  170. public int size() {
  171. return getPreOrder().size();
  172. }
  173. @Override
  174. public Collection<Object> getItemIds() {
  175. return Collections.unmodifiableCollection(getPreOrder());
  176. }
  177. @Override
  178. public boolean isLastId(Object itemId) {
  179. if (itemId == null) {
  180. return false;
  181. }
  182. return itemId.equals(lastItemId());
  183. }
  184. @Override
  185. public Object lastItemId() {
  186. if (!getPreOrder().isEmpty()) {
  187. return getPreOrder().get(getPreOrder().size() - 1);
  188. } else {
  189. return null;
  190. }
  191. }
  192. @Override
  193. public Object nextItemId(Object itemId) {
  194. int indexOf = getPreOrder().indexOf(itemId);
  195. if (indexOf == -1) {
  196. return null;
  197. }
  198. indexOf++;
  199. if (indexOf == getPreOrder().size()) {
  200. return null;
  201. } else {
  202. return getPreOrder().get(indexOf);
  203. }
  204. }
  205. @Override
  206. public Object prevItemId(Object itemId) {
  207. int indexOf = getPreOrder().indexOf(itemId);
  208. indexOf--;
  209. if (indexOf < 0) {
  210. return null;
  211. } else {
  212. return getPreOrder().get(indexOf);
  213. }
  214. }
  215. @Override
  216. public void toggleChildVisibility(Object itemId) {
  217. boolean removed = openItems.remove(itemId);
  218. if (!removed) {
  219. openItems.add(itemId);
  220. getLogger().log(Level.FINEST, "Item {0} is now expanded",
  221. itemId);
  222. } else {
  223. getLogger().log(Level.FINEST, "Item {0} is now collapsed",
  224. itemId);
  225. }
  226. clearPreorderCache();
  227. }
  228. private void clearPreorderCache() {
  229. preOrder = null; // clear preorder cache
  230. }
  231. List<Object> preOrder;
  232. /**
  233. * Preorder of ids currently visible
  234. *
  235. * @return
  236. */
  237. private List<Object> getPreOrder() {
  238. if (preOrder == null) {
  239. preOrder = new ArrayList<Object>();
  240. Collection<?> rootItemIds = getContainerDataSource()
  241. .rootItemIds();
  242. for (Object id : rootItemIds) {
  243. preOrder.add(id);
  244. addVisibleChildTree(id);
  245. }
  246. }
  247. return preOrder;
  248. }
  249. private void addVisibleChildTree(Object id) {
  250. if (isNodeOpen(id)) {
  251. Collection<?> children = getContainerDataSource()
  252. .getChildren(id);
  253. if (children != null) {
  254. for (Object childId : children) {
  255. preOrder.add(childId);
  256. addVisibleChildTree(childId);
  257. }
  258. }
  259. }
  260. }
  261. @Override
  262. public int indexOfId(Object id) {
  263. return getPreOrder().indexOf(id);
  264. }
  265. @Override
  266. public Object getIdByIndex(int index) {
  267. return getPreOrder().get(index);
  268. }
  269. @Override
  270. public void containerItemSetChange(ItemSetChangeEvent event) {
  271. // preorder becomes invalid on sort, item additions etc.
  272. clearPreorderCache();
  273. super.containerItemSetChange(event);
  274. }
  275. }
  276. /**
  277. * Creates an empty TreeTable with a default container.
  278. */
  279. public TreeTable() {
  280. super(null, new HierarchicalContainer());
  281. }
  282. /**
  283. * Creates an empty TreeTable with a default container.
  284. *
  285. * @param caption
  286. * the caption for the TreeTable
  287. */
  288. public TreeTable(String caption) {
  289. this();
  290. setCaption(caption);
  291. }
  292. /**
  293. * Creates a TreeTable instance with given captions and data source.
  294. *
  295. * @param caption
  296. * the caption for the component
  297. * @param dataSource
  298. * the dataSource that is used to list items in the component
  299. */
  300. public TreeTable(String caption, Container dataSource) {
  301. super(caption, dataSource);
  302. }
  303. private ContainerStrategy cStrategy;
  304. private Object focusedRowId = null;
  305. private Object hierarchyColumnId;
  306. /**
  307. * The item id that was expanded or collapsed during this request. Reset at
  308. * the end of paint and only used for determining if a partial or full paint
  309. * should be done.
  310. *
  311. * Can safely be reset to null whenever a change occurs that would prevent a
  312. * partial update from rendering the correct result, e.g. rows added or
  313. * removed during an expand operation.
  314. */
  315. private Object toggledItemId;
  316. private boolean animationsEnabled;
  317. private boolean clearFocusedRowPending;
  318. /**
  319. * If the container does not send item set change events, always do a full
  320. * repaint instead of a partial update when expanding/collapsing nodes.
  321. */
  322. private boolean containerSupportsPartialUpdates;
  323. private ContainerStrategy getContainerStrategy() {
  324. if (cStrategy == null) {
  325. if (getContainerDataSource() instanceof Collapsible) {
  326. cStrategy = new CollapsibleStrategy();
  327. } else {
  328. cStrategy = new HierarchicalStrategy();
  329. }
  330. }
  331. return cStrategy;
  332. }
  333. @Override
  334. protected void paintRowAttributes(PaintTarget target, Object itemId)
  335. throws PaintException {
  336. super.paintRowAttributes(target, itemId);
  337. target.addAttribute("depth", getContainerStrategy().getDepth(itemId));
  338. if (getContainerDataSource().areChildrenAllowed(itemId)) {
  339. target.addAttribute("ca", true);
  340. target.addAttribute("open",
  341. getContainerStrategy().isNodeOpen(itemId));
  342. }
  343. }
  344. @Override
  345. protected void paintRowIcon(PaintTarget target, Object[][] cells,
  346. int indexInRowbuffer) throws PaintException {
  347. // always paint if present (in parent only if row headers visible)
  348. if (getRowHeaderMode() == ROW_HEADER_MODE_HIDDEN) {
  349. Resource itemIcon = getItemIcon(
  350. cells[CELL_ITEMID][indexInRowbuffer]);
  351. if (itemIcon != null) {
  352. target.addAttribute("icon", itemIcon);
  353. }
  354. } else if (cells[CELL_ICON][indexInRowbuffer] != null) {
  355. target.addAttribute("icon",
  356. (Resource) cells[CELL_ICON][indexInRowbuffer]);
  357. }
  358. }
  359. @Override
  360. protected boolean rowHeadersAreEnabled() {
  361. if (getRowHeaderMode() == RowHeaderMode.ICON_ONLY) {
  362. return false;
  363. }
  364. return super.rowHeadersAreEnabled();
  365. }
  366. @Override
  367. public void changeVariables(Object source, Map<String, Object> variables) {
  368. super.changeVariables(source, variables);
  369. if (variables.containsKey("toggleCollapsed")) {
  370. String object = (String) variables.get("toggleCollapsed");
  371. Object itemId = itemIdMapper.get(object);
  372. toggledItemId = itemId;
  373. toggleChildVisibility(itemId, false);
  374. if (variables.containsKey("selectCollapsed")) {
  375. // ensure collapsed is selected unless opened with selection
  376. // head
  377. if (isSelectable()) {
  378. select(itemId);
  379. }
  380. }
  381. } else if (variables.containsKey("focusParent")) {
  382. String key = (String) variables.get("focusParent");
  383. Object refId = itemIdMapper.get(key);
  384. Object itemId = getParent(refId);
  385. focusParent(itemId);
  386. }
  387. }
  388. private void focusParent(Object itemId) {
  389. boolean inView = false;
  390. Object inPageId = getCurrentPageFirstItemId();
  391. for (int i = 0; inPageId != null && i < getPageLength(); i++) {
  392. if (inPageId.equals(itemId)) {
  393. inView = true;
  394. break;
  395. }
  396. inPageId = nextItemId(inPageId);
  397. i++;
  398. }
  399. if (!inView) {
  400. setCurrentPageFirstItemId(itemId);
  401. }
  402. // Select the row if it is selectable.
  403. if (isSelectable()) {
  404. if (isMultiSelect()) {
  405. setValue(Collections.singleton(itemId));
  406. } else {
  407. setValue(itemId);
  408. }
  409. }
  410. setFocusedRow(itemId);
  411. }
  412. private void setFocusedRow(Object itemId) {
  413. focusedRowId = itemId;
  414. if (focusedRowId == null) {
  415. // Must still inform the client that the focusParent request has
  416. // been processed
  417. clearFocusedRowPending = true;
  418. }
  419. markAsDirty();
  420. }
  421. @Override
  422. public void paintContent(PaintTarget target) throws PaintException {
  423. if (focusedRowId != null) {
  424. target.addAttribute("focusedRow", itemIdMapper.key(focusedRowId));
  425. focusedRowId = null;
  426. } else if (clearFocusedRowPending) {
  427. // Must still inform the client that the focusParent request has
  428. // been processed
  429. target.addAttribute("clearFocusPending", true);
  430. clearFocusedRowPending = false;
  431. }
  432. target.addAttribute("animate", animationsEnabled);
  433. if (hierarchyColumnId != null) {
  434. Object[] visibleColumns2 = getVisibleColumns();
  435. for (int i = 0; i < visibleColumns2.length; i++) {
  436. Object object = visibleColumns2[i];
  437. if (hierarchyColumnId.equals(object)) {
  438. target.addAttribute(
  439. TreeTableConstants.ATTRIBUTE_HIERARCHY_COLUMN_INDEX,
  440. i);
  441. break;
  442. }
  443. }
  444. }
  445. super.paintContent(target);
  446. toggledItemId = null;
  447. }
  448. /*
  449. * Override methods for partial row updates and additions when expanding /
  450. * collapsing nodes.
  451. */
  452. @Override
  453. protected boolean isPartialRowUpdate() {
  454. return toggledItemId != null && containerSupportsPartialUpdates
  455. && !isRowCacheInvalidated();
  456. }
  457. @Override
  458. protected int getFirstAddedItemIndex() {
  459. return indexOfId(toggledItemId) + 1;
  460. }
  461. @Override
  462. protected int getAddedRowCount() {
  463. return countSubNodesRecursively(getContainerDataSource(),
  464. toggledItemId);
  465. }
  466. private int countSubNodesRecursively(Hierarchical hc, Object itemId) {
  467. int count = 0;
  468. // we need the number of children for toggledItemId no matter if its
  469. // collapsed or expanded. Other items' children are only counted if the
  470. // item is expanded.
  471. if (getContainerStrategy().isNodeOpen(itemId)
  472. || itemId == toggledItemId) {
  473. Collection<?> children = hc.getChildren(itemId);
  474. if (children != null) {
  475. count += children != null ? children.size() : 0;
  476. for (Object id : children) {
  477. count += countSubNodesRecursively(hc, id);
  478. }
  479. }
  480. }
  481. return count;
  482. }
  483. @Override
  484. protected int getFirstUpdatedItemIndex() {
  485. return indexOfId(toggledItemId);
  486. }
  487. @Override
  488. protected int getUpdatedRowCount() {
  489. return 1;
  490. }
  491. @Override
  492. protected boolean shouldHideAddedRows() {
  493. return !getContainerStrategy().isNodeOpen(toggledItemId);
  494. }
  495. private void toggleChildVisibility(Object itemId,
  496. boolean forceFullRefresh) {
  497. getContainerStrategy().toggleChildVisibility(itemId);
  498. // ensure that page still has first item in page, DON'T clear the
  499. // caches.
  500. setCurrentPageFirstItemIndex(getCurrentPageFirstItemIndex(), false);
  501. if (isCollapsed(itemId)) {
  502. fireCollapseEvent(itemId);
  503. } else {
  504. fireExpandEvent(itemId);
  505. }
  506. if (containerSupportsPartialUpdates && !forceFullRefresh) {
  507. markAsDirty();
  508. } else {
  509. // For containers that do not send item set change events, always do
  510. // full repaint instead of partial row update.
  511. refreshRowCache();
  512. }
  513. }
  514. @Override
  515. public int size() {
  516. return getContainerStrategy().size();
  517. }
  518. @Override
  519. public Hierarchical getContainerDataSource() {
  520. return (Hierarchical) super.getContainerDataSource();
  521. }
  522. @Override
  523. public void setContainerDataSource(Container newDataSource) {
  524. cStrategy = null;
  525. // FIXME: This disables partial updates until TreeTable is fixed so it
  526. // does not change component hierarchy during paint
  527. containerSupportsPartialUpdates = (newDataSource instanceof ItemSetChangeNotifier)
  528. && false;
  529. if (newDataSource != null && !(newDataSource instanceof Hierarchical)) {
  530. newDataSource = new ContainerHierarchicalWrapper(newDataSource);
  531. }
  532. if (newDataSource != null && !(newDataSource instanceof Ordered)) {
  533. newDataSource = new HierarchicalContainerOrderedWrapper(
  534. (Hierarchical) newDataSource);
  535. }
  536. super.setContainerDataSource(newDataSource);
  537. }
  538. @Override
  539. public void containerItemSetChange(Container.ItemSetChangeEvent event) {
  540. // Can't do partial repaints if items are added or removed during the
  541. // expand/collapse request
  542. toggledItemId = null;
  543. getContainerStrategy().containerItemSetChange(event);
  544. super.containerItemSetChange(event);
  545. }
  546. @Override
  547. protected Object getIdByIndex(int index) {
  548. return getContainerStrategy().getIdByIndex(index);
  549. }
  550. @Override
  551. protected int indexOfId(Object itemId) {
  552. return getContainerStrategy().indexOfId(itemId);
  553. }
  554. @Override
  555. public Object nextItemId(Object itemId) {
  556. return getContainerStrategy().nextItemId(itemId);
  557. }
  558. @Override
  559. public Object lastItemId() {
  560. return getContainerStrategy().lastItemId();
  561. }
  562. @Override
  563. public Object prevItemId(Object itemId) {
  564. return getContainerStrategy().prevItemId(itemId);
  565. }
  566. @Override
  567. public boolean isLastId(Object itemId) {
  568. return getContainerStrategy().isLastId(itemId);
  569. }
  570. @Override
  571. public Collection<?> getItemIds() {
  572. return getContainerStrategy().getItemIds();
  573. }
  574. @Override
  575. public boolean areChildrenAllowed(Object itemId) {
  576. return getContainerDataSource().areChildrenAllowed(itemId);
  577. }
  578. @Override
  579. public Collection<?> getChildren(Object itemId) {
  580. return getContainerDataSource().getChildren(itemId);
  581. }
  582. @Override
  583. public Object getParent(Object itemId) {
  584. return getContainerDataSource().getParent(itemId);
  585. }
  586. @Override
  587. public boolean hasChildren(Object itemId) {
  588. return getContainerDataSource().hasChildren(itemId);
  589. }
  590. @Override
  591. public boolean isRoot(Object itemId) {
  592. return getContainerDataSource().isRoot(itemId);
  593. }
  594. @Override
  595. public Collection<?> rootItemIds() {
  596. return getContainerDataSource().rootItemIds();
  597. }
  598. @Override
  599. public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed)
  600. throws UnsupportedOperationException {
  601. return getContainerDataSource().setChildrenAllowed(itemId,
  602. areChildrenAllowed);
  603. }
  604. @Override
  605. public boolean setParent(Object itemId, Object newParentId)
  606. throws UnsupportedOperationException {
  607. return getContainerDataSource().setParent(itemId, newParentId);
  608. }
  609. /**
  610. * Sets the Item specified by given identifier as collapsed or expanded. If
  611. * the Item is collapsed, its children are not displayed to the user.
  612. *
  613. * @param itemId
  614. * the identifier of the Item
  615. * @param collapsed
  616. * true if the Item should be collapsed, false if expanded
  617. */
  618. public void setCollapsed(Object itemId, boolean collapsed) {
  619. if (isCollapsed(itemId) != collapsed) {
  620. if (null == toggledItemId && !isRowCacheInvalidated()
  621. && getVisibleItemIds().contains(itemId)) {
  622. // optimization: partial refresh if only one item is
  623. // collapsed/expanded
  624. toggledItemId = itemId;
  625. toggleChildVisibility(itemId, false);
  626. } else {
  627. // make sure a full refresh takes place - otherwise neither
  628. // partial nor full repaint of table content is performed
  629. toggledItemId = null;
  630. toggleChildVisibility(itemId, true);
  631. }
  632. }
  633. }
  634. /**
  635. * Checks if Item with given identifier is collapsed in the UI.
  636. *
  637. * <p>
  638. *
  639. * @param itemId
  640. * the identifier of the checked Item
  641. * @return true if the Item with given id is collapsed
  642. * @see Collapsible#isCollapsed(Object)
  643. */
  644. public boolean isCollapsed(Object itemId) {
  645. return !getContainerStrategy().isNodeOpen(itemId);
  646. }
  647. /**
  648. * Explicitly sets the column in which the TreeTable visualizes the
  649. * hierarchy. If hierarchyColumnId is not set, the hierarchy is visualized
  650. * in the first visible column.
  651. *
  652. * @param hierarchyColumnId
  653. */
  654. public void setHierarchyColumn(Object hierarchyColumnId) {
  655. this.hierarchyColumnId = hierarchyColumnId;
  656. }
  657. /**
  658. * @return the identifier of column into which the hierarchy will be
  659. * visualized or null if the column is not explicitly defined.
  660. */
  661. public Object getHierarchyColumnId() {
  662. return hierarchyColumnId;
  663. }
  664. /**
  665. * Adds an expand listener.
  666. *
  667. * @param listener
  668. * the Listener to be added.
  669. */
  670. public void addExpandListener(ExpandListener listener) {
  671. addListener(ExpandEvent.class, listener, ExpandListener.EXPAND_METHOD);
  672. }
  673. /**
  674. * @deprecated As of 7.0, replaced by
  675. * {@link #addExpandListener(ExpandListener)}
  676. **/
  677. @Deprecated
  678. public void addListener(ExpandListener listener) {
  679. addExpandListener(listener);
  680. }
  681. /**
  682. * Removes an expand listener.
  683. *
  684. * @param listener
  685. * the Listener to be removed.
  686. */
  687. public void removeExpandListener(ExpandListener listener) {
  688. removeListener(ExpandEvent.class, listener,
  689. ExpandListener.EXPAND_METHOD);
  690. }
  691. /**
  692. * @deprecated As of 7.0, replaced by
  693. * {@link #removeExpandListener(ExpandListener)}
  694. **/
  695. @Deprecated
  696. public void removeListener(ExpandListener listener) {
  697. removeExpandListener(listener);
  698. }
  699. /**
  700. * Emits an expand event.
  701. *
  702. * @param itemId
  703. * the item id.
  704. */
  705. protected void fireExpandEvent(Object itemId) {
  706. fireEvent(new ExpandEvent(this, itemId));
  707. }
  708. /**
  709. * Adds a collapse listener.
  710. *
  711. * @param listener
  712. * the Listener to be added.
  713. */
  714. public void addCollapseListener(CollapseListener listener) {
  715. addListener(CollapseEvent.class, listener,
  716. CollapseListener.COLLAPSE_METHOD);
  717. }
  718. /**
  719. * @deprecated As of 7.0, replaced by
  720. * {@link #addCollapseListener(CollapseListener)}
  721. **/
  722. @Deprecated
  723. public void addListener(CollapseListener listener) {
  724. addCollapseListener(listener);
  725. }
  726. /**
  727. * Removes a collapse listener.
  728. *
  729. * @param listener
  730. * the Listener to be removed.
  731. */
  732. public void removeCollapseListener(CollapseListener listener) {
  733. removeListener(CollapseEvent.class, listener,
  734. CollapseListener.COLLAPSE_METHOD);
  735. }
  736. /**
  737. * @deprecated As of 7.0, replaced by
  738. * {@link #removeCollapseListener(CollapseListener)}
  739. **/
  740. @Deprecated
  741. public void removeListener(CollapseListener listener) {
  742. removeCollapseListener(listener);
  743. }
  744. /**
  745. * Emits a collapse event.
  746. *
  747. * @param itemId
  748. * the item id.
  749. */
  750. protected void fireCollapseEvent(Object itemId) {
  751. fireEvent(new CollapseEvent(this, itemId));
  752. }
  753. /**
  754. * @return true if animations are enabled
  755. */
  756. public boolean isAnimationsEnabled() {
  757. return animationsEnabled;
  758. }
  759. /**
  760. * Animations can be enabled by passing true to this method. Currently
  761. * expanding rows slide in from the top and collapsing rows slide out the
  762. * same way. NOTE! not supported in Internet Explorer 6 or 7.
  763. *
  764. * @param animationsEnabled
  765. * true or false whether to enable animations or not.
  766. */
  767. public void setAnimationsEnabled(boolean animationsEnabled) {
  768. this.animationsEnabled = animationsEnabled;
  769. markAsDirty();
  770. }
  771. private static final Logger getLogger() {
  772. return Logger.getLogger(TreeTable.class.getName());
  773. }
  774. @Override
  775. protected List<Object> getItemIds(int firstIndex, int rows) {
  776. List<Object> itemIds = new ArrayList<Object>();
  777. for (int i = firstIndex; i < firstIndex + rows; i++) {
  778. itemIds.add(getIdByIndex(i));
  779. }
  780. return itemIds;
  781. }
  782. @Override
  783. protected void readBody(Element design, DesignContext context) {
  784. Element tbody = design.select("> table > tbody").first();
  785. if (tbody == null) {
  786. return;
  787. }
  788. Set<String> selected = new HashSet<String>();
  789. Stack<Object> parents = new Stack<Object>();
  790. int lastDepth = -1;
  791. for (Element tr : tbody.children()) {
  792. int depth = DesignAttributeHandler.readAttribute("depth",
  793. tr.attributes(), 0, int.class);
  794. if (depth < 0 || depth > lastDepth + 1) {
  795. throw new DesignException(
  796. "Malformed TreeTable item hierarchy at " + tr
  797. + ": last depth was " + lastDepth);
  798. } else if (depth <= lastDepth) {
  799. for (int d = depth; d <= lastDepth; d++) {
  800. parents.pop();
  801. }
  802. }
  803. Object itemId = readItem(tr, selected, context);
  804. setParent(itemId, !parents.isEmpty() ? parents.peek() : null);
  805. parents.push(itemId);
  806. lastDepth = depth;
  807. }
  808. }
  809. @Override
  810. protected Object readItem(Element tr, Set<String> selected,
  811. DesignContext context) {
  812. Object itemId = super.readItem(tr, selected, context);
  813. if (tr.hasAttr("collapsed")) {
  814. boolean collapsed = DesignAttributeHandler
  815. .readAttribute("collapsed", tr.attributes(), boolean.class);
  816. setCollapsed(itemId, collapsed);
  817. }
  818. return itemId;
  819. }
  820. @Override
  821. protected void writeItems(Element design, DesignContext context) {
  822. if (getVisibleColumns().length == 0) {
  823. return;
  824. }
  825. Element tbody = design.child(0).appendElement("tbody");
  826. writeItems(tbody, rootItemIds(), 0, context);
  827. }
  828. protected void writeItems(Element tbody, Collection<?> itemIds, int depth,
  829. DesignContext context) {
  830. for (Object itemId : itemIds) {
  831. Element tr = writeItem(tbody, itemId, context);
  832. DesignAttributeHandler.writeAttribute("depth", tr.attributes(),
  833. depth, 0, int.class, context);
  834. if (getChildren(itemId) != null) {
  835. writeItems(tbody, getChildren(itemId), depth + 1, context);
  836. }
  837. }
  838. }
  839. @Override
  840. protected Element writeItem(Element tbody, Object itemId,
  841. DesignContext context) {
  842. Element tr = super.writeItem(tbody, itemId, context);
  843. DesignAttributeHandler.writeAttribute("collapsed", tr.attributes(),
  844. isCollapsed(itemId), true, boolean.class, context);
  845. return tr;
  846. }
  847. @Override
  848. protected TreeTableState getState() {
  849. return (TreeTableState) super.getState();
  850. }
  851. }