Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

TabSheet.java 51KB

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