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

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