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

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