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 36KB

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