Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

VAccordion.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  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.updateVariable(id, "selected", "" + tabKeys.get(index), true);
  207. }
  208. }
  209. @Override
  210. public void setWidth(String width) {
  211. if (this.width.equals(width)) {
  212. return;
  213. }
  214. Util.setWidthExcludingPaddingAndBorder(this, width, 2);
  215. this.width = width;
  216. if (!rendering) {
  217. updateOpenTabSize();
  218. if (isDynamicHeight()) {
  219. Util.updateRelativeChildrenAndSendSizeUpdateEvent(client,
  220. openTab, this);
  221. updateOpenTabSize();
  222. }
  223. if (isDynamicHeight()) {
  224. openTab.setHeightFromWidget();
  225. }
  226. iLayout();
  227. }
  228. }
  229. @Override
  230. public void setHeight(String height) {
  231. Util.setHeightExcludingPaddingAndBorder(this, height, 2);
  232. this.height = height;
  233. if (!rendering) {
  234. updateOpenTabSize();
  235. }
  236. }
  237. /**
  238. * Sets the size of the open tab
  239. */
  240. private void updateOpenTabSize() {
  241. if (openTab == null) {
  242. renderSpace.setHeight(0);
  243. renderSpace.setWidth(0);
  244. return;
  245. }
  246. // WIDTH
  247. if (!isDynamicWidth()) {
  248. int w = getOffsetWidth();
  249. openTab.setWidth(w);
  250. renderSpace.setWidth(w);
  251. } else {
  252. renderSpace.setWidth(0);
  253. }
  254. // HEIGHT
  255. if (!isDynamicHeight()) {
  256. int usedPixels = 0;
  257. for (Widget w : getChildren()) {
  258. StackItem item = (StackItem) w;
  259. if (item == openTab) {
  260. usedPixels += item.getCaptionHeight();
  261. } else {
  262. // This includes the captionNode borders
  263. usedPixels += item.getHeight();
  264. }
  265. }
  266. int offsetHeight = getOffsetHeight();
  267. int spaceForOpenItem = offsetHeight - usedPixels;
  268. if (spaceForOpenItem < 0) {
  269. spaceForOpenItem = 0;
  270. }
  271. renderSpace.setHeight(spaceForOpenItem);
  272. openTab.setHeight(spaceForOpenItem);
  273. } else {
  274. renderSpace.setHeight(0);
  275. openTab.setHeightFromWidget();
  276. }
  277. }
  278. public void iLayout() {
  279. if (openTab == null) {
  280. return;
  281. }
  282. if (isDynamicWidth()) {
  283. int maxWidth = 40;
  284. for (Widget w : getChildren()) {
  285. StackItem si = (StackItem) w;
  286. int captionWidth = si.getCaptionWidth();
  287. if (captionWidth > maxWidth) {
  288. maxWidth = captionWidth;
  289. }
  290. }
  291. int widgetWidth = openTab.getWidgetWidth();
  292. if (widgetWidth > maxWidth) {
  293. maxWidth = widgetWidth;
  294. }
  295. super.setWidth(maxWidth + "px");
  296. openTab.setWidth(maxWidth);
  297. }
  298. Util.runWebkitOverflowAutoFix(openTab.getContainerElement());
  299. }
  300. /**
  301. *
  302. */
  303. protected class StackItem extends ComplexPanel implements ClickHandler {
  304. public void setHeight(int height) {
  305. if (height == -1) {
  306. super.setHeight("");
  307. DOM.setStyleAttribute(content, "height", "0px");
  308. } else {
  309. super.setHeight((height + getCaptionHeight()) + "px");
  310. DOM.setStyleAttribute(content, "height", height + "px");
  311. DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
  312. }
  313. }
  314. public Widget getComponent() {
  315. if (getWidgetCount() < 2) {
  316. return null;
  317. }
  318. return getWidget(1);
  319. }
  320. @Override
  321. public void setVisible(boolean visible) {
  322. super.setVisible(visible);
  323. }
  324. public void setHeightFromWidget() {
  325. Widget paintable = getPaintable();
  326. if (paintable == null) {
  327. return;
  328. }
  329. int paintableHeight = (paintable).getElement().getOffsetHeight();
  330. setHeight(paintableHeight);
  331. }
  332. /**
  333. * Returns caption width including padding
  334. *
  335. * @return
  336. */
  337. public int getCaptionWidth() {
  338. if (caption == null) {
  339. return 0;
  340. }
  341. int captionWidth = caption.getRequiredWidth();
  342. int padding = Util.measureHorizontalPaddingAndBorder(
  343. caption.getElement(), 18);
  344. return captionWidth + padding;
  345. }
  346. public void setWidth(int width) {
  347. if (width == -1) {
  348. super.setWidth("");
  349. } else {
  350. super.setWidth(width + "px");
  351. }
  352. }
  353. public int getHeight() {
  354. return getOffsetHeight();
  355. }
  356. public int getCaptionHeight() {
  357. return captionNode.getOffsetHeight();
  358. }
  359. private VCaption caption;
  360. private boolean open = false;
  361. private Element content = DOM.createDiv();
  362. private Element captionNode = DOM.createDiv();
  363. public StackItem(UIDL tabUidl) {
  364. setElement(DOM.createDiv());
  365. caption = new VCaption(null, client);
  366. caption.addClickHandler(this);
  367. if (BrowserInfo.get().isIE6()) {
  368. DOM.setEventListener(captionNode, this);
  369. DOM.sinkEvents(captionNode, Event.BUTTON_LEFT);
  370. }
  371. super.add(caption, captionNode);
  372. DOM.appendChild(captionNode, caption.getElement());
  373. DOM.appendChild(getElement(), captionNode);
  374. DOM.appendChild(getElement(), content);
  375. setStyleName(CLASSNAME + "-item");
  376. DOM.setElementProperty(content, "className", CLASSNAME
  377. + "-item-content");
  378. DOM.setElementProperty(captionNode, "className", CLASSNAME
  379. + "-item-caption");
  380. close();
  381. }
  382. @Override
  383. public void onBrowserEvent(Event event) {
  384. onSelectTab(this);
  385. }
  386. public Element getContainerElement() {
  387. return content;
  388. }
  389. public Widget getPaintable() {
  390. if (getWidgetCount() > 1) {
  391. return getWidget(1);
  392. } else {
  393. return null;
  394. }
  395. }
  396. public void replacePaintable(Paintable newPntbl) {
  397. if (getWidgetCount() > 1) {
  398. client.unregisterPaintable((Paintable) getWidget(1));
  399. paintables.remove(getWidget(1));
  400. remove(1);
  401. }
  402. add((Widget) newPntbl, content);
  403. paintables.add(newPntbl);
  404. }
  405. public void open() {
  406. open = true;
  407. DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
  408. DOM.setStyleAttribute(content, "left", "0px");
  409. DOM.setStyleAttribute(content, "visibility", "");
  410. addStyleDependentName("open");
  411. }
  412. public void hide() {
  413. DOM.setStyleAttribute(content, "visibility", "hidden");
  414. }
  415. public void close() {
  416. DOM.setStyleAttribute(content, "visibility", "hidden");
  417. DOM.setStyleAttribute(content, "top", "-100000px");
  418. DOM.setStyleAttribute(content, "left", "-100000px");
  419. removeStyleDependentName("open");
  420. setHeight(-1);
  421. setWidth("");
  422. if (BrowserInfo.get().isIE6()) {
  423. // Work around for IE6 layouting problem #3359
  424. getElement().getStyle().setProperty("zoom", "1");
  425. }
  426. open = false;
  427. }
  428. public boolean isOpen() {
  429. return open;
  430. }
  431. public void setContent(UIDL contentUidl) {
  432. final Paintable newPntbl = client.getPaintable(contentUidl);
  433. if (getPaintable() == null) {
  434. add((Widget) newPntbl, content);
  435. paintables.add(newPntbl);
  436. } else if (getPaintable() != newPntbl) {
  437. replacePaintable(newPntbl);
  438. }
  439. newPntbl.updateFromUIDL(contentUidl, client);
  440. if (contentUidl.getBooleanAttribute("cached")) {
  441. /*
  442. * The size of a cached, relative sized component must be
  443. * updated to report correct size.
  444. */
  445. client.handleComponentRelativeSize((Widget) newPntbl);
  446. }
  447. if (isOpen() && isDynamicHeight()) {
  448. setHeightFromWidget();
  449. }
  450. }
  451. public void onClick(ClickEvent event) {
  452. onSelectTab(this);
  453. }
  454. public void updateCaption(UIDL uidl) {
  455. caption.updateCaption(uidl);
  456. }
  457. public int getWidgetWidth() {
  458. return DOM.getFirstChild(content).getOffsetWidth();
  459. }
  460. public boolean contains(Paintable p) {
  461. return (getPaintable() == p);
  462. }
  463. public boolean isCaptionVisible() {
  464. return caption.isVisible();
  465. }
  466. }
  467. @Override
  468. protected void clearPaintables() {
  469. clear();
  470. }
  471. public boolean isDynamicHeight() {
  472. return height == null || height.equals("");
  473. }
  474. public boolean isDynamicWidth() {
  475. return width == null || width.equals("");
  476. }
  477. @Override
  478. protected Iterator getPaintableIterator() {
  479. return paintables.iterator();
  480. }
  481. public boolean hasChildComponent(Widget component) {
  482. if (paintables.contains(component)) {
  483. return true;
  484. } else {
  485. return false;
  486. }
  487. }
  488. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  489. for (Widget w : getChildren()) {
  490. StackItem item = (StackItem) w;
  491. if (item.getPaintable() == oldComponent) {
  492. item.replacePaintable((Paintable) newComponent);
  493. return;
  494. }
  495. }
  496. }
  497. public void updateCaption(Paintable component, UIDL uidl) {
  498. /* Accordion does not render its children's captions */
  499. }
  500. public boolean requestLayout(Set<Paintable> child) {
  501. if (!isDynamicHeight() && !isDynamicWidth()) {
  502. /*
  503. * If the height and width has been specified for this container the
  504. * child components cannot make the size of the layout change
  505. */
  506. // layout size change may affect its available space (scrollbars)
  507. for (Paintable paintable : child) {
  508. client.handleComponentRelativeSize((Widget) paintable);
  509. }
  510. return true;
  511. }
  512. updateOpenTabSize();
  513. if (renderInformation.updateSize(getElement())) {
  514. /*
  515. * Size has changed so we let the child components know about the
  516. * new size.
  517. */
  518. iLayout();
  519. // TODO Check if this is needed
  520. client.runDescendentsLayout(this);
  521. return false;
  522. } else {
  523. /*
  524. * Size has not changed so we do not need to propagate the event
  525. * further
  526. */
  527. return true;
  528. }
  529. }
  530. public RenderSpace getAllocatedSpace(Widget child) {
  531. return renderSpace;
  532. }
  533. @Override
  534. protected int getTabCount() {
  535. return getWidgetCount();
  536. }
  537. @Override
  538. protected void removeTab(int index) {
  539. StackItem item = getStackItem(index);
  540. remove(item);
  541. }
  542. @Override
  543. protected Paintable getTab(int index) {
  544. if (index < getWidgetCount()) {
  545. return (Paintable) (getStackItem(index)).getPaintable();
  546. }
  547. return null;
  548. }
  549. private StackItem getStackItem(int index) {
  550. return (StackItem) getWidget(index);
  551. }
  552. }