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

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