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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. /*
  2. * Copyright 2000-2021 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.HashSet;
  18. import java.util.Iterator;
  19. import java.util.Set;
  20. import com.google.gwt.dom.client.Element;
  21. import com.google.gwt.dom.client.Style.Unit;
  22. import com.google.gwt.dom.client.Style.Visibility;
  23. import com.google.gwt.event.dom.client.ClickEvent;
  24. import com.google.gwt.event.dom.client.ClickHandler;
  25. import com.google.gwt.user.client.DOM;
  26. import com.google.gwt.user.client.Event;
  27. import com.google.gwt.user.client.ui.ComplexPanel;
  28. import com.google.gwt.user.client.ui.Widget;
  29. import com.vaadin.client.ComponentConnector;
  30. import com.vaadin.client.VCaption;
  31. import com.vaadin.client.WidgetUtil;
  32. import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
  33. import com.vaadin.client.ui.VAccordion.StackItem;
  34. import com.vaadin.shared.ComponentConstants;
  35. import com.vaadin.shared.ui.accordion.AccordionState;
  36. import com.vaadin.shared.ui.tabsheet.TabState;
  37. import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc;
  38. import com.vaadin.shared.util.SharedUtil;
  39. /**
  40. * Widget class for the Accordion component. Displays one child item's contents
  41. * at a time.
  42. *
  43. * @author Vaadin Ltd
  44. *
  45. */
  46. public class VAccordion extends VTabsheetBase {
  47. /** Default classname for this widget. */
  48. public static final String CLASSNAME = AccordionState.PRIMARY_STYLE_NAME;
  49. private Set<Widget> widgets = new HashSet<>();
  50. private StackItem openTab;
  51. /** For internal use only. May be removed or replaced in the future. */
  52. public int selectedItemIndex = -1;
  53. private final TouchScrollHandler touchScrollHandler;
  54. private int tabulatorIndex;
  55. /**
  56. * Constructs a widget for an Accordion.
  57. */
  58. public VAccordion() {
  59. super(CLASSNAME);
  60. touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
  61. }
  62. @SuppressWarnings("deprecation")
  63. @Override
  64. public void renderTab(TabState tabState, int index) {
  65. StackItem item;
  66. if (getWidgetCount() <= index) {
  67. // Create stackItem and render caption
  68. item = new StackItem();
  69. if (getWidgetCount() == 0) {
  70. item.addStyleDependentName("first");
  71. }
  72. add(item, getElement());
  73. } else {
  74. item = getStackItem(index);
  75. }
  76. item.updateCaption(tabState);
  77. item.updateTabStyleName(tabState.styleName);
  78. item.setVisible(tabState.visible);
  79. item.setId(tabState.id);
  80. }
  81. @Override
  82. public void selectTab(int index) {
  83. selectedItemIndex = index;
  84. }
  85. @Override
  86. public void setStylePrimaryName(String style) {
  87. super.setStylePrimaryName(style);
  88. updateStyleNames(style);
  89. }
  90. @Override
  91. public void setStyleName(String style) {
  92. super.setStyleName(style);
  93. updateStyleNames(style);
  94. }
  95. /**
  96. * Updates the primary style name base for all stack items.
  97. *
  98. * @param primaryStyleName
  99. * the new primary style name base
  100. */
  101. protected void updateStyleNames(String primaryStyleName) {
  102. for (Widget w : getChildren()) {
  103. if (w instanceof StackItem) {
  104. StackItem item = (StackItem) w;
  105. item.updateStyleNames(primaryStyleName);
  106. }
  107. }
  108. }
  109. /**
  110. * For internal use only. May be renamed or removed in a future release.
  111. * <p>
  112. * Sets the tabulator index for the active stack item. The active stack item
  113. * represents the entire accordion in the browser's focus cycle (excluding
  114. * any focusable elements within the content panel).
  115. * <p>
  116. * This value is delegated from the TabsheetState via AccordionState.
  117. *
  118. * @param tabIndex
  119. * tabulator index for the open stack item
  120. * @since 8.1.7
  121. */
  122. public void setTabIndex(int tabIndex) {
  123. tabulatorIndex = tabIndex;
  124. StackItem openStackItem = getOpenStackItem();
  125. if (openStackItem != null) {
  126. openStackItem.getElement().setTabIndex(tabIndex);
  127. }
  128. }
  129. /**
  130. * For internal use only. May be removed or replaced in the future.
  131. *
  132. * @param itemIndex
  133. * the index of the stack item to open
  134. */
  135. public void open(int itemIndex) {
  136. StackItem item = (StackItem) getWidget(itemIndex);
  137. boolean alreadyOpen = false;
  138. if (openTab != null) {
  139. if (openTab.isOpen()) {
  140. if (openTab == item) {
  141. alreadyOpen = true;
  142. } else {
  143. openTab.close();
  144. }
  145. }
  146. }
  147. if (!alreadyOpen) {
  148. item.open();
  149. activeTabIndex = itemIndex;
  150. openTab = item;
  151. }
  152. }
  153. /**
  154. * For internal use only. May be removed or replaced in the future.
  155. *
  156. * @param item
  157. * the stack item to close
  158. */
  159. public void close(StackItem item) {
  160. if (!item.isOpen()) {
  161. return;
  162. }
  163. item.close();
  164. activeTabIndex = -1;
  165. openTab = null;
  166. }
  167. /**
  168. * Handle stack item selection.
  169. *
  170. * @param item
  171. * the selected stack item
  172. */
  173. public void onSelectTab(StackItem item) {
  174. final int index = getWidgetIndex(item);
  175. if (index != activeTabIndex && !disabled && !readonly
  176. && !disabledTabKeys.contains(tabKeys.get(index))) {
  177. addStyleDependentName("loading");
  178. connector.getRpcProxy(TabsheetServerRpc.class)
  179. .setSelected(tabKeys.get(index));
  180. }
  181. }
  182. /**
  183. * A StackItem has always two children, Child 0 is a VCaption, Child 1 is
  184. * the actual child widget.
  185. */
  186. public class StackItem extends ComplexPanel implements ClickHandler {
  187. private Widget widget;
  188. private String id;
  189. /**
  190. * Sets the height for this stack item's contents.
  191. *
  192. * @param height
  193. * the height to set (in pixels), or {@code -1} to remove
  194. * height
  195. */
  196. public void setHeight(int height) {
  197. if (height == -1) {
  198. super.setHeight("");
  199. content.getStyle().setHeight(0, Unit.PX);
  200. } else {
  201. super.setHeight((height + getCaptionHeight()) + "px");
  202. content.getStyle().setHeight(height, Unit.PX);
  203. content.getStyle().setTop(getCaptionHeight(), Unit.PX);
  204. }
  205. }
  206. /**
  207. * Sets the identifier for this stack item.
  208. *
  209. * @param newId
  210. * the identifier to set
  211. */
  212. public void setId(String newId) {
  213. if (!SharedUtil.equals(newId, id)) {
  214. if (id != null) {
  215. getElement().removeAttribute("id");
  216. }
  217. id = newId;
  218. if (id != null && !id.isEmpty()) {
  219. getElement().setId(id);
  220. }
  221. }
  222. }
  223. /**
  224. * Returns the wrapped widget of this stack item.
  225. *
  226. * @return the widget
  227. *
  228. * @deprecated This method is not called by the framework code anymore.
  229. * Use {@link #getChildWidget()} instead.
  230. */
  231. @Deprecated
  232. public Widget getComponent() {
  233. return getChildWidget();
  234. }
  235. /**
  236. * Queries the height from the wrapped widget and uses it to set this
  237. * stack item's height.
  238. */
  239. public void setHeightFromWidget() {
  240. Widget widget = getChildWidget();
  241. if (widget == null) {
  242. return;
  243. }
  244. int paintableHeight = widget.getElement().getOffsetHeight();
  245. setHeight(paintableHeight);
  246. }
  247. /**
  248. * Returns caption width including padding.
  249. *
  250. * @return the width of the caption (in pixels), or zero if there is no
  251. * caption element (not possible via the default implementation)
  252. */
  253. public int getCaptionWidth() {
  254. if (caption == null) {
  255. return 0;
  256. }
  257. int captionWidth = caption.getRequiredWidth();
  258. int padding = WidgetUtil.measureHorizontalPaddingAndBorder(
  259. caption.getElement(), 18);
  260. return captionWidth + padding;
  261. }
  262. /**
  263. * Sets the width of the stack item, or removes it if given value is
  264. * {@code -1}.
  265. *
  266. * @param width
  267. * the width to set (in pixels), or {@code -1} to remove
  268. * width
  269. */
  270. public void setWidth(int width) {
  271. if (width == -1) {
  272. super.setWidth("");
  273. } else {
  274. super.setWidth(width + "px");
  275. }
  276. }
  277. /**
  278. * Returns the offset height of this stack item.
  279. *
  280. * @return the height in pixels
  281. *
  282. * @deprecated This method is not called by the framework code anymore.
  283. * Use {@link #getOffsetHeight()} instead.
  284. */
  285. @Deprecated
  286. public int getHeight() {
  287. return getOffsetHeight();
  288. }
  289. /**
  290. * Returns the offset height of the caption node.
  291. *
  292. * @return the height in pixels
  293. */
  294. public int getCaptionHeight() {
  295. return captionNode.getOffsetHeight();
  296. }
  297. private VCaption caption;
  298. private boolean open = false;
  299. private Element content = DOM.createDiv();
  300. private Element captionNode = DOM.createDiv();
  301. private String styleName;
  302. /**
  303. * Constructs a stack item. The content widget should be set later when
  304. * the stack item is opened.
  305. */
  306. @SuppressWarnings("deprecation")
  307. public StackItem() {
  308. setElement(DOM.createDiv());
  309. caption = new VCaption(client);
  310. caption.addClickHandler(this);
  311. super.add(caption, captionNode);
  312. DOM.appendChild(captionNode, caption.getElement());
  313. DOM.appendChild(getElement(), captionNode);
  314. DOM.appendChild(getElement(), content);
  315. updateStyleNames(VAccordion.this.getStylePrimaryName());
  316. touchScrollHandler.addElement(getContainerElement());
  317. close();
  318. }
  319. private void updateStyleNames(String primaryStyleName) {
  320. content.removeClassName(getStylePrimaryName() + "-content");
  321. captionNode.removeClassName(getStylePrimaryName() + "-caption");
  322. setStylePrimaryName(primaryStyleName + "-item");
  323. updateTabStyleName(getStylePrimaryName());
  324. captionNode.addClassName(getStylePrimaryName() + "-caption");
  325. content.addClassName(getStylePrimaryName() + "-content");
  326. }
  327. @Override
  328. public void onBrowserEvent(Event event) {
  329. onSelectTab(this);
  330. }
  331. /**
  332. * Returns the container element for the content widget.
  333. *
  334. * @return the content container element
  335. */
  336. @SuppressWarnings("deprecation")
  337. public com.google.gwt.user.client.Element getContainerElement() {
  338. return DOM.asOld(content);
  339. }
  340. /**
  341. * Returns the wrapped widget of this stack item.
  342. *
  343. * @return the widget
  344. */
  345. public Widget getChildWidget() {
  346. return widget;
  347. }
  348. /**
  349. * Replaces the existing wrapped widget (if any) with a new widget.
  350. *
  351. * @param newWidget
  352. * the new widget to wrap
  353. */
  354. public void replaceWidget(Widget newWidget) {
  355. if (widget != null) {
  356. widgets.remove(widget);
  357. if (open) {
  358. remove(widget);
  359. }
  360. }
  361. widget = newWidget;
  362. widgets.add(newWidget);
  363. if (open) {
  364. add(widget, content);
  365. }
  366. }
  367. /**
  368. * Opens the stack item and clears any previous visibility settings.
  369. */
  370. public void open() {
  371. add(widget, content);
  372. open = true;
  373. content.getStyle().setTop(getCaptionHeight(), Unit.PX);
  374. content.getStyle().setLeft(0, Unit.PX);
  375. content.getStyle().clearVisibility();
  376. addStyleDependentName("open");
  377. getElement().setTabIndex(tabulatorIndex);
  378. }
  379. /**
  380. * Hides the stack item content but does not close the stack item.
  381. *
  382. * @deprecated This method is not called by the framework code anymore.
  383. */
  384. @Deprecated
  385. public void hide() {
  386. content.getStyle().setVisibility(Visibility.HIDDEN);
  387. }
  388. /**
  389. * Closes this stack item and removes the wrapped widget from the DOM
  390. * tree and this stack item.
  391. */
  392. public void close() {
  393. if (widget != null) {
  394. remove(widget);
  395. }
  396. content.getStyle().setVisibility(Visibility.HIDDEN);
  397. content.getStyle().setTop(-100000, Unit.PX);
  398. content.getStyle().setLeft(-100000, Unit.PX);
  399. removeStyleDependentName("open");
  400. setHeight(-1);
  401. setWidth("");
  402. open = false;
  403. getElement().setTabIndex(-1);
  404. }
  405. /**
  406. * Returns whether this stack item is open or not.
  407. *
  408. * @return {@code true} if open, {@code false} otherwise
  409. */
  410. public boolean isOpen() {
  411. return open;
  412. }
  413. /**
  414. * Updates the content of the open tab of the accordion.
  415. *
  416. * This method is mostly for internal use and may change in future
  417. * versions.
  418. *
  419. * @since 7.2
  420. * @param newWidget
  421. * new content
  422. */
  423. public void setContent(Widget newWidget) {
  424. if (widget == null) {
  425. widget = newWidget;
  426. widgets.add(newWidget);
  427. } else if (widget != newWidget) {
  428. replaceWidget(newWidget);
  429. }
  430. if (isOpen() && isDynamicHeight()) {
  431. setHeightFromWidget();
  432. }
  433. }
  434. @Override
  435. public void onClick(ClickEvent event) {
  436. onSelectTab(this);
  437. }
  438. /**
  439. * Updates the caption to match the current tab state.
  440. *
  441. * @param tabState
  442. * the state for this stack item
  443. */
  444. @SuppressWarnings("deprecation")
  445. public void updateCaption(TabState tabState) {
  446. // Need to call this because the caption does not have an owner, and
  447. // cannot have an owner, because only the selected stack item's
  448. // connector is sent to the client.
  449. caption.setCaptionAsHtml(isTabCaptionsAsHtml());
  450. caption.updateCaptionWithoutOwner(tabState.caption,
  451. !tabState.enabled, hasAttribute(tabState.description),
  452. hasAttribute(tabState.componentError),
  453. tabState.componentErrorLevel,
  454. connector.getResourceUrl(
  455. ComponentConstants.ICON_RESOURCE + tabState.key),
  456. tabState.iconAltText);
  457. }
  458. private boolean hasAttribute(String string) {
  459. return string != null && !string.trim().isEmpty();
  460. }
  461. /**
  462. * Updates the stack item's style name from the TabState.
  463. *
  464. * @param newStyleName
  465. * the new style name
  466. */
  467. private void updateTabStyleName(String newStyleName) {
  468. if (newStyleName != null && !newStyleName.isEmpty()) {
  469. if (!newStyleName.equals(styleName)) {
  470. // If we have a new style name
  471. if (styleName != null && !styleName.isEmpty()) {
  472. // Remove old style name if present
  473. removeStyleDependentName(styleName);
  474. }
  475. // Set new style name
  476. addStyleDependentName(newStyleName);
  477. styleName = newStyleName;
  478. }
  479. } else if (styleName != null) {
  480. // Remove the set stylename if no stylename is present in the
  481. // uidl
  482. removeStyleDependentName(styleName);
  483. styleName = null;
  484. }
  485. }
  486. /**
  487. * Returns the offset width of the wrapped widget.
  488. *
  489. * @return the offset width in pixels, or zero if no widget is set
  490. */
  491. public int getWidgetWidth() {
  492. if (widget == null) {
  493. return 0;
  494. }
  495. return widget.getOffsetWidth();
  496. }
  497. /**
  498. * Returns whether the given container's widget is this stack item's
  499. * wrapped widget. Does not check whether the given container's widget
  500. * is a child of the wrapped widget.
  501. *
  502. * @param p
  503. * the container whose widget to set
  504. * @return {@code true} if the container's widget matches wrapped
  505. * widget, {@code false} otherwise
  506. *
  507. * @deprecated This method is not called by the framework code anymore.
  508. */
  509. @Deprecated
  510. public boolean contains(ComponentConnector p) {
  511. return (getChildWidget() == p.getWidget());
  512. }
  513. /**
  514. * Returns whether the caption element is visible or not.
  515. *
  516. * @return {@code true} if visible, {@code false} otherwise
  517. *
  518. * @deprecated This method is not called by the framework code anymore.
  519. */
  520. @Deprecated
  521. public boolean isCaptionVisible() {
  522. return caption.isVisible();
  523. }
  524. }
  525. /**
  526. * {@inheritDoc}
  527. *
  528. * @deprecated This method is not called by the framework code anymore.
  529. */
  530. @Deprecated
  531. @Override
  532. protected void clearPaintables() {
  533. clear();
  534. }
  535. @Override
  536. public Iterator<Widget> getWidgetIterator() {
  537. return widgets.iterator();
  538. }
  539. @Override
  540. public int getTabCount() {
  541. return getWidgetCount();
  542. }
  543. @Override
  544. public void removeTab(int index) {
  545. StackItem item = getStackItem(index);
  546. remove(item);
  547. if (selectedItemIndex == index) {
  548. selectedItemIndex = -1;
  549. }
  550. touchScrollHandler.removeElement(item.getContainerElement());
  551. }
  552. @Override
  553. public ComponentConnector getTab(int index) {
  554. if (index < getWidgetCount()) {
  555. StackItem stackItem = getStackItem(index);
  556. if (stackItem == null) {
  557. return null;
  558. }
  559. Widget w = stackItem.getChildWidget();
  560. if (w != null) {
  561. return getConnectorForWidget(w);
  562. }
  563. }
  564. return null;
  565. }
  566. /**
  567. * For internal use only. May be removed or replaced in the future.
  568. *
  569. * @param index
  570. * the index of the stack item to get
  571. * @return the stack item
  572. */
  573. public StackItem getStackItem(int index) {
  574. return (StackItem) getWidget(index);
  575. }
  576. /**
  577. * Returns an iterable over all the stack items.
  578. *
  579. * @return the iterable
  580. */
  581. @SuppressWarnings({ "rawtypes", "unchecked" })
  582. public Iterable<StackItem> getStackItems() {
  583. return (Iterable) getChildren();
  584. }
  585. /**
  586. * Returns the currently open stack item.
  587. *
  588. * @return the open stack item, or {@code null} if one does not exist
  589. */
  590. public StackItem getOpenStackItem() {
  591. return openTab;
  592. }
  593. }