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

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