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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  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.HashMap;
  18. import java.util.HashSet;
  19. import java.util.Iterator;
  20. import java.util.Set;
  21. import com.google.gwt.event.dom.client.ClickEvent;
  22. import com.google.gwt.event.dom.client.ClickHandler;
  23. import com.google.gwt.user.client.DOM;
  24. import com.google.gwt.user.client.Event;
  25. import com.google.gwt.user.client.ui.ComplexPanel;
  26. import com.google.gwt.user.client.ui.Widget;
  27. import com.vaadin.client.ComponentConnector;
  28. import com.vaadin.client.ConnectorMap;
  29. import com.vaadin.client.UIDL;
  30. import com.vaadin.client.Util;
  31. import com.vaadin.client.VCaption;
  32. import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
  33. import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
  34. import com.vaadin.shared.ui.tabsheet.TabsheetConstants;
  35. public class VAccordion extends VTabsheetBase {
  36. public static final String CLASSNAME = "v-accordion";
  37. private Set<Widget> widgets = new HashSet<Widget>();
  38. /** For internal use only. May be removed or replaced in the future. */
  39. public HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>();
  40. private StackItem openTab = null;
  41. /** For internal use only. May be removed or replaced in the future. */
  42. public int selectedUIDLItemIndex = -1;
  43. private final TouchScrollHandler touchScrollHandler;
  44. public VAccordion() {
  45. super(CLASSNAME);
  46. touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
  47. }
  48. @Override
  49. public void renderTab(UIDL tabUidl, int index, boolean selected,
  50. boolean hidden) {
  51. StackItem item;
  52. int itemIndex;
  53. if (getWidgetCount() <= index) {
  54. // Create stackItem and render caption
  55. item = new StackItem(tabUidl);
  56. if (getWidgetCount() == 0) {
  57. item.addStyleDependentName("first");
  58. }
  59. itemIndex = getWidgetCount();
  60. add(item, getElement());
  61. } else {
  62. item = getStackItem(index);
  63. item = moveStackItemIfNeeded(item, index, tabUidl);
  64. itemIndex = index;
  65. }
  66. item.updateCaption(tabUidl);
  67. item.updateTabStyleName(tabUidl
  68. .getStringAttribute(TabsheetConstants.TAB_STYLE_NAME));
  69. item.setVisible(!hidden);
  70. if (selected) {
  71. selectedUIDLItemIndex = itemIndex;
  72. }
  73. if (tabUidl.getChildCount() > 0) {
  74. lazyUpdateMap.put(item, tabUidl.getChildUIDL(0));
  75. }
  76. }
  77. @Override
  78. public void setStylePrimaryName(String style) {
  79. super.setStylePrimaryName(style);
  80. updateStyleNames(style);
  81. }
  82. @Override
  83. public void setStyleName(String style) {
  84. super.setStyleName(style);
  85. updateStyleNames(style);
  86. }
  87. protected void updateStyleNames(String primaryStyleName) {
  88. for (Widget w : getChildren()) {
  89. if (w instanceof StackItem) {
  90. StackItem item = (StackItem) w;
  91. item.updateStyleNames(primaryStyleName);
  92. }
  93. }
  94. }
  95. /**
  96. * This method tries to find out if a tab has been rendered with a different
  97. * index previously. If this is the case it re-orders the children so the
  98. * same StackItem is used for rendering this time. E.g. if the first tab has
  99. * been removed all tabs which contain cached content must be moved 1 step
  100. * up to preserve the cached content.
  101. *
  102. * @param item
  103. * @param newIndex
  104. * @param tabUidl
  105. * @return
  106. */
  107. private StackItem moveStackItemIfNeeded(StackItem item, int newIndex,
  108. UIDL tabUidl) {
  109. UIDL tabContentUIDL = null;
  110. ComponentConnector tabContent = null;
  111. if (tabUidl.getChildCount() > 0) {
  112. tabContentUIDL = tabUidl.getChildUIDL(0);
  113. tabContent = client.getPaintable(tabContentUIDL);
  114. }
  115. Widget itemWidget = item.getComponent();
  116. if (tabContent != null) {
  117. if (tabContent.getWidget() != itemWidget) {
  118. /*
  119. * This is not the same widget as before, find out if it has
  120. * been moved
  121. */
  122. int oldIndex = -1;
  123. StackItem oldItem = null;
  124. for (int i = 0; i < getWidgetCount(); i++) {
  125. Widget w = getWidget(i);
  126. oldItem = (StackItem) w;
  127. if (tabContent == oldItem.getComponent()) {
  128. oldIndex = i;
  129. break;
  130. }
  131. }
  132. if (oldIndex != -1 && oldIndex > newIndex) {
  133. /*
  134. * The tab has previously been rendered in another position
  135. * so we must move the cached content to correct position.
  136. * We move only items with oldIndex > newIndex to prevent
  137. * moving items already rendered in this update. If for
  138. * instance tabs 1,2,3 are removed and added as 3,2,1 we
  139. * cannot re-use "1" when we get to the third tab.
  140. */
  141. insert(oldItem, getElement(), newIndex, true);
  142. return oldItem;
  143. }
  144. }
  145. } else {
  146. // Tab which has never been loaded. Must assure we use an empty
  147. // StackItem
  148. Widget oldWidget = item.getComponent();
  149. if (oldWidget != null) {
  150. oldWidget.removeFromParent();
  151. }
  152. }
  153. return item;
  154. }
  155. /** For internal use only. May be removed or replaced in the future. */
  156. public void open(int itemIndex) {
  157. StackItem item = (StackItem) getWidget(itemIndex);
  158. boolean alreadyOpen = false;
  159. if (openTab != null) {
  160. if (openTab.isOpen()) {
  161. if (openTab == item) {
  162. alreadyOpen = true;
  163. } else {
  164. openTab.close();
  165. }
  166. }
  167. }
  168. if (!alreadyOpen) {
  169. item.open();
  170. activeTabIndex = itemIndex;
  171. openTab = item;
  172. }
  173. }
  174. /** For internal use only. May be removed or replaced in the future. */
  175. public void close(StackItem item) {
  176. if (!item.isOpen()) {
  177. return;
  178. }
  179. item.close();
  180. activeTabIndex = -1;
  181. openTab = null;
  182. }
  183. public void onSelectTab(StackItem item) {
  184. final int index = getWidgetIndex(item);
  185. if (index != activeTabIndex && !disabled && !readonly
  186. && !disabledTabKeys.contains(tabKeys.get(index))) {
  187. addStyleDependentName("loading");
  188. client.updateVariable(id, "selected", "" + tabKeys.get(index), true);
  189. }
  190. }
  191. /**
  192. * A StackItem has always two children, Child 0 is a VCaption, Child 1 is
  193. * the actual child widget.
  194. */
  195. public class StackItem extends ComplexPanel implements ClickHandler {
  196. public void setHeight(int height) {
  197. if (height == -1) {
  198. super.setHeight("");
  199. DOM.setStyleAttribute(content, "height", "0px");
  200. } else {
  201. super.setHeight((height + getCaptionHeight()) + "px");
  202. DOM.setStyleAttribute(content, "height", height + "px");
  203. DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
  204. }
  205. }
  206. public Widget getComponent() {
  207. if (getWidgetCount() < 2) {
  208. return null;
  209. }
  210. return getWidget(1);
  211. }
  212. @Override
  213. public void setVisible(boolean visible) {
  214. super.setVisible(visible);
  215. }
  216. public void setHeightFromWidget() {
  217. Widget widget = getChildWidget();
  218. if (widget == null) {
  219. return;
  220. }
  221. int paintableHeight = widget.getElement().getOffsetHeight();
  222. setHeight(paintableHeight);
  223. }
  224. /**
  225. * Returns caption width including padding
  226. *
  227. * @return
  228. */
  229. public int getCaptionWidth() {
  230. if (caption == null) {
  231. return 0;
  232. }
  233. int captionWidth = caption.getRequiredWidth();
  234. int padding = Util.measureHorizontalPaddingAndBorder(
  235. caption.getElement(), 18);
  236. return captionWidth + padding;
  237. }
  238. public void setWidth(int width) {
  239. if (width == -1) {
  240. super.setWidth("");
  241. } else {
  242. super.setWidth(width + "px");
  243. }
  244. }
  245. public int getHeight() {
  246. return getOffsetHeight();
  247. }
  248. public int getCaptionHeight() {
  249. return captionNode.getOffsetHeight();
  250. }
  251. private VCaption caption;
  252. private boolean open = false;
  253. private com.google.gwt.user.client.Element content = DOM.createDiv();
  254. private com.google.gwt.user.client.Element captionNode = DOM
  255. .createDiv();
  256. private String styleName;
  257. public StackItem(UIDL tabUidl) {
  258. setElement(DOM.createDiv());
  259. caption = new VCaption(client);
  260. caption.addClickHandler(this);
  261. super.add(caption, captionNode);
  262. DOM.appendChild(captionNode, caption.getElement());
  263. DOM.appendChild(getElement(), captionNode);
  264. DOM.appendChild(getElement(), content);
  265. updateStyleNames(VAccordion.this.getStylePrimaryName());
  266. touchScrollHandler.addElement(getContainerElement());
  267. close();
  268. }
  269. private void updateStyleNames(String primaryStyleName) {
  270. content.removeClassName(getStylePrimaryName() + "-content");
  271. captionNode.removeClassName(getStylePrimaryName() + "-caption");
  272. setStylePrimaryName(primaryStyleName + "-item");
  273. updateTabStyleName(getStylePrimaryName());
  274. captionNode.addClassName(getStylePrimaryName() + "-caption");
  275. content.addClassName(getStylePrimaryName() + "-content");
  276. }
  277. @Override
  278. public void onBrowserEvent(Event event) {
  279. onSelectTab(this);
  280. }
  281. public com.google.gwt.user.client.Element getContainerElement() {
  282. return content;
  283. }
  284. public Widget getChildWidget() {
  285. if (getWidgetCount() > 1) {
  286. return getWidget(1);
  287. } else {
  288. return null;
  289. }
  290. }
  291. public void replaceWidget(Widget newWidget) {
  292. if (getWidgetCount() > 1) {
  293. Widget oldWidget = getWidget(1);
  294. widgets.remove(oldWidget);
  295. }
  296. add(newWidget, content);
  297. widgets.add(newWidget);
  298. }
  299. public void open() {
  300. open = true;
  301. DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
  302. DOM.setStyleAttribute(content, "left", "0px");
  303. DOM.setStyleAttribute(content, "visibility", "");
  304. addStyleDependentName("open");
  305. }
  306. public void hide() {
  307. DOM.setStyleAttribute(content, "visibility", "hidden");
  308. }
  309. public void close() {
  310. DOM.setStyleAttribute(content, "visibility", "hidden");
  311. DOM.setStyleAttribute(content, "top", "-100000px");
  312. DOM.setStyleAttribute(content, "left", "-100000px");
  313. removeStyleDependentName("open");
  314. setHeight(-1);
  315. setWidth("");
  316. open = false;
  317. }
  318. public boolean isOpen() {
  319. return open;
  320. }
  321. public void setContent(UIDL contentUidl) {
  322. final ComponentConnector newPntbl = client
  323. .getPaintable(contentUidl);
  324. Widget newWidget = newPntbl.getWidget();
  325. if (getChildWidget() == null) {
  326. add(newWidget, content);
  327. widgets.add(newWidget);
  328. } else if (getChildWidget() != newWidget) {
  329. replaceWidget(newWidget);
  330. }
  331. if (isOpen() && isDynamicHeight()) {
  332. setHeightFromWidget();
  333. }
  334. }
  335. @Override
  336. public void onClick(ClickEvent event) {
  337. onSelectTab(this);
  338. }
  339. public void updateCaption(UIDL uidl) {
  340. // TODO need to call this because the caption does not have an owner
  341. caption.updateCaptionWithoutOwner(
  342. uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION),
  343. uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
  344. uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
  345. uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
  346. uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON));
  347. }
  348. /**
  349. * Updates a tabs stylename from the child UIDL
  350. *
  351. * @param uidl
  352. * The child uidl of the tab
  353. */
  354. private void updateTabStyleName(String newStyleName) {
  355. if (newStyleName != null && newStyleName.length() != 0) {
  356. if (!newStyleName.equals(styleName)) {
  357. // If we have a new style name
  358. if (styleName != null && styleName.length() != 0) {
  359. // Remove old style name if present
  360. removeStyleDependentName(styleName);
  361. }
  362. // Set new style name
  363. addStyleDependentName(newStyleName);
  364. styleName = newStyleName;
  365. }
  366. } else if (styleName != null) {
  367. // Remove the set stylename if no stylename is present in the
  368. // uidl
  369. removeStyleDependentName(styleName);
  370. styleName = null;
  371. }
  372. }
  373. public int getWidgetWidth() {
  374. return DOM.getFirstChild(content).getOffsetWidth();
  375. }
  376. public boolean contains(ComponentConnector p) {
  377. return (getChildWidget() == p.getWidget());
  378. }
  379. public boolean isCaptionVisible() {
  380. return caption.isVisible();
  381. }
  382. }
  383. @Override
  384. protected void clearPaintables() {
  385. clear();
  386. }
  387. boolean isDynamicWidth() {
  388. ComponentConnector paintable = ConnectorMap.get(client).getConnector(
  389. this);
  390. return paintable.isUndefinedWidth();
  391. }
  392. boolean isDynamicHeight() {
  393. ComponentConnector paintable = ConnectorMap.get(client).getConnector(
  394. this);
  395. return paintable.isUndefinedHeight();
  396. }
  397. @Override
  398. public Iterator<Widget> getWidgetIterator() {
  399. return widgets.iterator();
  400. }
  401. @Override
  402. public int getTabCount() {
  403. return getWidgetCount();
  404. }
  405. @Override
  406. public void removeTab(int index) {
  407. StackItem item = getStackItem(index);
  408. remove(item);
  409. touchScrollHandler.removeElement(item.getContainerElement());
  410. }
  411. @Override
  412. public ComponentConnector getTab(int index) {
  413. if (index < getWidgetCount()) {
  414. StackItem stackItem = getStackItem(index);
  415. if (stackItem == null) {
  416. return null;
  417. }
  418. Widget w = stackItem.getChildWidget();
  419. if (w != null) {
  420. return ConnectorMap.get(client).getConnector(w);
  421. }
  422. }
  423. return null;
  424. }
  425. /** For internal use only. May be removed or replaced in the future. */
  426. public StackItem getStackItem(int index) {
  427. return (StackItem) getWidget(index);
  428. }
  429. public Iterable<StackItem> getStackItems() {
  430. return (Iterable) getChildren();
  431. }
  432. public StackItem getOpenStackItem() {
  433. return openTab;
  434. }
  435. }