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

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