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.

TabSheet.java 53KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704
  1. /*
  2. * Copyright 2000-2018 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.ui;
  17. import java.io.Serializable;
  18. import java.lang.reflect.Method;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.Iterator;
  24. import java.util.Map;
  25. import java.util.Objects;
  26. import org.jsoup.nodes.Attributes;
  27. import org.jsoup.nodes.Element;
  28. import com.vaadin.event.FieldEvents.BlurEvent;
  29. import com.vaadin.event.FieldEvents.BlurListener;
  30. import com.vaadin.event.FieldEvents.BlurNotifier;
  31. import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcDecorator;
  32. import com.vaadin.event.FieldEvents.FocusEvent;
  33. import com.vaadin.event.FieldEvents.FocusListener;
  34. import com.vaadin.event.FieldEvents.FocusNotifier;
  35. import com.vaadin.event.HasUserOriginated;
  36. import com.vaadin.server.ErrorMessage;
  37. import com.vaadin.server.KeyMapper;
  38. import com.vaadin.server.Resource;
  39. import com.vaadin.shared.ComponentConstants;
  40. import com.vaadin.shared.Registration;
  41. import com.vaadin.shared.ui.ContentMode;
  42. import com.vaadin.shared.ui.tabsheet.TabState;
  43. import com.vaadin.shared.ui.tabsheet.TabsheetClientRpc;
  44. import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc;
  45. import com.vaadin.shared.ui.tabsheet.TabsheetState;
  46. import com.vaadin.ui.Component.Focusable;
  47. import com.vaadin.ui.declarative.DesignAttributeHandler;
  48. import com.vaadin.ui.declarative.DesignContext;
  49. import com.vaadin.ui.declarative.DesignException;
  50. /**
  51. * TabSheet component.
  52. *
  53. * Tabs are typically identified by the component contained on the tab (see
  54. * {@link ComponentContainer}), and tab metadata (including caption, icon,
  55. * visibility, enabledness, closability etc.) is kept in separate {@link Tab}
  56. * instances.
  57. *
  58. * Tabs added with {@link #addComponent(Component)} get the caption and the icon
  59. * of the component at the time when the component is created, and these are not
  60. * automatically updated after tab creation.
  61. *
  62. * A tab sheet can have multiple tab selection listeners and one tab close
  63. * handler ({@link CloseHandler}), which by default removes the tab from the
  64. * TabSheet.
  65. *
  66. * The {@link TabSheet} can be styled with the .v-tabsheet, .v-tabsheet-tabs and
  67. * .v-tabsheet-content styles.
  68. *
  69. * The current implementation does not load the tabs to the UI before the first
  70. * time they are shown, but this may change in future releases.
  71. *
  72. * @author Vaadin Ltd.
  73. * @since 3.0
  74. */
  75. public class TabSheet extends AbstractComponentContainer
  76. implements Focusable, FocusNotifier, BlurNotifier, SelectiveRenderer {
  77. /**
  78. * Client to server RPC implementation for TabSheet.
  79. *
  80. * @since 7.2
  81. */
  82. protected class TabsheetServerRpcImpl implements TabsheetServerRpc {
  83. @Override
  84. public void setSelected(String key) {
  85. setSelectedTab(keyMapper.get(key), true);
  86. }
  87. @Override
  88. public void closeTab(String key) {
  89. final Component tab = keyMapper.get(key);
  90. if (tab != null) {
  91. closeHandler.onTabClose(TabSheet.this, tab);
  92. }
  93. }
  94. }
  95. /**
  96. * List of component tabs (tab contents). In addition to being on this list,
  97. * there is a {@link Tab} object in tabs for each tab with meta-data about
  98. * the tab.
  99. */
  100. private final ArrayList<Component> components = new ArrayList<>();
  101. /**
  102. * Map containing information related to the tabs (caption, icon etc).
  103. */
  104. private final Map<Component, Tab> tabs = new HashMap<>();
  105. /**
  106. * Selected tab content component.
  107. */
  108. private Component selected = null;
  109. /**
  110. * Mapper between server-side component instances (tab contents) and keys
  111. * given to the client that identify tabs.
  112. */
  113. protected final KeyMapper<Component> keyMapper = new KeyMapper<>();
  114. /**
  115. * Handler to be called when a tab is closed.
  116. */
  117. private CloseHandler closeHandler;
  118. /**
  119. * Constructs a new TabSheet. A TabSheet is immediate by default, and the
  120. * default close handler removes the tab being closed.
  121. */
  122. public TabSheet() {
  123. super();
  124. registerRpc(rpc);
  125. registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent));
  126. // expand horizontally by default
  127. setWidth(100, UNITS_PERCENTAGE);
  128. setCloseHandler(TabSheet::removeComponent);
  129. }
  130. /**
  131. * Constructs a new TabSheet containing the given components.
  132. *
  133. * @param components
  134. * The components to add to the tab sheet. Each component will be
  135. * added to a separate tab.
  136. */
  137. public TabSheet(Component... components) {
  138. this();
  139. addComponents(components);
  140. }
  141. /**
  142. * Gets the component container iterator for going through all the
  143. * components (tab contents).
  144. *
  145. * @return the unmodifiable Iterator of the tab content components
  146. */
  147. @Override
  148. public Iterator<Component> iterator() {
  149. return Collections.unmodifiableList(components).iterator();
  150. }
  151. /**
  152. * Gets the number of contained components (tabs). Consistent with the
  153. * iterator returned by {@link #getComponentIterator()}.
  154. *
  155. * @return the number of contained components
  156. */
  157. @Override
  158. public int getComponentCount() {
  159. return components.size();
  160. }
  161. /**
  162. * Removes a component and its corresponding tab.
  163. *
  164. * If the tab was selected, the first eligible (visible and enabled)
  165. * remaining tab is selected.
  166. *
  167. * @param component
  168. * the component to be removed.
  169. */
  170. @Override
  171. public void removeComponent(Component component) {
  172. if (component != null && components.contains(component)) {
  173. int componentIndex = components.indexOf(component);
  174. super.removeComponent(component);
  175. keyMapper.remove(component);
  176. components.remove(component);
  177. Tab removedTab = tabs.remove(component);
  178. getState().tabs
  179. .remove(((TabSheetTabImpl) removedTab).getTabState());
  180. if (component.equals(selected)) {
  181. if (components.isEmpty()) {
  182. setSelected(null);
  183. } else {
  184. int newSelectedIndex = selectedTabIndexAfterTabRemove(
  185. componentIndex);
  186. // Make sure the component actually exists, in case someone
  187. // override it and provide a non existing component.
  188. if (0 <= newSelectedIndex
  189. && newSelectedIndex < components.size()) {
  190. Component newSelectedComponent = components
  191. .get(newSelectedIndex);
  192. // Select only if the tab is enabled.
  193. Tab newSelectedTab = tabs.get(newSelectedComponent);
  194. if (newSelectedTab.isEnabled()) {
  195. setSelected(newSelectedComponent);
  196. }
  197. }
  198. // select the first enabled and visible tab, if any
  199. updateSelection();
  200. fireSelectedTabChange(false);
  201. }
  202. }
  203. }
  204. }
  205. /**
  206. * Called when a selected tab is removed to specify the new tab to select.
  207. * Can be overridden to provide another algorithm that calculates the new
  208. * selection.
  209. *
  210. * Current implementation will choose the first enabled tab to the right of
  211. * the removed tab if it's not the last one, otherwise will choose the
  212. * closer enabled tab to the left.
  213. *
  214. * @since 7.4
  215. * @param removedTabIndex
  216. * the index of the selected tab which was just remove.
  217. * @return the index of the tab to be selected or -1 if there are no more
  218. * enabled tabs to select.
  219. */
  220. protected int selectedTabIndexAfterTabRemove(int removedTabIndex) {
  221. for (int i = removedTabIndex; i < components.size(); i++) {
  222. Tab tab = getTab(i);
  223. if (tab.isEnabled()) {
  224. return i;
  225. }
  226. }
  227. for (int i = removedTabIndex - 1; i >= 0; i--) {
  228. Tab tab = getTab(i);
  229. if (tab.isEnabled()) {
  230. return i;
  231. }
  232. }
  233. return -1;
  234. }
  235. /**
  236. * Removes a {@link Tab} and the component associated with it, as previously
  237. * added with {@link #addTab(Component)},
  238. * {@link #addTab(Component, String, Resource)} or
  239. * {@link #addComponent(Component)}.
  240. * <p>
  241. * If the tab was selected, the first eligible (visible and enabled)
  242. * remaining tab is selected.
  243. * </p>
  244. *
  245. * @see #addTab(Component)
  246. * @see #addTab(Component, String, Resource)
  247. * @see #addComponent(Component)
  248. * @see #removeComponent(Component)
  249. * @param tab
  250. * the Tab to remove
  251. */
  252. public void removeTab(Tab tab) {
  253. removeComponent(tab.getComponent());
  254. }
  255. /**
  256. * Adds a new tab into TabSheet. Component caption and icon are copied to
  257. * the tab metadata at creation time.
  258. *
  259. * @see #addTab(Component)
  260. *
  261. * @param c
  262. * the component to be added.
  263. */
  264. @Override
  265. public void addComponent(Component c) {
  266. addTab(c);
  267. }
  268. /**
  269. * Adds a new tab into TabSheet.
  270. *
  271. * The first tab added to a tab sheet is automatically selected and a tab
  272. * selection event is fired.
  273. *
  274. * If the component is already present in the tab sheet, changes its caption
  275. * and returns the corresponding (old) tab, preserving other tab metadata.
  276. *
  277. * @param c
  278. * the component to be added onto tab - should not be null.
  279. * @param caption
  280. * the caption to be set for the component and used rendered in
  281. * tab bar
  282. * @return the created {@link Tab}
  283. */
  284. public Tab addTab(Component c, String caption) {
  285. return addTab(c, caption, null);
  286. }
  287. /**
  288. * Adds a new tab into TabSheet.
  289. *
  290. * The first tab added to a tab sheet is automatically selected and a tab
  291. * selection event is fired.
  292. *
  293. * If the component is already present in the tab sheet, changes its caption
  294. * and icon and returns the corresponding (old) tab, preserving other tab
  295. * metadata.
  296. *
  297. * @param c
  298. * the component to be added onto tab - should not be null.
  299. * @param caption
  300. * the caption to be set for the component and used rendered in
  301. * tab bar
  302. * @param icon
  303. * the icon to be set for the component and used rendered in tab
  304. * bar
  305. * @return the created {@link Tab}
  306. */
  307. public Tab addTab(Component c, String caption, Resource icon) {
  308. return addTab(c, caption, icon, components.size());
  309. }
  310. /**
  311. * Adds a new tab into TabSheet.
  312. *
  313. * The first tab added to a tab sheet is automatically selected and a tab
  314. * selection event is fired.
  315. *
  316. * If the component is already present in the tab sheet, changes its caption
  317. * and icon and returns the corresponding (old) tab, preserving other tab
  318. * metadata like the position.
  319. *
  320. * @param tabComponent
  321. * the component to be added onto tab - should not be null.
  322. * @param caption
  323. * the caption to be set for the component and used rendered in
  324. * tab bar
  325. * @param icon
  326. * the icon to be set for the component and used rendered in tab
  327. * bar
  328. * @param position
  329. * the position at where the the tab should be added.
  330. * @return the created {@link Tab}
  331. */
  332. public Tab addTab(Component tabComponent, String caption, Resource icon,
  333. int position) {
  334. if (tabComponent == null) {
  335. return null;
  336. } else if (tabs.containsKey(tabComponent)) {
  337. Tab tab = tabs.get(tabComponent);
  338. tab.setCaption(caption);
  339. tab.setIcon(icon);
  340. return tab;
  341. } else {
  342. components.add(position, tabComponent);
  343. TabSheetTabImpl tab = new TabSheetTabImpl(
  344. keyMapper.key(tabComponent), caption, icon);
  345. getState().tabs.add(position, tab.getTabState());
  346. tabs.put(tabComponent, tab);
  347. if (selected == null) {
  348. setSelected(tabComponent);
  349. fireSelectedTabChange(false);
  350. }
  351. super.addComponent(tabComponent);
  352. return tab;
  353. }
  354. }
  355. /**
  356. * Adds a new tab into TabSheet. Component caption and icon are copied to
  357. * the tab metadata at creation time.
  358. *
  359. * If the tab sheet already contains the component, its tab is returned.
  360. *
  361. * @param c
  362. * the component to be added onto tab - should not be null.
  363. * @return the created {@link Tab}
  364. */
  365. public Tab addTab(Component c) {
  366. return addTab(c, components.size());
  367. }
  368. /**
  369. * Adds a new tab into TabSheet. Component caption and icon are copied to
  370. * the tab metadata at creation time.
  371. *
  372. * If the tab sheet already contains the component, its tab is returned.
  373. *
  374. * @param component
  375. * the component to be added onto tab - should not be null.
  376. * @param position
  377. * The position where the tab should be added
  378. * @return the created {@link Tab}
  379. */
  380. public Tab addTab(Component component, int position) {
  381. Tab result = tabs.get(component);
  382. if (result == null) {
  383. result = addTab(component, component.getCaption(),
  384. component.getIcon(), position);
  385. }
  386. return result;
  387. }
  388. /**
  389. * Moves all components from another container to this container. The
  390. * components are removed from the other container.
  391. *
  392. * If the source container is a {@link TabSheet}, component captions and
  393. * icons are copied from it.
  394. *
  395. * @param source
  396. * the container components are removed from.
  397. */
  398. @Override
  399. public void moveComponentsFrom(ComponentContainer source) {
  400. for (final Iterator<Component> i = source.getComponentIterator(); i
  401. .hasNext();) {
  402. final Component c = i.next();
  403. String caption = null;
  404. Resource icon = null;
  405. String iconAltText = "";
  406. if (TabSheet.class.isAssignableFrom(source.getClass())) {
  407. Tab tab = ((TabSheet) source).getTab(c);
  408. caption = tab.getCaption();
  409. icon = tab.getIcon();
  410. iconAltText = tab.getIconAlternateText();
  411. }
  412. source.removeComponent(c);
  413. Tab tab = addTab(c, caption, icon);
  414. tab.setIconAlternateText(iconAltText);
  415. }
  416. }
  417. /**
  418. * Are the tab selection parts ("tabs") hidden.
  419. *
  420. * @return true if the tabs are hidden in the UI
  421. * @deprecated as of 7.5, use {@link #isTabsVisible()} instead
  422. */
  423. @Deprecated
  424. public boolean areTabsHidden() {
  425. return !isTabsVisible();
  426. }
  427. /**
  428. * Hides or shows the tab selection parts ("tabs").
  429. *
  430. * @param tabsHidden
  431. * true if the tabs should be hidden
  432. * @deprecated as of 7.5, use {@link #setTabsVisible(boolean)} instead
  433. */
  434. @Deprecated
  435. public void hideTabs(boolean tabsHidden) {
  436. setTabsVisible(!tabsHidden);
  437. }
  438. /**
  439. * Sets whether the tab selection part should be shown in the UI.
  440. *
  441. * @since 7.5
  442. * @param tabsVisible
  443. * true if the tabs should be shown in the UI, false otherwise
  444. */
  445. public void setTabsVisible(boolean tabsVisible) {
  446. getState().tabsVisible = tabsVisible;
  447. }
  448. /**
  449. * Checks if the tab selection part should be shown in the UI.
  450. *
  451. * @return true if the tabs are shown in the UI, false otherwise
  452. * @since 7.5
  453. */
  454. public boolean isTabsVisible() {
  455. return getState(false).tabsVisible;
  456. }
  457. /**
  458. * Returns the {@link Tab} (metadata) for a component. The {@link Tab}
  459. * object can be used for setting caption,icon, etc for the tab.
  460. *
  461. * @param c
  462. * the component
  463. * @return The tab instance associated with the given component, or null if
  464. * the tabsheet does not contain the component.
  465. */
  466. public Tab getTab(Component c) {
  467. return tabs.get(c);
  468. }
  469. /**
  470. * Returns the {@link Tab} (metadata) with the given index. The {@link Tab}
  471. * object can be used for setting caption,icon, etc for the tab.
  472. *
  473. * @param index
  474. * the index of the tab
  475. * @return The tab with the given index, or null if the index is out of
  476. * bounds.
  477. */
  478. public Tab getTab(int index) {
  479. if (index >= 0 && index < getComponentCount()) {
  480. return getTab(components.get(index));
  481. } else {
  482. return null;
  483. }
  484. }
  485. /**
  486. * Sets the selected tab. The tab is identified by the tab content
  487. * component. Does nothing if the tabsheet doesn't contain the component.
  488. *
  489. * @param component
  490. * the component of the tab to select
  491. */
  492. public void setSelectedTab(Component component) {
  493. setSelectedTab(component, false);
  494. }
  495. /**
  496. * Sets the selected tab. The tab is identified by the tab content
  497. * component. Does nothing if the tabsheet doesn't contain the component.
  498. *
  499. * @param component
  500. * the component of the tab to select
  501. * @param userOriginated
  502. * <code>true</code> if the event originates from the client
  503. * side, <code>false</code> otherwise
  504. * @since 8.1
  505. */
  506. public void setSelectedTab(Component component, boolean userOriginated) {
  507. if (component != null && components.contains(component)
  508. && !component.equals(selected)) {
  509. setSelected(component);
  510. updateSelection();
  511. fireSelectedTabChange(userOriginated);
  512. markAsDirty();
  513. getRpcProxy(TabsheetClientRpc.class).revertToSharedStateSelection();
  514. }
  515. }
  516. /**
  517. * Sets the selected tab in the TabSheet. Ensures that the selected tab is
  518. * repainted if needed.
  519. *
  520. * @param component
  521. * The new selection or null for no selection
  522. */
  523. private void setSelected(Component component) {
  524. Tab tab = tabs.get(selected);
  525. selected = component;
  526. // Repaint of the selected component is needed as only the selected
  527. // component is communicated to the client. Otherwise this will be a
  528. // "cached" update even though the client knows nothing about the
  529. // connector
  530. if (selected != null) {
  531. tab = getTab(component);
  532. if (tab != null && tab.getDefaultFocusComponent() != null) {
  533. tab.getDefaultFocusComponent().focus();
  534. }
  535. getState().selected = keyMapper.key(selected);
  536. selected.markAsDirtyRecursive();
  537. } else {
  538. getState().selected = null;
  539. }
  540. }
  541. /**
  542. * Sets the selected tab. The tab is identified by the corresponding
  543. * {@link Tab Tab} instance. Does nothing if the tabsheet doesn't contain
  544. * the given tab.
  545. *
  546. * @param tab
  547. * the tab to select
  548. */
  549. public void setSelectedTab(Tab tab) {
  550. if (tab != null) {
  551. setSelectedTab(tab.getComponent(), false);
  552. }
  553. }
  554. /**
  555. * Sets the selected tab, identified by its index. Does nothing if the
  556. * position is out of bounds.
  557. *
  558. * @param index
  559. * the index of the tab to select
  560. */
  561. public void setSelectedTab(int index) {
  562. setSelectedTab(getTab(index));
  563. }
  564. /**
  565. * Checks if the current selection is valid, and updates the selection if
  566. * the previously selected component is not visible and enabled. The first
  567. * visible and enabled tab is selected if the current selection is empty or
  568. * invalid.
  569. *
  570. * This method does not fire tab change events, but the caller should do so
  571. * if appropriate.
  572. *
  573. * @return true if selection was changed, false otherwise
  574. */
  575. private boolean updateSelection() {
  576. Component originalSelection = selected;
  577. for (final Iterator<Component> i = getComponentIterator(); i
  578. .hasNext();) {
  579. final Component component = i.next();
  580. Tab tab = tabs.get(component);
  581. /*
  582. * If we have no selection, if the current selection is invisible or
  583. * if the current selection is disabled (but the whole component is
  584. * not) we select this tab instead
  585. */
  586. Tab selectedTabInfo = null;
  587. if (selected != null) {
  588. selectedTabInfo = tabs.get(selected);
  589. }
  590. if (selected == null || selectedTabInfo == null
  591. || !selectedTabInfo.isVisible()
  592. || !selectedTabInfo.isEnabled()) {
  593. // The current selection is not valid so we need to change
  594. // it
  595. if (tab.isEnabled() && tab.isVisible()) {
  596. setSelected(component);
  597. break;
  598. } else {
  599. /*
  600. * The current selection is not valid but this tab cannot be
  601. * selected either.
  602. */
  603. setSelected(null);
  604. }
  605. }
  606. }
  607. return originalSelection != selected;
  608. }
  609. /**
  610. * Gets the selected tab content component.
  611. *
  612. * @return the selected tab contents
  613. */
  614. public Component getSelectedTab() {
  615. return selected;
  616. }
  617. private TabsheetServerRpcImpl rpc = new TabsheetServerRpcImpl();
  618. /**
  619. * Replaces a component (tab content) with another. This can be used to
  620. * change tab contents or to rearrange tabs. The tab position and some
  621. * metadata are preserved when moving components within the same
  622. * {@link TabSheet}.
  623. *
  624. * If the oldComponent is not present in the tab sheet, the new one is added
  625. * at the end.
  626. *
  627. * If the oldComponent is already in the tab sheet but the newComponent
  628. * isn't, the old tab is replaced with a new one, and the caption and icon
  629. * of the old one are copied to the new tab.
  630. *
  631. * If both old and new components are present, their positions are swapped.
  632. *
  633. * {@inheritDoc}
  634. */
  635. @Override
  636. public void replaceComponent(Component oldComponent,
  637. Component newComponent) {
  638. boolean selectAfterInserting = false;
  639. if (selected == oldComponent) {
  640. selectAfterInserting = true;
  641. }
  642. Tab newTab = tabs.get(newComponent);
  643. Tab oldTab = tabs.get(oldComponent);
  644. // Gets the locations
  645. int oldLocation = -1;
  646. int newLocation = -1;
  647. int location = 0;
  648. for (final Component component : components) {
  649. if (component == oldComponent) {
  650. oldLocation = location;
  651. }
  652. if (component == newComponent) {
  653. newLocation = location;
  654. }
  655. location++;
  656. }
  657. if (oldLocation == -1) {
  658. addComponent(newComponent);
  659. } else if (newLocation == -1) {
  660. if (selected == oldComponent) {
  661. setSelected(null);
  662. }
  663. removeComponent(oldComponent);
  664. newTab = addTab(newComponent, oldLocation);
  665. if (selectAfterInserting) {
  666. setSelected(newComponent);
  667. }
  668. // Copy all relevant metadata to the new tab (#8793)
  669. // TODO Should reuse the old tab instance instead?
  670. copyTabMetadata(oldTab, newTab);
  671. } else {
  672. components.set(oldLocation, newComponent);
  673. components.set(newLocation, oldComponent);
  674. if (selectAfterInserting) {
  675. setSelected(newComponent);
  676. // SelectedTabChangeEvent should be fired here as selected Tab
  677. // is changed.
  678. // Other cases are handled implicitly by removeComponent() and
  679. // addComponent()addTab()
  680. fireSelectedTabChange(false);
  681. }
  682. // Tab associations are not changed, but metadata is swapped between
  683. // the instances
  684. // TODO Should reassociate the instances instead?
  685. Tab tmp = new TabSheetTabImpl(null, null, null);
  686. copyTabMetadata(newTab, tmp);
  687. copyTabMetadata(oldTab, newTab);
  688. copyTabMetadata(tmp, oldTab);
  689. markAsDirty();
  690. }
  691. }
  692. /* Click event */
  693. private static final Method SELECTED_TAB_CHANGE_METHOD;
  694. static {
  695. try {
  696. SELECTED_TAB_CHANGE_METHOD = SelectedTabChangeListener.class
  697. .getDeclaredMethod("selectedTabChange",
  698. SelectedTabChangeEvent.class);
  699. } catch (final NoSuchMethodException e) {
  700. // This should never happen
  701. throw new RuntimeException(
  702. "Internal error finding methods in TabSheet");
  703. }
  704. }
  705. /**
  706. * Selected tab change event. This event is sent when the selected (shown)
  707. * tab in the tab sheet is changed.
  708. *
  709. * @author Vaadin Ltd.
  710. * @since 3.0
  711. */
  712. public static class SelectedTabChangeEvent extends Component.Event
  713. implements HasUserOriginated {
  714. private final boolean userOriginated;
  715. /**
  716. * Creates a new instance of the event.
  717. *
  718. * @param source
  719. * the source of the event
  720. * @param userOriginated
  721. * <code>true</code> if the event originates from the client
  722. * side, <code>false</code> otherwise
  723. * @since 8.1
  724. */
  725. public SelectedTabChangeEvent(Component source,
  726. boolean userOriginated) {
  727. super(source);
  728. this.userOriginated = userOriginated;
  729. }
  730. /**
  731. * The TabSheet where the event occurred.
  732. *
  733. * @return the TabSheet where the event occurred
  734. */
  735. public TabSheet getTabSheet() {
  736. return (TabSheet) getSource();
  737. }
  738. /**
  739. * {@inheritDoc}
  740. *
  741. * @since 8.1
  742. */
  743. @Override
  744. public boolean isUserOriginated() {
  745. return userOriginated;
  746. }
  747. }
  748. /**
  749. * Selected tab change event listener. The listener is called whenever
  750. * another tab is selected, including when adding the first tab to a
  751. * tabsheet.
  752. *
  753. * @author Vaadin Ltd.
  754. *
  755. * @since 3.0
  756. */
  757. @FunctionalInterface
  758. public interface SelectedTabChangeListener extends Serializable {
  759. /**
  760. * Selected (shown) tab in tab sheet has has been changed.
  761. *
  762. * @param event
  763. * the selected tab change event.
  764. */
  765. public void selectedTabChange(SelectedTabChangeEvent event);
  766. }
  767. /**
  768. * Adds a tab selection listener.
  769. *
  770. * @see Registration
  771. *
  772. * @param listener
  773. * the Listener to be added, not null
  774. * @return a registration object for removing the listener
  775. * @since 8.0
  776. */
  777. public Registration addSelectedTabChangeListener(
  778. SelectedTabChangeListener listener) {
  779. return addListener(SelectedTabChangeEvent.class, listener,
  780. SELECTED_TAB_CHANGE_METHOD);
  781. }
  782. /**
  783. * Removes a tab selection listener.
  784. *
  785. * @param listener
  786. * the Listener to be removed.
  787. *
  788. * @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
  789. * registration object returned from
  790. * {@link #removeSelectedTabChangeListener(SelectedTabChangeListener)}
  791. * .
  792. */
  793. @Deprecated
  794. public void removeSelectedTabChangeListener(
  795. SelectedTabChangeListener listener) {
  796. removeListener(SelectedTabChangeEvent.class, listener,
  797. SELECTED_TAB_CHANGE_METHOD);
  798. }
  799. /**
  800. * Sends an event originating from the server, telling that the currently
  801. * selected tab has changed.
  802. *
  803. * @deprecated use {@link #fireSelectedTabChange(boolean)} to indicate the
  804. * origin of the event
  805. */
  806. @Deprecated
  807. protected void fireSelectedTabChange() {
  808. fireSelectedTabChange(false);
  809. }
  810. /**
  811. * Sends an event that the currently selected tab has changed.
  812. *
  813. * @param userOriginated
  814. * <code>true</code> if the event originates from the client
  815. * side, <code>false</code> otherwise
  816. * @since 8.1
  817. */
  818. protected void fireSelectedTabChange(boolean userOriginated) {
  819. fireEvent(new SelectedTabChangeEvent(this, userOriginated));
  820. }
  821. /**
  822. * Tab meta-data for a component in a {@link TabSheet}.
  823. *
  824. * The meta-data includes the tab caption, icon, visibility and enabledness,
  825. * closability, description (tooltip) and an optional component error shown
  826. * in the tab.
  827. *
  828. * Tabs are identified by the component contained on them in most cases, and
  829. * the meta-data can be obtained with {@link TabSheet#getTab(Component)}.
  830. */
  831. public interface Tab extends Serializable {
  832. /**
  833. * Returns the visible status for the tab. An invisible tab is not shown
  834. * in the tab bar and cannot be selected.
  835. *
  836. * @return true for visible, false for hidden
  837. */
  838. public boolean isVisible();
  839. /**
  840. * Sets the visible status for the tab. An invisible tab is not shown in
  841. * the tab bar and cannot be selected, selection is changed
  842. * automatically when there is an attempt to select an invisible tab.
  843. *
  844. * @param visible
  845. * true for visible, false for hidden
  846. */
  847. public void setVisible(boolean visible);
  848. /**
  849. * Returns the closability status for the tab.
  850. *
  851. * @return true if the tab is allowed to be closed by the end user,
  852. * false for not allowing closing
  853. */
  854. public boolean isClosable();
  855. /**
  856. * Sets the closability status for the tab. A closable tab can be closed
  857. * by the user through the user interface. This also controls if a close
  858. * button is shown to the user or not.
  859. * <p>
  860. * Note! Currently only supported by TabSheet, not Accordion.
  861. * </p>
  862. *
  863. * @param closable
  864. * true if the end user is allowed to close the tab, false
  865. * for not allowing to close. Should default to false.
  866. */
  867. public void setClosable(boolean closable);
  868. /**
  869. * Set the component that should automatically focused when the tab is
  870. * selected.
  871. *
  872. * @param component
  873. * the component to focus
  874. */
  875. public void setDefaultFocusComponent(Focusable component);
  876. /**
  877. * Get the component that should be automatically focused when the tab
  878. * is selected.
  879. *
  880. * @return the focusable component
  881. */
  882. public Focusable getDefaultFocusComponent();
  883. /**
  884. * Returns the enabled status for the tab. A disabled tab is shown as
  885. * such in the tab bar and cannot be selected.
  886. *
  887. * @return true for enabled, false for disabled
  888. */
  889. public boolean isEnabled();
  890. /**
  891. * Sets the enabled status for the tab. A disabled tab is shown as such
  892. * in the tab bar and cannot be selected.
  893. *
  894. * @param enabled
  895. * true for enabled, false for disabled
  896. */
  897. public void setEnabled(boolean enabled);
  898. /**
  899. * Sets the caption for the tab.
  900. *
  901. * @param caption
  902. * the caption to set
  903. */
  904. public void setCaption(String caption);
  905. /**
  906. * Gets the caption for the tab.
  907. */
  908. public String getCaption();
  909. /**
  910. * Gets the icon for the tab.
  911. */
  912. public Resource getIcon();
  913. /**
  914. * Sets the icon for the tab.
  915. *
  916. * @param icon
  917. * the icon to set
  918. */
  919. public void setIcon(Resource icon);
  920. /**
  921. * Sets the icon and alt text for the tab.
  922. *
  923. * @param icon
  924. * the icon to set
  925. */
  926. public void setIcon(Resource icon, String iconAltText);
  927. /**
  928. * Gets the icon alt text for the tab.
  929. *
  930. * @since 7.2
  931. */
  932. public String getIconAlternateText();
  933. /**
  934. * Sets the icon alt text for the tab.
  935. *
  936. * @since 7.2
  937. *
  938. * @param iconAltText
  939. * the icon to set
  940. */
  941. public void setIconAlternateText(String iconAltText);
  942. /**
  943. * Gets the description for the tab. The description can be used to
  944. * briefly describe the state of the tab to the user, and is typically
  945. * shown as a tooltip when hovering over the tab.
  946. *
  947. * @return the description for the tab
  948. */
  949. public String getDescription();
  950. /**
  951. * Sets the description for the tab. The description can be used to
  952. * briefly describe the state of the tab to the user, and is typically
  953. * shown as a tooltip when hovering over the tab.
  954. * <p>
  955. * Setting a description through this method will additionally set the
  956. * content mode of the description to preformatted. For setting a
  957. * different content mode see the overload
  958. * {@link #setDescription(String, ContentMode)}.
  959. *
  960. * @param description
  961. * the new description string for the tab.
  962. */
  963. public void setDescription(String description);
  964. /**
  965. * Sets the description for the tab. The description can be used to
  966. * briefly describe the state of the tab to the user, and is typically
  967. * shown as a tooltip when hovering over the tab.
  968. *
  969. * @see ContentMode
  970. *
  971. * @param description
  972. * the new description string for the tab
  973. * @param mode
  974. * content mode used to display the description
  975. *
  976. * @since 8.1
  977. */
  978. public void setDescription(String description, ContentMode mode);
  979. /**
  980. * Sets an error indicator to be shown in the tab. This can be used e.g.
  981. * to communicate to the user that there is a problem in the contents of
  982. * the tab.
  983. *
  984. * @see AbstractComponent#setComponentError(ErrorMessage)
  985. *
  986. * @param componentError
  987. * error message or null for none
  988. */
  989. public void setComponentError(ErrorMessage componentError);
  990. /**
  991. * Gets the current error message shown for the tab.
  992. *
  993. * TODO currently not sent to the client
  994. *
  995. * @see AbstractComponent#setComponentError(ErrorMessage)
  996. */
  997. public ErrorMessage getComponentError();
  998. /**
  999. * Get the component related to the Tab.
  1000. */
  1001. public Component getComponent();
  1002. /**
  1003. * Sets a style name for the tab. The style name will be rendered as a
  1004. * HTML class name, which can be used in a CSS definition.
  1005. *
  1006. * <pre>
  1007. * Tab tab = tabsheet.addTab(tabContent, &quot;Tab text&quot;);
  1008. * tab.setStyleName(&quot;mystyle&quot;);
  1009. * </pre>
  1010. * <p>
  1011. * The used style name will be prefixed with "
  1012. * {@code v-tabsheet-tabitemcell-}". For example, if you give a tab the
  1013. * style "{@code mystyle}", the tab will get a "
  1014. * {@code v-tabsheet-tabitemcell-mystyle}" style. You could then style
  1015. * the component with:
  1016. * </p>
  1017. *
  1018. * <pre>
  1019. * .v-tabsheet-tabitemcell-mystyle {font-style: italic;}
  1020. * </pre>
  1021. *
  1022. * @param styleName
  1023. * the new style to be set for tab
  1024. * @see #getStyleName()
  1025. */
  1026. public void setStyleName(String styleName);
  1027. /**
  1028. * Gets the user-defined CSS style name of the tab. Built-in style names
  1029. * defined in Vaadin or GWT are not returned.
  1030. *
  1031. * @return the style name or of the tab
  1032. * @see #setStyleName(String)
  1033. */
  1034. public String getStyleName();
  1035. /**
  1036. * Adds an unique id for component that is used in the client-side for
  1037. * testing purposes. Keeping identifiers unique is the responsibility of
  1038. * the programmer.
  1039. *
  1040. * @param id
  1041. * An alphanumeric id
  1042. */
  1043. public void setId(String id);
  1044. /**
  1045. * Gets currently set debug identifier.
  1046. *
  1047. * @return current id, null if not set
  1048. */
  1049. public String getId();
  1050. }
  1051. /**
  1052. * TabSheet's implementation of {@link Tab} - tab metadata.
  1053. */
  1054. public class TabSheetTabImpl implements Tab {
  1055. private final TabState tabState;
  1056. private Focusable defaultFocus;
  1057. private ErrorMessage componentError;
  1058. public TabSheetTabImpl(String key, String caption, Resource icon) {
  1059. tabState = new TabState();
  1060. if (caption == null) {
  1061. caption = "";
  1062. }
  1063. tabState.key = key;
  1064. tabState.caption = caption;
  1065. setIcon(icon);
  1066. }
  1067. /**
  1068. * Returns the tab caption. Can never be null.
  1069. */
  1070. @Override
  1071. public String getCaption() {
  1072. return tabState.caption;
  1073. }
  1074. @Override
  1075. public void setCaption(String caption) {
  1076. tabState.caption = caption;
  1077. markAsDirty();
  1078. }
  1079. @Override
  1080. public Resource getIcon() {
  1081. return getResource(ComponentConstants.ICON_RESOURCE + tabState.key);
  1082. }
  1083. @Override
  1084. public void setIcon(Resource icon) {
  1085. // this might not be ideal (resetting icon altText), but matches
  1086. // previous semantics
  1087. setIcon(icon, "");
  1088. }
  1089. @Override
  1090. public String getIconAlternateText() {
  1091. return tabState.iconAltText;
  1092. }
  1093. @Override
  1094. public void setIconAlternateText(String iconAltText) {
  1095. tabState.iconAltText = iconAltText;
  1096. markAsDirty();
  1097. }
  1098. @Override
  1099. public void setDefaultFocusComponent(Focusable defaultFocus) {
  1100. this.defaultFocus = defaultFocus;
  1101. }
  1102. @Override
  1103. public Focusable getDefaultFocusComponent() {
  1104. return defaultFocus;
  1105. }
  1106. @Override
  1107. public boolean isEnabled() {
  1108. return tabState.enabled;
  1109. }
  1110. @Override
  1111. public void setEnabled(boolean enabled) {
  1112. tabState.enabled = enabled;
  1113. if (updateSelection()) {
  1114. fireSelectedTabChange(false);
  1115. }
  1116. markAsDirty();
  1117. }
  1118. @Override
  1119. public boolean isVisible() {
  1120. return tabState.visible;
  1121. }
  1122. @Override
  1123. public void setVisible(boolean visible) {
  1124. tabState.visible = visible;
  1125. if (updateSelection()) {
  1126. fireSelectedTabChange(false);
  1127. }
  1128. markAsDirty();
  1129. }
  1130. @Override
  1131. public boolean isClosable() {
  1132. return tabState.closable;
  1133. }
  1134. @Override
  1135. public void setClosable(boolean closable) {
  1136. tabState.closable = closable;
  1137. markAsDirty();
  1138. }
  1139. @Override
  1140. public String getDescription() {
  1141. return tabState.description;
  1142. }
  1143. @Override
  1144. public void setDescription(String description) {
  1145. setDescription(description, ContentMode.PREFORMATTED);
  1146. }
  1147. @Override
  1148. public void setDescription(String description, ContentMode mode) {
  1149. tabState.description = description;
  1150. tabState.descriptionContentMode = mode;
  1151. markAsDirty();
  1152. }
  1153. @Override
  1154. public ErrorMessage getComponentError() {
  1155. return componentError;
  1156. }
  1157. @Override
  1158. public void setComponentError(ErrorMessage componentError) {
  1159. this.componentError = componentError;
  1160. if (componentError != null) {
  1161. tabState.componentError = componentError
  1162. .getFormattedHtmlMessage();
  1163. tabState.componentErrorLevel = componentError.getErrorLevel();
  1164. } else {
  1165. tabState.componentError = null;
  1166. tabState.componentErrorLevel = null;
  1167. }
  1168. markAsDirty();
  1169. }
  1170. @Override
  1171. public Component getComponent() {
  1172. for (Map.Entry<Component, Tab> entry : tabs.entrySet()) {
  1173. if (equals(entry.getValue())) {
  1174. return entry.getKey();
  1175. }
  1176. }
  1177. return null;
  1178. }
  1179. @Override
  1180. public void setStyleName(String styleName) {
  1181. tabState.styleName = styleName;
  1182. markAsDirty();
  1183. }
  1184. @Override
  1185. public String getStyleName() {
  1186. return tabState.styleName;
  1187. }
  1188. protected TabState getTabState() {
  1189. return tabState;
  1190. }
  1191. @Override
  1192. public void setId(String id) {
  1193. tabState.id = id;
  1194. markAsDirty();
  1195. }
  1196. @Override
  1197. public String getId() {
  1198. return tabState.id;
  1199. }
  1200. @Override
  1201. public void setIcon(Resource icon, String iconAltText) {
  1202. setResource(ComponentConstants.ICON_RESOURCE + tabState.key, icon);
  1203. tabState.iconAltText = iconAltText;
  1204. }
  1205. }
  1206. /**
  1207. * CloseHandler is used to process tab closing events. Default behavior is
  1208. * to remove the tab from the TabSheet.
  1209. *
  1210. * @author Jouni Koivuviita / Vaadin Ltd.
  1211. * @since 6.2.0
  1212. *
  1213. */
  1214. @FunctionalInterface
  1215. public interface CloseHandler extends Serializable {
  1216. /**
  1217. * Called when a user has pressed the close icon of a tab in the client
  1218. * side widget.
  1219. *
  1220. * @param tabsheet
  1221. * the TabSheet to which the tab belongs to
  1222. * @param tabContent
  1223. * the component that corresponds to the tab whose close
  1224. * button was clicked
  1225. */
  1226. void onTabClose(final TabSheet tabsheet, final Component tabContent);
  1227. }
  1228. /**
  1229. * Provide a custom {@link CloseHandler} for this TabSheet if you wish to
  1230. * perform some additional tasks when a user clicks on a tabs close button,
  1231. * e.g. show a confirmation dialogue before removing the tab.
  1232. *
  1233. * To remove the tab, if you provide your own close handler, you must call
  1234. * {@link #removeComponent(Component)} yourself.
  1235. *
  1236. * The default CloseHandler for TabSheet will only remove the tab.
  1237. *
  1238. * @param handler
  1239. */
  1240. public void setCloseHandler(CloseHandler handler) {
  1241. closeHandler = handler;
  1242. }
  1243. /**
  1244. * Sets the position of the tab.
  1245. *
  1246. * @param tab
  1247. * The tab
  1248. * @param position
  1249. * The new position of the tab
  1250. */
  1251. public void setTabPosition(Tab tab, int position) {
  1252. int oldPosition = getTabPosition(tab);
  1253. components.remove(oldPosition);
  1254. components.add(position, tab.getComponent());
  1255. getState().tabs.remove(oldPosition);
  1256. getState().tabs.add(position, ((TabSheetTabImpl) tab).getTabState());
  1257. }
  1258. /**
  1259. * Gets the position of the tab.
  1260. *
  1261. * @param tab
  1262. * The tab
  1263. * @return
  1264. */
  1265. public int getTabPosition(Tab tab) {
  1266. return components.indexOf(tab.getComponent());
  1267. }
  1268. @Override
  1269. public void focus() {
  1270. super.focus();
  1271. }
  1272. @Override
  1273. public int getTabIndex() {
  1274. return getState(false).tabIndex;
  1275. }
  1276. @Override
  1277. public void setTabIndex(int tabIndex) {
  1278. getState().tabIndex = tabIndex;
  1279. }
  1280. @Override
  1281. public Registration addBlurListener(BlurListener listener) {
  1282. return addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
  1283. BlurListener.blurMethod);
  1284. }
  1285. @Override
  1286. public Registration addFocusListener(FocusListener listener) {
  1287. return addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
  1288. FocusListener.focusMethod);
  1289. }
  1290. @Override
  1291. public boolean isRendered(Component childComponent) {
  1292. return childComponent == getSelectedTab();
  1293. }
  1294. /**
  1295. * Copies properties from one Tab to another.
  1296. *
  1297. * @param from
  1298. * The tab whose data to copy.
  1299. * @param to
  1300. * The tab to which copy the data.
  1301. */
  1302. private static void copyTabMetadata(Tab from, Tab to) {
  1303. to.setCaption(from.getCaption());
  1304. to.setIcon(from.getIcon(), from.getIconAlternateText());
  1305. to.setDescription(from.getDescription());
  1306. to.setVisible(from.isVisible());
  1307. to.setEnabled(from.isEnabled());
  1308. to.setClosable(from.isClosable());
  1309. to.setStyleName(from.getStyleName());
  1310. to.setComponentError(from.getComponentError());
  1311. }
  1312. @Override
  1313. protected TabsheetState getState(boolean markAsDirty) {
  1314. return (TabsheetState) super.getState(markAsDirty);
  1315. }
  1316. @Override
  1317. protected TabsheetState getState() {
  1318. return (TabsheetState) super.getState();
  1319. }
  1320. /*
  1321. * (non-Javadoc)
  1322. *
  1323. * @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element,
  1324. * com.vaadin.ui.declarative.DesignContext)
  1325. */
  1326. @Override
  1327. public void readDesign(Element design, DesignContext designContext) {
  1328. super.readDesign(design, designContext);
  1329. // create new tabs
  1330. for (Element tab : design.children()) {
  1331. if (!tab.tagName().equals("tab")) {
  1332. throw new DesignException(
  1333. "Invalid tag name for tabsheet tab " + tab.tagName());
  1334. }
  1335. readTabFromDesign(tab, designContext);
  1336. }
  1337. }
  1338. /**
  1339. * Reads the given tab element from design
  1340. *
  1341. * @since 7.4
  1342. *
  1343. * @param tabElement
  1344. * the element to be read
  1345. * @param designContext
  1346. * the design context
  1347. */
  1348. private void readTabFromDesign(Element tabElement,
  1349. DesignContext designContext) {
  1350. Attributes attr = tabElement.attributes();
  1351. if (tabElement.children().size() != 1) {
  1352. throw new DesignException(
  1353. "A tab must have exactly one child element");
  1354. }
  1355. // create the component that is in tab content
  1356. Element content = tabElement.child(0);
  1357. Component child = designContext.readDesign(content);
  1358. Tab tab = this.addTab(child);
  1359. if (attr.hasKey("visible")) {
  1360. tab.setVisible(DesignAttributeHandler.readAttribute("visible", attr,
  1361. Boolean.class));
  1362. }
  1363. if (attr.hasKey("closable")) {
  1364. tab.setClosable(DesignAttributeHandler.readAttribute("closable",
  1365. attr, Boolean.class));
  1366. }
  1367. if (attr.hasKey("caption")) {
  1368. tab.setCaption(DesignAttributeHandler.readAttribute("caption", attr,
  1369. String.class));
  1370. }
  1371. if (attr.hasKey("enabled")) {
  1372. tab.setEnabled(DesignAttributeHandler.readAttribute("enabled", attr,
  1373. Boolean.class));
  1374. }
  1375. if (attr.hasKey("icon")) {
  1376. tab.setIcon(DesignAttributeHandler.readAttribute("icon", attr,
  1377. Resource.class));
  1378. }
  1379. if (attr.hasKey("icon-alt")) {
  1380. tab.setIconAlternateText(DesignAttributeHandler
  1381. .readAttribute("icon-alt", attr, String.class));
  1382. }
  1383. if (attr.hasKey("description")) {
  1384. tab.setDescription(DesignAttributeHandler
  1385. .readAttribute("description", attr, String.class));
  1386. }
  1387. if (attr.hasKey("style-name")) {
  1388. tab.setStyleName(DesignAttributeHandler.readAttribute("style-name",
  1389. attr, String.class));
  1390. }
  1391. if (attr.hasKey("id")) {
  1392. tab.setId(DesignAttributeHandler.readAttribute("id", attr,
  1393. String.class));
  1394. }
  1395. if (attr.hasKey("selected")) {
  1396. boolean selected = DesignAttributeHandler.readAttribute("selected",
  1397. attr, Boolean.class);
  1398. if (selected) {
  1399. this.setSelectedTab(tab.getComponent(), false);
  1400. }
  1401. }
  1402. }
  1403. /**
  1404. * Writes the given tab to design
  1405. *
  1406. * @since 7.4
  1407. * @param design
  1408. * the design node for tabsheet
  1409. * @param designContext
  1410. * the design context
  1411. * @param tab
  1412. * the tab to be written
  1413. */
  1414. private void writeTabToDesign(Element design, DesignContext designContext,
  1415. Tab tab) {
  1416. // get default tab instance
  1417. Tab def = new TabSheetTabImpl(null, null, null);
  1418. // create element for tab
  1419. Element tabElement = design.appendElement("tab");
  1420. // add tab content
  1421. tabElement.appendChild(designContext.createElement(tab.getComponent()));
  1422. Attributes attr = tabElement.attributes();
  1423. // write attributes
  1424. DesignAttributeHandler.writeAttribute("visible", attr, tab.isVisible(),
  1425. def.isVisible(), Boolean.class, designContext);
  1426. DesignAttributeHandler.writeAttribute("closable", attr,
  1427. tab.isClosable(), def.isClosable(), Boolean.class,
  1428. designContext);
  1429. DesignAttributeHandler.writeAttribute("caption", attr, tab.getCaption(),
  1430. def.getCaption(), String.class, designContext);
  1431. DesignAttributeHandler.writeAttribute("enabled", attr, tab.isEnabled(),
  1432. def.isEnabled(), Boolean.class, designContext);
  1433. DesignAttributeHandler.writeAttribute("icon", attr, tab.getIcon(),
  1434. def.getIcon(), Resource.class, designContext);
  1435. DesignAttributeHandler.writeAttribute("icon-alt", attr,
  1436. tab.getIconAlternateText(), def.getIconAlternateText(),
  1437. String.class, designContext);
  1438. DesignAttributeHandler.writeAttribute("description", attr,
  1439. tab.getDescription(), def.getDescription(), String.class,
  1440. designContext);
  1441. DesignAttributeHandler.writeAttribute("style-name", attr,
  1442. tab.getStyleName(), def.getStyleName(), String.class,
  1443. designContext);
  1444. DesignAttributeHandler.writeAttribute("id", attr, tab.getId(),
  1445. def.getId(), String.class, designContext);
  1446. if (getSelectedTab() != null
  1447. && getSelectedTab().equals(tab.getComponent())) {
  1448. // use write attribute to get consistent handling for boolean
  1449. DesignAttributeHandler.writeAttribute("selected", attr, true, false,
  1450. boolean.class, designContext);
  1451. }
  1452. }
  1453. /*
  1454. * (non-Javadoc)
  1455. *
  1456. * @see com.vaadin.ui.AbstractComponent#getCustomAttributes()
  1457. */
  1458. @Override
  1459. protected Collection<String> getCustomAttributes() {
  1460. Collection<String> attributes = super.getCustomAttributes();
  1461. // no need to list tab attributes since they are considered internal
  1462. return attributes;
  1463. }
  1464. /*
  1465. * (non-Javadoc)
  1466. *
  1467. * @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Element
  1468. * , com.vaadin.ui.declarative.DesignContext)
  1469. */
  1470. @Override
  1471. public void writeDesign(Element design, DesignContext designContext) {
  1472. super.writeDesign(design, designContext);
  1473. TabSheet def = designContext.getDefaultInstance(this);
  1474. Attributes attr = design.attributes();
  1475. // write tabs
  1476. if (!designContext.shouldWriteChildren(this, def)) {
  1477. return;
  1478. }
  1479. for (Component component : this) {
  1480. Tab tab = this.getTab(component);
  1481. writeTabToDesign(design, designContext, tab);
  1482. }
  1483. }
  1484. /**
  1485. * Sets whether HTML is allowed in the tab captions.
  1486. * <p>
  1487. * If set to true, the captions are rendered in the browser as HTML and the
  1488. * developer is responsible for ensuring no harmful HTML is used. If set to
  1489. * false, the content is rendered in the browser as plain text.
  1490. * <p>
  1491. * The default is false, i.e. render tab captions as plain text
  1492. *
  1493. * @param tabCaptionsAsHtml
  1494. * true if the tab captions are rendered as HTML, false if
  1495. * rendered as plain text
  1496. * @since 7.4
  1497. */
  1498. public void setTabCaptionsAsHtml(boolean tabCaptionsAsHtml) {
  1499. getState().tabCaptionsAsHtml = tabCaptionsAsHtml;
  1500. }
  1501. /**
  1502. * Checks whether HTML is allowed in the tab captions.
  1503. * <p>
  1504. * The default is false, i.e. render tab captions as plain text
  1505. *
  1506. * @return true if the tab captions are rendered as HTML, false if rendered
  1507. * as plain text
  1508. * @since 7.4
  1509. */
  1510. public boolean isTabCaptionsAsHtml() {
  1511. return getState(false).tabCaptionsAsHtml;
  1512. }
  1513. }