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.

VTreeTable.java 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  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.client.ui;
  17. import java.util.ArrayList;
  18. import java.util.Iterator;
  19. import java.util.LinkedList;
  20. import java.util.List;
  21. import com.google.gwt.animation.client.Animation;
  22. import com.google.gwt.core.client.Scheduler;
  23. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  24. import com.google.gwt.dom.client.Document;
  25. import com.google.gwt.dom.client.Element;
  26. import com.google.gwt.dom.client.SpanElement;
  27. import com.google.gwt.dom.client.Style.Display;
  28. import com.google.gwt.dom.client.Style.Unit;
  29. import com.google.gwt.dom.client.Style.Visibility;
  30. import com.google.gwt.dom.client.TableCellElement;
  31. import com.google.gwt.event.dom.client.KeyCodes;
  32. import com.google.gwt.user.client.DOM;
  33. import com.google.gwt.user.client.Event;
  34. import com.google.gwt.user.client.ui.Widget;
  35. import com.vaadin.client.ComputedStyle;
  36. import com.vaadin.client.UIDL;
  37. import com.vaadin.client.WidgetUtil;
  38. import com.vaadin.client.ui.VTreeTable.VTreeTableScrollBody.VTreeTableRow;
  39. public class VTreeTable extends VScrollTable {
  40. /** For internal use only. May be removed or replaced in the future. */
  41. public static class PendingNavigationEvent {
  42. public final int keycode;
  43. public final boolean ctrl;
  44. public final boolean shift;
  45. public PendingNavigationEvent(int keycode, boolean ctrl, boolean shift) {
  46. this.keycode = keycode;
  47. this.ctrl = ctrl;
  48. this.shift = shift;
  49. }
  50. @Override
  51. public String toString() {
  52. String string = "Keyboard event: " + keycode;
  53. if (ctrl) {
  54. string += " + ctrl";
  55. }
  56. if (shift) {
  57. string += " + shift";
  58. }
  59. return string;
  60. }
  61. }
  62. /** For internal use only. May be removed or replaced in the future. */
  63. public boolean collapseRequest;
  64. private boolean selectionPending;
  65. /** For internal use only. May be removed or replaced in the future. */
  66. public int colIndexOfHierarchy;
  67. /** For internal use only. May be removed or replaced in the future. */
  68. public String collapsedRowKey;
  69. /** For internal use only. May be removed or replaced in the future. */
  70. public VTreeTableScrollBody scrollBody;
  71. /** For internal use only. May be removed or replaced in the future. */
  72. public boolean animationsEnabled;
  73. /** For internal use only. May be removed or replaced in the future. */
  74. public LinkedList<PendingNavigationEvent> pendingNavigationEvents = new LinkedList<VTreeTable.PendingNavigationEvent>();
  75. /** For internal use only. May be removed or replaced in the future. */
  76. public boolean focusParentResponsePending;
  77. @Override
  78. protected VScrollTableBody createScrollBody() {
  79. scrollBody = new VTreeTableScrollBody();
  80. return scrollBody;
  81. }
  82. /*
  83. * Overridden to allow animation of expands and collapses of nodes.
  84. */
  85. @Override
  86. public void addAndRemoveRows(UIDL partialRowAdditions) {
  87. if (partialRowAdditions == null) {
  88. return;
  89. }
  90. if (animationsEnabled) {
  91. if (partialRowAdditions.hasAttribute("hide")) {
  92. scrollBody.unlinkRowsAnimatedAndUpdateCacheWhenFinished(
  93. partialRowAdditions.getIntAttribute("firstprowix"),
  94. partialRowAdditions.getIntAttribute("numprows"));
  95. } else {
  96. scrollBody.insertRowsAnimated(partialRowAdditions,
  97. partialRowAdditions.getIntAttribute("firstprowix"),
  98. partialRowAdditions.getIntAttribute("numprows"));
  99. discardRowsOutsideCacheWindow();
  100. }
  101. } else {
  102. super.addAndRemoveRows(partialRowAdditions);
  103. }
  104. }
  105. @Override
  106. protected int getHierarchyColumnIndex() {
  107. return colIndexOfHierarchy + (showRowHeaders ? 1 : 0);
  108. }
  109. public class VTreeTableScrollBody extends VScrollTable.VScrollTableBody {
  110. private int indentWidth = -1;
  111. private int maxIndent = 0;
  112. protected VTreeTableScrollBody() {
  113. super();
  114. }
  115. @Override
  116. protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
  117. if (uidl.hasAttribute("gen_html")) {
  118. // This is a generated row.
  119. return new VTreeTableGeneratedRow(uidl, aligns2);
  120. }
  121. return new VTreeTableRow(uidl, aligns2);
  122. }
  123. public class VTreeTableRow extends
  124. VScrollTable.VScrollTableBody.VScrollTableRow {
  125. private boolean isTreeCellAdded = false;
  126. private SpanElement treeSpacer;
  127. private boolean open;
  128. private int depth;
  129. private boolean canHaveChildren;
  130. protected Widget widgetInHierarchyColumn;
  131. public VTreeTableRow(UIDL uidl, char[] aligns2) {
  132. super(uidl, aligns2);
  133. // this fix causes #15118 and doesn't work for treetable anyway
  134. applyZeroWidthFix = false;
  135. }
  136. @Override
  137. public void addCell(UIDL rowUidl, String text, char align,
  138. String style, boolean textIsHTML, boolean isSorted,
  139. String description) {
  140. super.addCell(rowUidl, text, align, style, textIsHTML,
  141. isSorted, description);
  142. addTreeSpacer(rowUidl);
  143. }
  144. protected boolean addTreeSpacer(UIDL rowUidl) {
  145. if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) {
  146. Element container = (Element) getElement().getLastChild()
  147. .getFirstChild();
  148. if (rowUidl.hasAttribute("icon")) {
  149. Icon icon = client.getIcon(rowUidl
  150. .getStringAttribute("icon"));
  151. icon.setAlternateText("icon");
  152. container.insertFirst(icon.getElement());
  153. }
  154. String classname = "v-treetable-treespacer";
  155. if (rowUidl.getBooleanAttribute("ca")) {
  156. canHaveChildren = true;
  157. open = rowUidl.getBooleanAttribute("open");
  158. classname += open ? " v-treetable-node-open"
  159. : " v-treetable-node-closed";
  160. }
  161. treeSpacer = Document.get().createSpanElement();
  162. treeSpacer.setClassName(classname);
  163. container.insertFirst(treeSpacer);
  164. depth = rowUidl.hasAttribute("depth") ? rowUidl
  165. .getIntAttribute("depth") : 0;
  166. setIndent();
  167. isTreeCellAdded = true;
  168. return true;
  169. }
  170. return false;
  171. }
  172. private boolean cellShowsTreeHierarchy(int curColIndex) {
  173. if (isTreeCellAdded) {
  174. return false;
  175. }
  176. return curColIndex == getHierarchyColumnIndex();
  177. }
  178. @Override
  179. public void onBrowserEvent(Event event) {
  180. if (event.getEventTarget().cast() == treeSpacer
  181. && treeSpacer.getClassName().contains("node")) {
  182. if (event.getTypeInt() == Event.ONMOUSEUP) {
  183. sendToggleCollapsedUpdate(getKey());
  184. }
  185. return;
  186. }
  187. super.onBrowserEvent(event);
  188. }
  189. @Override
  190. public void addCell(UIDL rowUidl, Widget w, char align,
  191. String style, boolean isSorted, String description) {
  192. super.addCell(rowUidl, w, align, style, isSorted, description);
  193. if (addTreeSpacer(rowUidl)) {
  194. widgetInHierarchyColumn = w;
  195. }
  196. }
  197. private void setIndent() {
  198. if (getIndentWidth() > 0) {
  199. treeSpacer.getParentElement().getStyle()
  200. .setPaddingLeft(getIndent(), Unit.PX);
  201. treeSpacer.getStyle().setWidth(getIndent(), Unit.PX);
  202. int colWidth = getColWidth(getHierarchyColumnIndex());
  203. if (colWidth > 0 && getIndent() > colWidth) {
  204. VTreeTable.this.setColWidth(getHierarchyColumnIndex(),
  205. getIndent(), false);
  206. }
  207. }
  208. }
  209. @Override
  210. protected void onAttach() {
  211. super.onAttach();
  212. if (getIndentWidth() < 0) {
  213. detectIndent(this);
  214. // If we detect indent here then the size of the hierarchy
  215. // column is still wrong as it has been set when the indent
  216. // was not known.
  217. int w = getCellWidthFromDom(getHierarchyColumnIndex());
  218. if (w >= 0) {
  219. setColWidth(getHierarchyColumnIndex(), w);
  220. }
  221. }
  222. }
  223. private int getCellWidthFromDom(int cellIndex) {
  224. final Element cell = DOM.getChild(getElement(), cellIndex);
  225. String w = cell.getStyle().getProperty("width");
  226. if (w == null || "".equals(w) || !w.endsWith("px")) {
  227. return -1;
  228. } else {
  229. return Integer.parseInt(w.substring(0, w.length() - 2));
  230. }
  231. }
  232. private int getHierarchyAndIconWidth() {
  233. int consumedSpace = treeSpacer.getOffsetWidth();
  234. if (treeSpacer.getParentElement().getChildCount() > 2) {
  235. // icon next to tree spacer
  236. consumedSpace += ((com.google.gwt.dom.client.Element) treeSpacer
  237. .getNextSibling()).getOffsetWidth();
  238. }
  239. return consumedSpace;
  240. }
  241. @Override
  242. protected void setCellWidth(int cellIx, int width) {
  243. if (cellIx == getHierarchyColumnIndex()) {
  244. // take indentation padding into account if this is the
  245. // hierarchy column
  246. int indent = getIndent();
  247. if (indent != -1) {
  248. width = Math.max(width - indent, 0);
  249. }
  250. }
  251. super.setCellWidth(cellIx, width);
  252. }
  253. private int getIndent() {
  254. return (depth + 1) * getIndentWidth();
  255. }
  256. }
  257. protected class VTreeTableGeneratedRow extends VTreeTableRow {
  258. private boolean spanColumns;
  259. private boolean htmlContentAllowed;
  260. public VTreeTableGeneratedRow(UIDL uidl, char[] aligns) {
  261. super(uidl, aligns);
  262. addStyleName("v-table-generated-row");
  263. }
  264. public boolean isSpanColumns() {
  265. return spanColumns;
  266. }
  267. @Override
  268. protected void initCellWidths() {
  269. if (spanColumns) {
  270. setSpannedColumnWidthAfterDOMFullyInited();
  271. } else {
  272. super.initCellWidths();
  273. }
  274. }
  275. private void setSpannedColumnWidthAfterDOMFullyInited() {
  276. // Defer setting width on spanned columns to make sure that
  277. // they are added to the DOM before trying to calculate
  278. // widths.
  279. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  280. @Override
  281. public void execute() {
  282. if (showRowHeaders) {
  283. setCellWidth(0, tHead.getHeaderCell(0)
  284. .getWidthWithIndent());
  285. calcAndSetSpanWidthOnCell(1);
  286. } else {
  287. calcAndSetSpanWidthOnCell(0);
  288. }
  289. }
  290. });
  291. }
  292. @Override
  293. protected boolean isRenderHtmlInCells() {
  294. return htmlContentAllowed;
  295. }
  296. @Override
  297. protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
  298. int visibleColumnIndex) {
  299. htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
  300. spanColumns = uidl.getBooleanAttribute("gen_span");
  301. final Iterator<?> cells = uidl.getChildIterator();
  302. if (spanColumns) {
  303. int colCount = uidl.getChildCount();
  304. if (cells.hasNext()) {
  305. final Object cell = cells.next();
  306. if (cell instanceof String) {
  307. addSpannedCell(uidl, cell.toString(), aligns[0],
  308. "", htmlContentAllowed, false, null,
  309. colCount);
  310. } else {
  311. addSpannedCell(uidl, (Widget) cell, aligns[0], "",
  312. false, colCount);
  313. }
  314. }
  315. } else {
  316. super.addCellsFromUIDL(uidl, aligns, col,
  317. visibleColumnIndex);
  318. }
  319. }
  320. private void addSpannedCell(UIDL rowUidl, Widget w, char align,
  321. String style, boolean sorted, int colCount) {
  322. TableCellElement td = DOM.createTD().cast();
  323. td.setColSpan(colCount);
  324. initCellWithWidget(w, align, style, sorted, td);
  325. td.getStyle().setHeight(getRowHeight(), Unit.PX);
  326. if (addTreeSpacer(rowUidl)) {
  327. widgetInHierarchyColumn = w;
  328. }
  329. }
  330. private void addSpannedCell(UIDL rowUidl, String text, char align,
  331. String style, boolean textIsHTML, boolean sorted,
  332. String description, int colCount) {
  333. // String only content is optimized by not using Label widget
  334. final TableCellElement td = DOM.createTD().cast();
  335. td.setColSpan(colCount);
  336. initCellWithText(text, align, style, textIsHTML, sorted,
  337. description, td);
  338. td.getStyle().setHeight(getRowHeight(), Unit.PX);
  339. addTreeSpacer(rowUidl);
  340. }
  341. @Override
  342. protected void setCellWidth(int cellIx, int width) {
  343. if (isSpanColumns()) {
  344. if (showRowHeaders) {
  345. if (cellIx == 0) {
  346. super.setCellWidth(0, width);
  347. } else {
  348. // We need to recalculate the spanning TDs width for
  349. // every cellIx in order to support column resizing.
  350. calcAndSetSpanWidthOnCell(1);
  351. }
  352. } else {
  353. // Same as above.
  354. calcAndSetSpanWidthOnCell(0);
  355. }
  356. } else {
  357. super.setCellWidth(cellIx, width);
  358. }
  359. }
  360. private void calcAndSetSpanWidthOnCell(final int cellIx) {
  361. int spanWidth = 0;
  362. for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
  363. .getVisibleCellCount(); ix++) {
  364. spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
  365. }
  366. WidgetUtil.setWidthExcludingPaddingAndBorder(
  367. (Element) getElement().getChild(cellIx), spanWidth, 13,
  368. false);
  369. }
  370. }
  371. private int getIndentWidth() {
  372. return indentWidth;
  373. }
  374. @Override
  375. protected int getMaxIndent() {
  376. return maxIndent;
  377. }
  378. @Override
  379. protected void calculateMaxIndent() {
  380. int maxIndent = 0;
  381. Iterator<Widget> iterator = iterator();
  382. while (iterator.hasNext()) {
  383. VTreeTableRow next = (VTreeTableRow) iterator.next();
  384. maxIndent = Math.max(maxIndent, next.getIndent());
  385. }
  386. this.maxIndent = maxIndent;
  387. }
  388. private void detectIndent(VTreeTableRow vTreeTableRow) {
  389. indentWidth = vTreeTableRow.treeSpacer.getOffsetWidth();
  390. if (indentWidth == 0) {
  391. indentWidth = -1;
  392. return;
  393. }
  394. Iterator<Widget> iterator = iterator();
  395. while (iterator.hasNext()) {
  396. VTreeTableRow next = (VTreeTableRow) iterator.next();
  397. next.setIndent();
  398. }
  399. calculateMaxIndent();
  400. }
  401. protected void unlinkRowsAnimatedAndUpdateCacheWhenFinished(
  402. final int firstIndex, final int rows) {
  403. List<VScrollTableRow> rowsToDelete = new ArrayList<VScrollTableRow>();
  404. for (int ix = firstIndex; ix < firstIndex + rows; ix++) {
  405. VScrollTableRow row = getRowByRowIndex(ix);
  406. if (row != null) {
  407. rowsToDelete.add(row);
  408. }
  409. }
  410. if (!rowsToDelete.isEmpty()) {
  411. // #8810 Only animate if there's something to animate
  412. RowCollapseAnimation anim = new RowCollapseAnimation(
  413. rowsToDelete) {
  414. @Override
  415. protected void onComplete() {
  416. super.onComplete();
  417. // Actually unlink the rows and update the cache after
  418. // the
  419. // animation is done.
  420. unlinkAndReindexRows(firstIndex, rows);
  421. discardRowsOutsideCacheWindow();
  422. ensureCacheFilled();
  423. }
  424. };
  425. anim.run(150);
  426. }
  427. }
  428. protected List<VScrollTableRow> insertRowsAnimated(UIDL rowData,
  429. int firstIndex, int rows) {
  430. List<VScrollTableRow> insertedRows = insertAndReindexRows(rowData,
  431. firstIndex, rows);
  432. if (!insertedRows.isEmpty()) {
  433. // Only animate if there's something to animate (#8810)
  434. RowExpandAnimation anim = new RowExpandAnimation(insertedRows);
  435. anim.run(150);
  436. }
  437. scrollBody.calculateMaxIndent();
  438. return insertedRows;
  439. }
  440. /**
  441. * Prepares the table for animation by copying the background colors of
  442. * all TR elements to their respective TD elements if the TD element is
  443. * transparent. This is needed, since if TDs have transparent
  444. * backgrounds, the rows sliding behind them are visible.
  445. */
  446. private class AnimationPreparator {
  447. private final int lastItemIx;
  448. public AnimationPreparator(int lastItemIx) {
  449. this.lastItemIx = lastItemIx;
  450. }
  451. public void prepareTableForAnimation() {
  452. int ix = lastItemIx;
  453. VScrollTableRow row = null;
  454. while ((row = getRowByRowIndex(ix)) != null) {
  455. copyTRBackgroundsToTDs(row);
  456. --ix;
  457. }
  458. }
  459. private void copyTRBackgroundsToTDs(VScrollTableRow row) {
  460. Element tr = row.getElement();
  461. ComputedStyle cs = new ComputedStyle(tr);
  462. String backgroundAttachment = cs
  463. .getProperty("backgroundAttachment");
  464. String backgroundClip = cs.getProperty("backgroundClip");
  465. String backgroundColor = cs.getProperty("backgroundColor");
  466. String backgroundImage = cs.getProperty("backgroundImage");
  467. String backgroundOrigin = cs.getProperty("backgroundOrigin");
  468. for (int ix = 0; ix < tr.getChildCount(); ix++) {
  469. Element td = tr.getChild(ix).cast();
  470. if (!elementHasBackground(td)) {
  471. td.getStyle().setProperty("backgroundAttachment",
  472. backgroundAttachment);
  473. td.getStyle().setProperty("backgroundClip",
  474. backgroundClip);
  475. td.getStyle().setProperty("backgroundColor",
  476. backgroundColor);
  477. td.getStyle().setProperty("backgroundImage",
  478. backgroundImage);
  479. td.getStyle().setProperty("backgroundOrigin",
  480. backgroundOrigin);
  481. }
  482. }
  483. }
  484. private boolean elementHasBackground(Element element) {
  485. ComputedStyle cs = new ComputedStyle(element);
  486. String clr = cs.getProperty("backgroundColor");
  487. String img = cs.getProperty("backgroundImage");
  488. return !("rgba(0, 0, 0, 0)".equals(clr.trim())
  489. || "transparent".equals(clr.trim()) || img == null);
  490. }
  491. public void restoreTableAfterAnimation() {
  492. int ix = lastItemIx;
  493. VScrollTableRow row = null;
  494. while ((row = getRowByRowIndex(ix)) != null) {
  495. restoreStyleForTDsInRow(row);
  496. --ix;
  497. }
  498. }
  499. private void restoreStyleForTDsInRow(VScrollTableRow row) {
  500. Element tr = row.getElement();
  501. for (int ix = 0; ix < tr.getChildCount(); ix++) {
  502. Element td = tr.getChild(ix).cast();
  503. td.getStyle().clearProperty("backgroundAttachment");
  504. td.getStyle().clearProperty("backgroundClip");
  505. td.getStyle().clearProperty("backgroundColor");
  506. td.getStyle().clearProperty("backgroundImage");
  507. td.getStyle().clearProperty("backgroundOrigin");
  508. }
  509. }
  510. }
  511. /**
  512. * Animates row expansion using the GWT animation framework.
  513. *
  514. * The idea is as follows:
  515. *
  516. * 1. Insert all rows normally
  517. *
  518. * 2. Insert a newly created DIV containing a new TABLE element below
  519. * the DIV containing the actual scroll table body.
  520. *
  521. * 3. Clone the rows that were inserted in step 1 and attach the clones
  522. * to the new TABLE element created in step 2.
  523. *
  524. * 4. The new DIV from step 2 is absolutely positioned so that the last
  525. * inserted row is just behind the row that was expanded.
  526. *
  527. * 5. Hide the contents of the originally inserted rows by setting the
  528. * DIV.v-table-cell-wrapper to display:none;.
  529. *
  530. * 6. Set the height of the originally inserted rows to 0.
  531. *
  532. * 7. The animation loop slides the DIV from step 2 downwards, while at
  533. * the same pace growing the height of each of the inserted rows from 0
  534. * to full height. The first inserted row grows from 0 to full and after
  535. * this the second row grows from 0 to full, etc until all rows are full
  536. * height.
  537. *
  538. * 8. Remove the DIV from step 2
  539. *
  540. * 9. Restore display:block; to the DIV.v-table-cell-wrapper elements.
  541. *
  542. * 10. DONE
  543. */
  544. private class RowExpandAnimation extends Animation {
  545. private final List<VScrollTableRow> rows;
  546. private Element cloneDiv;
  547. private Element cloneTable;
  548. private AnimationPreparator preparator;
  549. /**
  550. * @param rows
  551. * List of rows to animate. Must not be empty.
  552. */
  553. public RowExpandAnimation(List<VScrollTableRow> rows) {
  554. this.rows = rows;
  555. buildAndInsertAnimatingDiv();
  556. preparator = new AnimationPreparator(rows.get(0).getIndex() - 1);
  557. preparator.prepareTableForAnimation();
  558. for (VScrollTableRow row : rows) {
  559. cloneAndAppendRow(row);
  560. row.addStyleName("v-table-row-animating");
  561. setCellWrapperDivsToDisplayNone(row);
  562. row.setHeight(getInitialHeight());
  563. }
  564. }
  565. protected String getInitialHeight() {
  566. return "0px";
  567. }
  568. private void cloneAndAppendRow(VScrollTableRow row) {
  569. Element clonedTR = null;
  570. clonedTR = row.getElement().cloneNode(true).cast();
  571. clonedTR.getStyle().setVisibility(Visibility.VISIBLE);
  572. cloneTable.appendChild(clonedTR);
  573. }
  574. protected double getBaseOffset() {
  575. return rows.get(0).getAbsoluteTop()
  576. - rows.get(0).getParent().getAbsoluteTop()
  577. - rows.size() * getRowHeight();
  578. }
  579. private void buildAndInsertAnimatingDiv() {
  580. cloneDiv = DOM.createDiv();
  581. cloneDiv.addClassName("v-treetable-animation-clone-wrapper");
  582. cloneTable = DOM.createTable();
  583. cloneTable.addClassName("v-treetable-animation-clone");
  584. cloneDiv.appendChild(cloneTable);
  585. insertAnimatingDiv();
  586. }
  587. private void insertAnimatingDiv() {
  588. Element tableBody = getElement();
  589. Element tableBodyParent = tableBody.getParentElement();
  590. tableBodyParent.insertAfter(cloneDiv, tableBody);
  591. }
  592. @Override
  593. protected void onUpdate(double progress) {
  594. animateDiv(progress);
  595. animateRowHeights(progress);
  596. }
  597. private void animateDiv(double progress) {
  598. double offset = calculateDivOffset(progress, getRowHeight());
  599. cloneDiv.getStyle().setTop(getBaseOffset() + offset, Unit.PX);
  600. }
  601. private void animateRowHeights(double progress) {
  602. double rh = getRowHeight();
  603. double vlh = calculateHeightOfAllVisibleLines(progress, rh);
  604. int ix = 0;
  605. while (ix < rows.size()) {
  606. double height = vlh < rh ? vlh : rh;
  607. rows.get(ix).setHeight(height + "px");
  608. vlh -= height;
  609. ix++;
  610. }
  611. }
  612. protected double calculateHeightOfAllVisibleLines(double progress,
  613. double rh) {
  614. return rows.size() * rh * progress;
  615. }
  616. protected double calculateDivOffset(double progress, double rh) {
  617. return progress * rows.size() * rh;
  618. }
  619. @Override
  620. protected void onComplete() {
  621. preparator.restoreTableAfterAnimation();
  622. for (VScrollTableRow row : rows) {
  623. resetCellWrapperDivsDisplayProperty(row);
  624. row.removeStyleName("v-table-row-animating");
  625. }
  626. Element tableBodyParent = getElement().getParentElement();
  627. tableBodyParent.removeChild(cloneDiv);
  628. }
  629. private void setCellWrapperDivsToDisplayNone(VScrollTableRow row) {
  630. Element tr = row.getElement();
  631. for (int ix = 0; ix < tr.getChildCount(); ix++) {
  632. getWrapperDiv(tr, ix).getStyle().setDisplay(Display.NONE);
  633. }
  634. }
  635. private Element getWrapperDiv(Element tr, int tdIx) {
  636. Element td = tr.getChild(tdIx).cast();
  637. return td.getChild(0).cast();
  638. }
  639. private void resetCellWrapperDivsDisplayProperty(VScrollTableRow row) {
  640. Element tr = row.getElement();
  641. for (int ix = 0; ix < tr.getChildCount(); ix++) {
  642. getWrapperDiv(tr, ix).getStyle().clearProperty("display");
  643. }
  644. }
  645. }
  646. /**
  647. * This is the inverse of the RowExpandAnimation and is implemented by
  648. * extending it and overriding the calculation of offsets and heights.
  649. */
  650. private class RowCollapseAnimation extends RowExpandAnimation {
  651. private final List<VScrollTableRow> rows;
  652. /**
  653. * @param rows
  654. * List of rows to animate. Must not be empty.
  655. */
  656. public RowCollapseAnimation(List<VScrollTableRow> rows) {
  657. super(rows);
  658. this.rows = rows;
  659. }
  660. @Override
  661. protected String getInitialHeight() {
  662. return getRowHeight() + "px";
  663. }
  664. @Override
  665. protected double getBaseOffset() {
  666. return getRowHeight();
  667. }
  668. @Override
  669. protected double calculateHeightOfAllVisibleLines(double progress,
  670. double rh) {
  671. return rows.size() * rh * (1 - progress);
  672. }
  673. @Override
  674. protected double calculateDivOffset(double progress, double rh) {
  675. return -super.calculateDivOffset(progress, rh);
  676. }
  677. }
  678. }
  679. /**
  680. * Icons rendered into first actual column in TreeTable, not to row header
  681. * cell
  682. */
  683. @Override
  684. protected String buildCaptionHtmlSnippet(UIDL uidl) {
  685. if (uidl.getTag().equals("column")) {
  686. return super.buildCaptionHtmlSnippet(uidl);
  687. } else {
  688. String s = uidl.getStringAttribute("caption");
  689. return s;
  690. }
  691. }
  692. /** For internal use only. May be removed or replaced in the future. */
  693. @Override
  694. public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
  695. if (collapseRequest || focusParentResponsePending) {
  696. // Enqueue the event if there might be pending content changes from
  697. // the server
  698. if (pendingNavigationEvents.size() < 10) {
  699. // Only keep 10 keyboard events in the queue
  700. PendingNavigationEvent pendingNavigationEvent = new PendingNavigationEvent(
  701. keycode, ctrl, shift);
  702. pendingNavigationEvents.add(pendingNavigationEvent);
  703. }
  704. return true;
  705. }
  706. VTreeTableRow focusedRow = (VTreeTableRow) getFocusedRow();
  707. if (focusedRow != null) {
  708. if (focusedRow.canHaveChildren
  709. && ((keycode == KeyCodes.KEY_RIGHT && !focusedRow.open) || (keycode == KeyCodes.KEY_LEFT && focusedRow.open))) {
  710. if (!ctrl) {
  711. client.updateVariable(paintableId, "selectCollapsed", true,
  712. false);
  713. }
  714. sendSelectedRows(false);
  715. sendToggleCollapsedUpdate(focusedRow.getKey());
  716. return true;
  717. } else if (keycode == KeyCodes.KEY_RIGHT && focusedRow.open) {
  718. // already expanded, move selection down if next is on a deeper
  719. // level (is-a-child)
  720. VTreeTableScrollBody body = (VTreeTableScrollBody) focusedRow
  721. .getParent();
  722. Iterator<Widget> iterator = body.iterator();
  723. VTreeTableRow next = null;
  724. while (iterator.hasNext()) {
  725. next = (VTreeTableRow) iterator.next();
  726. if (next == focusedRow) {
  727. next = (VTreeTableRow) iterator.next();
  728. break;
  729. }
  730. }
  731. if (next != null) {
  732. if (next.depth > focusedRow.depth) {
  733. selectionPending = true;
  734. return super.handleNavigation(getNavigationDownKey(),
  735. ctrl, shift);
  736. }
  737. } else {
  738. // Note, a minor change here for a bit false behavior if
  739. // cache rows is disabled + last visible row + no childs for
  740. // the node
  741. selectionPending = true;
  742. return super.handleNavigation(getNavigationDownKey(), ctrl,
  743. shift);
  744. }
  745. } else if (keycode == KeyCodes.KEY_LEFT) {
  746. // already collapsed move selection up to parent node
  747. // do on the server side as the parent is not necessary
  748. // rendered on the client, could check if parent is visible if
  749. // a performance issue arises
  750. client.updateVariable(paintableId, "focusParent",
  751. focusedRow.getKey(), true);
  752. // Set flag that we should enqueue navigation events until we
  753. // get a response to this request
  754. focusParentResponsePending = true;
  755. return true;
  756. }
  757. }
  758. return super.handleNavigation(keycode, ctrl, shift);
  759. }
  760. private void sendToggleCollapsedUpdate(String rowKey) {
  761. collapsedRowKey = rowKey;
  762. collapseRequest = true;
  763. client.updateVariable(paintableId, "toggleCollapsed", rowKey, true);
  764. }
  765. @Override
  766. public void onBrowserEvent(Event event) {
  767. super.onBrowserEvent(event);
  768. if (event.getTypeInt() == Event.ONKEYUP && selectionPending) {
  769. sendSelectedRows();
  770. }
  771. }
  772. @Override
  773. protected void sendSelectedRows(boolean immediately) {
  774. super.sendSelectedRows(immediately);
  775. selectionPending = false;
  776. }
  777. @Override
  778. protected void reOrderColumn(String columnKey, int newIndex) {
  779. super.reOrderColumn(columnKey, newIndex);
  780. // current impl not intelligent enough to survive without visiting the
  781. // server to redraw content
  782. client.sendPendingVariableChanges();
  783. }
  784. @Override
  785. public void setStyleName(String style) {
  786. super.setStyleName(style + " v-treetable");
  787. }
  788. @Override
  789. public void updateTotalRows(UIDL uidl) {
  790. // Make sure that initializedAndAttached & al are not reset when the
  791. // totalrows are updated on expand/collapse requests.
  792. int newTotalRows = uidl.getIntAttribute("totalrows");
  793. setTotalRows(newTotalRows);
  794. }
  795. }