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.

VAccordion.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.HashMap;
  6. import java.util.HashSet;
  7. import java.util.Iterator;
  8. import java.util.Set;
  9. import com.google.gwt.event.dom.client.ClickEvent;
  10. import com.google.gwt.event.dom.client.ClickHandler;
  11. import com.google.gwt.user.client.DOM;
  12. import com.google.gwt.user.client.Element;
  13. import com.google.gwt.user.client.Event;
  14. import com.google.gwt.user.client.ui.ComplexPanel;
  15. import com.google.gwt.user.client.ui.Widget;
  16. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  17. import com.vaadin.terminal.gwt.client.ContainerResizedListener;
  18. import com.vaadin.terminal.gwt.client.RenderInformation;
  19. import com.vaadin.terminal.gwt.client.RenderSpace;
  20. import com.vaadin.terminal.gwt.client.UIDL;
  21. import com.vaadin.terminal.gwt.client.Util;
  22. import com.vaadin.terminal.gwt.client.VCaption;
  23. import com.vaadin.terminal.gwt.client.VPaintableMap;
  24. import com.vaadin.terminal.gwt.client.VPaintableWidget;
  25. public class VAccordion extends VTabsheetBase implements
  26. ContainerResizedListener {
  27. public static final String CLASSNAME = "v-accordion";
  28. private Set<Widget> widgets = new HashSet<Widget>();
  29. private String height;
  30. private String width = "";
  31. private HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>();
  32. private RenderSpace renderSpace = new RenderSpace(0, 0, true);
  33. private StackItem openTab = null;
  34. private boolean rendering = false;
  35. private int selectedUIDLItemIndex = -1;
  36. private RenderInformation renderInformation = new RenderInformation();
  37. public VAccordion() {
  38. super(CLASSNAME);
  39. }
  40. @Override
  41. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  42. rendering = true;
  43. selectedUIDLItemIndex = -1;
  44. super.updateFromUIDL(uidl, client);
  45. /*
  46. * Render content after all tabs have been created and we know how large
  47. * the content area is
  48. */
  49. if (selectedUIDLItemIndex >= 0) {
  50. StackItem selectedItem = getStackItem(selectedUIDLItemIndex);
  51. UIDL selectedTabUIDL = lazyUpdateMap.remove(selectedItem);
  52. open(selectedUIDLItemIndex);
  53. selectedItem.setContent(selectedTabUIDL);
  54. } else if (!uidl.getBooleanAttribute("cached") && openTab != null) {
  55. close(openTab);
  56. }
  57. iLayout();
  58. // finally render possible hidden tabs
  59. if (lazyUpdateMap.size() > 0) {
  60. for (Iterator iterator = lazyUpdateMap.keySet().iterator(); iterator
  61. .hasNext();) {
  62. StackItem item = (StackItem) iterator.next();
  63. item.setContent(lazyUpdateMap.get(item));
  64. }
  65. lazyUpdateMap.clear();
  66. }
  67. renderInformation.updateSize(getElement());
  68. rendering = false;
  69. }
  70. @Override
  71. protected void renderTab(UIDL tabUidl, int index, boolean selected,
  72. boolean hidden) {
  73. StackItem item;
  74. int itemIndex;
  75. if (getWidgetCount() <= index) {
  76. // Create stackItem and render caption
  77. item = new StackItem(tabUidl);
  78. if (getWidgetCount() == 0) {
  79. item.addStyleDependentName("first");
  80. }
  81. itemIndex = getWidgetCount();
  82. add(item, getElement());
  83. } else {
  84. item = getStackItem(index);
  85. item = moveStackItemIfNeeded(item, index, tabUidl);
  86. itemIndex = index;
  87. }
  88. item.updateCaption(tabUidl);
  89. item.setVisible(!hidden);
  90. if (selected) {
  91. selectedUIDLItemIndex = itemIndex;
  92. }
  93. if (tabUidl.getChildCount() > 0) {
  94. lazyUpdateMap.put(item, tabUidl.getChildUIDL(0));
  95. }
  96. }
  97. /**
  98. * This method tries to find out if a tab has been rendered with a different
  99. * index previously. If this is the case it re-orders the children so the
  100. * same StackItem is used for rendering this time. E.g. if the first tab has
  101. * been removed all tabs which contain cached content must be moved 1 step
  102. * up to preserve the cached content.
  103. *
  104. * @param item
  105. * @param newIndex
  106. * @param tabUidl
  107. * @return
  108. */
  109. private StackItem moveStackItemIfNeeded(StackItem item, int newIndex,
  110. UIDL tabUidl) {
  111. UIDL tabContentUIDL = null;
  112. VPaintableWidget tabContent = null;
  113. if (tabUidl.getChildCount() > 0) {
  114. tabContentUIDL = tabUidl.getChildUIDL(0);
  115. tabContent = client.getPaintable(tabContentUIDL);
  116. }
  117. Widget itemWidget = item.getComponent();
  118. if (tabContent != null) {
  119. if (tabContent != itemWidget) {
  120. /*
  121. * This is not the same widget as before, find out if it has
  122. * been moved
  123. */
  124. int oldIndex = -1;
  125. StackItem oldItem = null;
  126. for (int i = 0; i < getWidgetCount(); i++) {
  127. Widget w = getWidget(i);
  128. oldItem = (StackItem) w;
  129. if (tabContent == oldItem.getComponent()) {
  130. oldIndex = i;
  131. break;
  132. }
  133. }
  134. if (oldIndex != -1 && oldIndex > newIndex) {
  135. /*
  136. * The tab has previously been rendered in another position
  137. * so we must move the cached content to correct position.
  138. * We move only items with oldIndex > newIndex to prevent
  139. * moving items already rendered in this update. If for
  140. * instance tabs 1,2,3 are removed and added as 3,2,1 we
  141. * cannot re-use "1" when we get to the third tab.
  142. */
  143. insert(oldItem, getElement(), newIndex, true);
  144. return oldItem;
  145. }
  146. }
  147. } else {
  148. // Tab which has never been loaded. Must assure we use an empty
  149. // StackItem
  150. Widget oldWidget = item.getComponent();
  151. if (oldWidget != null) {
  152. item = new StackItem(tabUidl);
  153. insert(item, getElement(), newIndex, true);
  154. }
  155. }
  156. return item;
  157. }
  158. private void open(int itemIndex) {
  159. StackItem item = (StackItem) getWidget(itemIndex);
  160. boolean alreadyOpen = false;
  161. if (openTab != null) {
  162. if (openTab.isOpen()) {
  163. if (openTab == item) {
  164. alreadyOpen = true;
  165. } else {
  166. openTab.close();
  167. }
  168. }
  169. }
  170. if (!alreadyOpen) {
  171. item.open();
  172. activeTabIndex = itemIndex;
  173. openTab = item;
  174. }
  175. // Update the size for the open tab
  176. updateOpenTabSize();
  177. }
  178. private void close(StackItem item) {
  179. if (!item.isOpen()) {
  180. return;
  181. }
  182. item.close();
  183. activeTabIndex = -1;
  184. openTab = null;
  185. }
  186. @Override
  187. protected void selectTab(final int index, final UIDL contentUidl) {
  188. StackItem item = getStackItem(index);
  189. if (index != activeTabIndex) {
  190. open(index);
  191. iLayout();
  192. // TODO Check if this is needed
  193. client.runDescendentsLayout(this);
  194. }
  195. item.setContent(contentUidl);
  196. }
  197. public void onSelectTab(StackItem item) {
  198. final int index = getWidgetIndex(item);
  199. if (index != activeTabIndex && !disabled && !readonly
  200. && !disabledTabKeys.contains(tabKeys.get(index))) {
  201. addStyleDependentName("loading");
  202. client.updateVariable(id, "selected", "" + tabKeys.get(index), true);
  203. }
  204. }
  205. @Override
  206. public void setWidth(String width) {
  207. if (this.width.equals(width)) {
  208. return;
  209. }
  210. Util.setWidthExcludingPaddingAndBorder(this, width, 2);
  211. this.width = width;
  212. if (!rendering) {
  213. updateOpenTabSize();
  214. if (isDynamicHeight()) {
  215. Util.updateRelativeChildrenAndSendSizeUpdateEvent(client,
  216. openTab, this);
  217. updateOpenTabSize();
  218. }
  219. if (isDynamicHeight()) {
  220. openTab.setHeightFromWidget();
  221. }
  222. iLayout();
  223. }
  224. }
  225. @Override
  226. public void setHeight(String height) {
  227. Util.setHeightExcludingPaddingAndBorder(this, height, 2);
  228. this.height = height;
  229. if (!rendering) {
  230. updateOpenTabSize();
  231. }
  232. }
  233. /**
  234. * Sets the size of the open tab
  235. */
  236. private void updateOpenTabSize() {
  237. if (openTab == null) {
  238. renderSpace.setHeight(0);
  239. renderSpace.setWidth(0);
  240. return;
  241. }
  242. // WIDTH
  243. if (!isDynamicWidth()) {
  244. int w = getOffsetWidth();
  245. openTab.setWidth(w);
  246. renderSpace.setWidth(w);
  247. } else {
  248. renderSpace.setWidth(0);
  249. }
  250. // HEIGHT
  251. if (!isDynamicHeight()) {
  252. int usedPixels = 0;
  253. for (Widget w : getChildren()) {
  254. StackItem item = (StackItem) w;
  255. if (item == openTab) {
  256. usedPixels += item.getCaptionHeight();
  257. } else {
  258. // This includes the captionNode borders
  259. usedPixels += item.getHeight();
  260. }
  261. }
  262. int offsetHeight = getOffsetHeight();
  263. int spaceForOpenItem = offsetHeight - usedPixels;
  264. if (spaceForOpenItem < 0) {
  265. spaceForOpenItem = 0;
  266. }
  267. renderSpace.setHeight(spaceForOpenItem);
  268. openTab.setHeight(spaceForOpenItem);
  269. } else {
  270. renderSpace.setHeight(0);
  271. openTab.setHeightFromWidget();
  272. }
  273. }
  274. public void iLayout() {
  275. if (openTab == null) {
  276. return;
  277. }
  278. if (isDynamicWidth()) {
  279. int maxWidth = 40;
  280. for (Widget w : getChildren()) {
  281. StackItem si = (StackItem) w;
  282. int captionWidth = si.getCaptionWidth();
  283. if (captionWidth > maxWidth) {
  284. maxWidth = captionWidth;
  285. }
  286. }
  287. int widgetWidth = openTab.getWidgetWidth();
  288. if (widgetWidth > maxWidth) {
  289. maxWidth = widgetWidth;
  290. }
  291. super.setWidth(maxWidth + "px");
  292. openTab.setWidth(maxWidth);
  293. }
  294. Util.runWebkitOverflowAutoFix(openTab.getContainerElement());
  295. }
  296. /**
  297. *
  298. */
  299. protected class StackItem extends ComplexPanel implements ClickHandler {
  300. public void setHeight(int height) {
  301. if (height == -1) {
  302. super.setHeight("");
  303. DOM.setStyleAttribute(content, "height", "0px");
  304. } else {
  305. super.setHeight((height + getCaptionHeight()) + "px");
  306. DOM.setStyleAttribute(content, "height", height + "px");
  307. DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
  308. }
  309. }
  310. public Widget getComponent() {
  311. if (getWidgetCount() < 2) {
  312. return null;
  313. }
  314. return getWidget(1);
  315. }
  316. @Override
  317. public void setVisible(boolean visible) {
  318. super.setVisible(visible);
  319. }
  320. public void setHeightFromWidget() {
  321. Widget widget = getChildWidget();
  322. if (widget == null) {
  323. return;
  324. }
  325. int paintableHeight = widget.getElement().getOffsetHeight();
  326. setHeight(paintableHeight);
  327. }
  328. /**
  329. * Returns caption width including padding
  330. *
  331. * @return
  332. */
  333. public int getCaptionWidth() {
  334. if (caption == null) {
  335. return 0;
  336. }
  337. int captionWidth = caption.getRequiredWidth();
  338. int padding = Util.measureHorizontalPaddingAndBorder(
  339. caption.getElement(), 18);
  340. return captionWidth + padding;
  341. }
  342. public void setWidth(int width) {
  343. if (width == -1) {
  344. super.setWidth("");
  345. } else {
  346. super.setWidth(width + "px");
  347. }
  348. }
  349. public int getHeight() {
  350. return getOffsetHeight();
  351. }
  352. public int getCaptionHeight() {
  353. return captionNode.getOffsetHeight();
  354. }
  355. private VCaption caption;
  356. private boolean open = false;
  357. private Element content = DOM.createDiv();
  358. private Element captionNode = DOM.createDiv();
  359. public StackItem(UIDL tabUidl) {
  360. setElement(DOM.createDiv());
  361. caption = new VCaption(null, client);
  362. caption.addClickHandler(this);
  363. super.add(caption, captionNode);
  364. DOM.appendChild(captionNode, caption.getElement());
  365. DOM.appendChild(getElement(), captionNode);
  366. DOM.appendChild(getElement(), content);
  367. setStyleName(CLASSNAME + "-item");
  368. DOM.setElementProperty(content, "className", CLASSNAME
  369. + "-item-content");
  370. DOM.setElementProperty(captionNode, "className", CLASSNAME
  371. + "-item-caption");
  372. close();
  373. }
  374. @Override
  375. public void onBrowserEvent(Event event) {
  376. onSelectTab(this);
  377. }
  378. public Element getContainerElement() {
  379. return content;
  380. }
  381. public Widget getChildWidget() {
  382. if (getWidgetCount() > 1) {
  383. return getWidget(1);
  384. } else {
  385. return null;
  386. }
  387. }
  388. public void replaceWidget(Widget newWidget) {
  389. if (getWidgetCount() > 1) {
  390. Widget oldWidget = getWidget(1);
  391. VPaintableWidget oldPaintable = VPaintableMap.get(client)
  392. .getPaintable(oldWidget);
  393. VPaintableMap.get(client).unregisterPaintable(oldPaintable);
  394. widgets.remove(oldWidget);
  395. remove(1);
  396. }
  397. add(newWidget, content);
  398. widgets.add(newWidget);
  399. }
  400. public void open() {
  401. open = true;
  402. DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
  403. DOM.setStyleAttribute(content, "left", "0px");
  404. DOM.setStyleAttribute(content, "visibility", "");
  405. addStyleDependentName("open");
  406. }
  407. public void hide() {
  408. DOM.setStyleAttribute(content, "visibility", "hidden");
  409. }
  410. public void close() {
  411. DOM.setStyleAttribute(content, "visibility", "hidden");
  412. DOM.setStyleAttribute(content, "top", "-100000px");
  413. DOM.setStyleAttribute(content, "left", "-100000px");
  414. removeStyleDependentName("open");
  415. setHeight(-1);
  416. setWidth("");
  417. open = false;
  418. }
  419. public boolean isOpen() {
  420. return open;
  421. }
  422. public void setContent(UIDL contentUidl) {
  423. final VPaintableWidget newPntbl = client.getPaintable(contentUidl);
  424. Widget newWidget = newPntbl.getWidgetForPaintable();
  425. if (getChildWidget() == null) {
  426. add(newWidget, content);
  427. widgets.add(newWidget);
  428. } else if (getChildWidget() != newWidget) {
  429. replaceWidget(newWidget);
  430. }
  431. newPntbl.updateFromUIDL(contentUidl, client);
  432. if (contentUidl.getBooleanAttribute("cached")) {
  433. /*
  434. * The size of a cached, relative sized component must be
  435. * updated to report correct size.
  436. */
  437. client.handleComponentRelativeSize(newPntbl
  438. .getWidgetForPaintable());
  439. }
  440. if (isOpen() && isDynamicHeight()) {
  441. setHeightFromWidget();
  442. }
  443. }
  444. public void onClick(ClickEvent event) {
  445. onSelectTab(this);
  446. }
  447. public void updateCaption(UIDL uidl) {
  448. caption.updateCaption(uidl);
  449. }
  450. public int getWidgetWidth() {
  451. return DOM.getFirstChild(content).getOffsetWidth();
  452. }
  453. public boolean contains(VPaintableWidget p) {
  454. return (getChildWidget() == p.getWidgetForPaintable());
  455. }
  456. public boolean isCaptionVisible() {
  457. return caption.isVisible();
  458. }
  459. }
  460. @Override
  461. protected void clearPaintables() {
  462. clear();
  463. }
  464. public boolean isDynamicHeight() {
  465. return height == null || height.equals("");
  466. }
  467. public boolean isDynamicWidth() {
  468. return width == null || width.equals("");
  469. }
  470. @Override
  471. @SuppressWarnings("unchecked")
  472. protected Iterator<Widget> getWidgetIterator() {
  473. return widgets.iterator();
  474. }
  475. public boolean hasChildComponent(Widget component) {
  476. for (Widget w : widgets) {
  477. if (w == component) {
  478. return true;
  479. }
  480. }
  481. return false;
  482. }
  483. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  484. for (Widget w : getChildren()) {
  485. StackItem item = (StackItem) w;
  486. if (item.getChildWidget() == oldComponent) {
  487. item.replaceWidget(newComponent);
  488. return;
  489. }
  490. }
  491. }
  492. public void updateCaption(VPaintableWidget component, UIDL uidl) {
  493. /* Accordion does not render its children's captions */
  494. }
  495. public boolean requestLayout(Set<Widget> children) {
  496. if (!isDynamicHeight() && !isDynamicWidth()) {
  497. /*
  498. * If the height and width has been specified for this container the
  499. * child components cannot make the size of the layout change
  500. */
  501. // layout size change may affect its available space (scrollbars)
  502. for (Widget widget : children) {
  503. client.handleComponentRelativeSize(widget);
  504. }
  505. return true;
  506. }
  507. updateOpenTabSize();
  508. if (renderInformation.updateSize(getElement())) {
  509. /*
  510. * Size has changed so we let the child components know about the
  511. * new size.
  512. */
  513. iLayout();
  514. // TODO Check if this is needed
  515. client.runDescendentsLayout(this);
  516. return false;
  517. } else {
  518. /*
  519. * Size has not changed so we do not need to propagate the event
  520. * further
  521. */
  522. return true;
  523. }
  524. }
  525. public RenderSpace getAllocatedSpace(Widget child) {
  526. return renderSpace;
  527. }
  528. @Override
  529. protected int getTabCount() {
  530. return getWidgetCount();
  531. }
  532. @Override
  533. protected void removeTab(int index) {
  534. StackItem item = getStackItem(index);
  535. remove(item);
  536. }
  537. @Override
  538. protected VPaintableWidget getTab(int index) {
  539. if (index < getWidgetCount()) {
  540. Widget w = getStackItem(index);
  541. return VPaintableMap.get(client).getPaintable(w);
  542. }
  543. return null;
  544. }
  545. private StackItem getStackItem(int index) {
  546. return (StackItem) getWidget(index);
  547. }
  548. public Widget getWidgetForPaintable() {
  549. return this;
  550. }
  551. }