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.

VTabsheet.java 34KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.Iterator;
  6. import com.google.gwt.core.client.Scheduler;
  7. import com.google.gwt.dom.client.DivElement;
  8. import com.google.gwt.dom.client.Style;
  9. import com.google.gwt.dom.client.TableCellElement;
  10. import com.google.gwt.dom.client.TableElement;
  11. import com.google.gwt.event.dom.client.ClickEvent;
  12. import com.google.gwt.event.dom.client.ClickHandler;
  13. import com.google.gwt.user.client.Command;
  14. import com.google.gwt.user.client.DOM;
  15. import com.google.gwt.user.client.Element;
  16. import com.google.gwt.user.client.Event;
  17. import com.google.gwt.user.client.ui.ComplexPanel;
  18. import com.google.gwt.user.client.ui.SimplePanel;
  19. import com.google.gwt.user.client.ui.Widget;
  20. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  21. import com.vaadin.terminal.gwt.client.BrowserInfo;
  22. import com.vaadin.terminal.gwt.client.ComponentConnector;
  23. import com.vaadin.terminal.gwt.client.ComponentState;
  24. import com.vaadin.terminal.gwt.client.ConnectorMap;
  25. import com.vaadin.terminal.gwt.client.TooltipInfo;
  26. import com.vaadin.terminal.gwt.client.UIDL;
  27. import com.vaadin.terminal.gwt.client.Util;
  28. import com.vaadin.terminal.gwt.client.VCaption;
  29. import com.vaadin.terminal.gwt.client.ui.label.VLabel;
  30. public class VTabsheet extends VTabsheetBase {
  31. private static class VCloseEvent {
  32. private Tab tab;
  33. VCloseEvent(Tab tab) {
  34. this.tab = tab;
  35. }
  36. public Tab getTab() {
  37. return tab;
  38. }
  39. }
  40. private interface VCloseHandler {
  41. public void onClose(VCloseEvent event);
  42. }
  43. /**
  44. * Representation of a single "tab" shown in the TabBar
  45. *
  46. */
  47. private static class Tab extends SimplePanel {
  48. private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell";
  49. private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME
  50. + "-first";
  51. private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME
  52. + "-selected";
  53. private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME
  54. + "-first";
  55. private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME
  56. + "-disabled";
  57. private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem";
  58. private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME
  59. + "-selected";
  60. private TabCaption tabCaption;
  61. Element td = getElement();
  62. private VCloseHandler closeHandler;
  63. private boolean enabledOnServer = true;
  64. private Element div;
  65. private TabBar tabBar;
  66. private boolean hiddenOnServer = false;
  67. private String styleName;
  68. private Tab(TabBar tabBar) {
  69. super(DOM.createTD());
  70. this.tabBar = tabBar;
  71. setStyleName(td, TD_CLASSNAME);
  72. div = DOM.createDiv();
  73. setStyleName(div, DIV_CLASSNAME);
  74. DOM.appendChild(td, div);
  75. tabCaption = new TabCaption(this, getTabsheet()
  76. .getApplicationConnection());
  77. add(tabCaption);
  78. }
  79. public boolean isHiddenOnServer() {
  80. return hiddenOnServer;
  81. }
  82. public void setHiddenOnServer(boolean hiddenOnServer) {
  83. this.hiddenOnServer = hiddenOnServer;
  84. }
  85. @Override
  86. protected Element getContainerElement() {
  87. // Attach caption element to div, not td
  88. return div;
  89. }
  90. public boolean isEnabledOnServer() {
  91. return enabledOnServer;
  92. }
  93. public void setEnabledOnServer(boolean enabled) {
  94. enabledOnServer = enabled;
  95. setStyleName(td, TD_DISABLED_CLASSNAME, !enabled);
  96. }
  97. public void addClickHandler(ClickHandler handler) {
  98. tabCaption.addClickHandler(handler);
  99. }
  100. public void setCloseHandler(VCloseHandler closeHandler) {
  101. this.closeHandler = closeHandler;
  102. }
  103. /**
  104. * Toggles the style names for the Tab
  105. *
  106. * @param selected
  107. * true if the Tab is selected
  108. * @param first
  109. * true if the Tab is the first visible Tab
  110. */
  111. public void setStyleNames(boolean selected, boolean first) {
  112. setStyleName(td, TD_FIRST_CLASSNAME, first);
  113. setStyleName(td, TD_SELECTED_CLASSNAME, selected);
  114. setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first);
  115. setStyleName(div, DIV_SELECTED_CLASSNAME, selected);
  116. }
  117. public void onClose() {
  118. closeHandler.onClose(new VCloseEvent(this));
  119. }
  120. public VTabsheet getTabsheet() {
  121. return tabBar.getTabsheet();
  122. }
  123. public void updateFromUIDL(UIDL tabUidl) {
  124. tabCaption.updateCaption(tabUidl);
  125. // Apply the styleName set for the tab
  126. String newStyleName = tabUidl.getStringAttribute(TAB_STYLE_NAME);
  127. // Find the nth td element
  128. if (newStyleName != null && newStyleName.length() != 0) {
  129. if (!newStyleName.equals(styleName)) {
  130. // If we have a new style name
  131. if (styleName != null && styleName.length() != 0) {
  132. // Remove old style name if present
  133. td.removeClassName(TD_CLASSNAME + "-" + styleName);
  134. }
  135. // Set new style name
  136. td.addClassName(TD_CLASSNAME + "-" + newStyleName);
  137. styleName = newStyleName;
  138. }
  139. } else if (styleName != null) {
  140. // Remove the set stylename if no stylename is present in the
  141. // uidl
  142. td.removeClassName(TD_CLASSNAME + "-" + styleName);
  143. styleName = null;
  144. }
  145. }
  146. public void recalculateCaptionWidth() {
  147. tabCaption.setWidth(tabCaption.getRequiredWidth() + "px");
  148. }
  149. }
  150. private static class TabCaption extends VCaption {
  151. private boolean closable = false;
  152. private Element closeButton;
  153. private Tab tab;
  154. private ApplicationConnection client;
  155. TabCaption(Tab tab, ApplicationConnection client) {
  156. super(client);
  157. this.client = client;
  158. this.tab = tab;
  159. }
  160. @Override
  161. public boolean updateCaption(UIDL uidl) {
  162. if (uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION)
  163. || uidl.hasAttribute(AbstractComponentConnector.ATTRIBUTE_ERROR)) {
  164. TooltipInfo tooltipInfo = new TooltipInfo();
  165. tooltipInfo
  166. .setTitle(uidl
  167. .getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION));
  168. if (uidl.hasAttribute(AbstractComponentConnector.ATTRIBUTE_ERROR)) {
  169. tooltipInfo.setErrorUidl(uidl.getErrors());
  170. }
  171. client.registerTooltip(getTabsheet(), getElement(), tooltipInfo);
  172. } else {
  173. client.registerTooltip(getTabsheet(), getElement(), null);
  174. }
  175. // TODO need to call this instead of super because the caption does
  176. // not have an owner
  177. boolean ret = updateCaptionWithoutOwner(
  178. uidl,
  179. uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_CAPTION),
  180. uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DISABLED),
  181. uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION));
  182. setClosable(uidl.hasAttribute("closable"));
  183. return ret;
  184. }
  185. private VTabsheet getTabsheet() {
  186. return tab.getTabsheet();
  187. }
  188. @Override
  189. public void onBrowserEvent(Event event) {
  190. if (closable && event.getTypeInt() == Event.ONCLICK
  191. && event.getEventTarget().cast() == closeButton) {
  192. tab.onClose();
  193. event.stopPropagation();
  194. event.preventDefault();
  195. }
  196. super.onBrowserEvent(event);
  197. if (event.getTypeInt() == Event.ONLOAD) {
  198. getTabsheet().tabSizeMightHaveChanged(getTab());
  199. }
  200. client.handleTooltipEvent(event, getTabsheet(), getElement());
  201. }
  202. public Tab getTab() {
  203. return tab;
  204. }
  205. public void setClosable(boolean closable) {
  206. this.closable = closable;
  207. if (closable && closeButton == null) {
  208. closeButton = DOM.createSpan();
  209. closeButton.setInnerHTML("×");
  210. closeButton
  211. .setClassName(VTabsheet.CLASSNAME + "-caption-close");
  212. getElement().insertBefore(closeButton,
  213. getElement().getLastChild());
  214. } else if (!closable && closeButton != null) {
  215. getElement().removeChild(closeButton);
  216. closeButton = null;
  217. }
  218. if (closable) {
  219. addStyleDependentName("closable");
  220. } else {
  221. removeStyleDependentName("closable");
  222. }
  223. }
  224. @Override
  225. public int getRequiredWidth() {
  226. int width = super.getRequiredWidth();
  227. if (closeButton != null) {
  228. width += Util.getRequiredWidth(closeButton);
  229. }
  230. return width;
  231. }
  232. }
  233. static class TabBar extends ComplexPanel implements ClickHandler,
  234. VCloseHandler {
  235. private final Element tr = DOM.createTR();
  236. private final Element spacerTd = DOM.createTD();
  237. private Tab selected;
  238. private VTabsheet tabsheet;
  239. TabBar(VTabsheet tabsheet) {
  240. this.tabsheet = tabsheet;
  241. Element el = DOM.createTable();
  242. Element tbody = DOM.createTBody();
  243. DOM.appendChild(el, tbody);
  244. DOM.appendChild(tbody, tr);
  245. setStyleName(spacerTd, CLASSNAME + "-spacertd");
  246. DOM.appendChild(tr, spacerTd);
  247. DOM.appendChild(spacerTd, DOM.createDiv());
  248. setElement(el);
  249. }
  250. public void onClose(VCloseEvent event) {
  251. Tab tab = event.getTab();
  252. if (!tab.isEnabledOnServer()) {
  253. return;
  254. }
  255. int tabIndex = getWidgetIndex(tab);
  256. getTabsheet().sendTabClosedEvent(tabIndex);
  257. }
  258. protected Element getContainerElement() {
  259. return tr;
  260. }
  261. public int getTabCount() {
  262. return getWidgetCount();
  263. }
  264. public Tab addTab() {
  265. Tab t = new Tab(this);
  266. int tabIndex = getTabCount();
  267. // Logical attach
  268. insert(t, tr, tabIndex, true);
  269. if (tabIndex == 0) {
  270. // Set the "first" style
  271. t.setStyleNames(false, true);
  272. }
  273. t.addClickHandler(this);
  274. t.setCloseHandler(this);
  275. return t;
  276. }
  277. public void onClick(ClickEvent event) {
  278. Widget caption = (Widget) event.getSource();
  279. int index = getWidgetIndex(caption.getParent());
  280. getTabsheet().onTabSelected(index);
  281. }
  282. public VTabsheet getTabsheet() {
  283. return tabsheet;
  284. }
  285. public Tab getTab(int index) {
  286. if (index < 0 || index >= getTabCount()) {
  287. return null;
  288. }
  289. return (Tab) super.getWidget(index);
  290. }
  291. public void selectTab(int index) {
  292. final Tab newSelected = getTab(index);
  293. final Tab oldSelected = selected;
  294. newSelected.setStyleNames(true, isFirstVisibleTab(index));
  295. if (oldSelected != null && oldSelected != newSelected) {
  296. oldSelected.setStyleNames(false,
  297. isFirstVisibleTab(getWidgetIndex(oldSelected)));
  298. }
  299. // Update the field holding the currently selected tab
  300. selected = newSelected;
  301. // The selected tab might need more (or less) space
  302. newSelected.recalculateCaptionWidth();
  303. getTab(tabsheet.activeTabIndex).recalculateCaptionWidth();
  304. }
  305. public void removeTab(int i) {
  306. Tab tab = getTab(i);
  307. if (tab == null) {
  308. return;
  309. }
  310. remove(tab);
  311. /*
  312. * If this widget was selected we need to unmark it as the last
  313. * selected
  314. */
  315. if (tab == selected) {
  316. selected = null;
  317. }
  318. // FIXME: Shouldn't something be selected instead?
  319. }
  320. private boolean isFirstVisibleTab(int index) {
  321. return getFirstVisibleTab() == index;
  322. }
  323. /**
  324. * Returns the index of the first visible tab
  325. *
  326. * @return
  327. */
  328. private int getFirstVisibleTab() {
  329. return getNextVisibleTab(-1);
  330. }
  331. /**
  332. * Find the next visible tab. Returns -1 if none is found.
  333. *
  334. * @param i
  335. * @return
  336. */
  337. private int getNextVisibleTab(int i) {
  338. int tabs = getTabCount();
  339. do {
  340. i++;
  341. } while (i < tabs && getTab(i).isHiddenOnServer());
  342. if (i == tabs) {
  343. return -1;
  344. } else {
  345. return i;
  346. }
  347. }
  348. /**
  349. * Find the previous visible tab. Returns -1 if none is found.
  350. *
  351. * @param i
  352. * @return
  353. */
  354. private int getPreviousVisibleTab(int i) {
  355. do {
  356. i--;
  357. } while (i >= 0 && getTab(i).isHiddenOnServer());
  358. return i;
  359. }
  360. public int scrollLeft(int currentFirstVisible) {
  361. int prevVisible = getPreviousVisibleTab(currentFirstVisible);
  362. if (prevVisible == -1) {
  363. return -1;
  364. }
  365. Tab newFirst = getTab(prevVisible);
  366. newFirst.setVisible(true);
  367. newFirst.recalculateCaptionWidth();
  368. return prevVisible;
  369. }
  370. public int scrollRight(int currentFirstVisible) {
  371. int nextVisible = getNextVisibleTab(currentFirstVisible);
  372. if (nextVisible == -1) {
  373. return -1;
  374. }
  375. Tab currentFirst = getTab(currentFirstVisible);
  376. currentFirst.setVisible(false);
  377. currentFirst.recalculateCaptionWidth();
  378. return nextVisible;
  379. }
  380. }
  381. public static final String CLASSNAME = "v-tabsheet";
  382. public static final String TABS_CLASSNAME = "v-tabsheet-tabcontainer";
  383. public static final String SCROLLER_CLASSNAME = "v-tabsheet-scroller";
  384. // Can't use "style" as it's already in use
  385. public static final String TAB_STYLE_NAME = "tabstyle";
  386. final Element tabs; // tabbar and 'scroller' container
  387. private final Element scroller; // tab-scroller element
  388. private final Element scrollerNext; // tab-scroller next button element
  389. private final Element scrollerPrev; // tab-scroller prev button element
  390. /**
  391. * The index of the first visible tab (when scrolled)
  392. */
  393. private int scrollerIndex = 0;
  394. private final TabBar tb = new TabBar(this);
  395. final VTabsheetPanel tp = new VTabsheetPanel();
  396. final Element contentNode;
  397. private final Element deco;
  398. boolean waitingForResponse;
  399. /**
  400. * Previous visible widget is set invisible with CSS (not display: none, but
  401. * visibility: hidden), to avoid flickering during render process. Normal
  402. * visibility must be returned later when new widget is rendered.
  403. */
  404. private Widget previousVisibleWidget;
  405. private String currentStyle;
  406. private void onTabSelected(final int tabIndex) {
  407. if (disabled || waitingForResponse) {
  408. return;
  409. }
  410. final Object tabKey = tabKeys.get(tabIndex);
  411. if (disabledTabKeys.contains(tabKey)) {
  412. return;
  413. }
  414. if (client != null && activeTabIndex != tabIndex) {
  415. tb.selectTab(tabIndex);
  416. addStyleDependentName("loading");
  417. // run updating variables in deferred command to bypass some FF
  418. // optimization issues
  419. Scheduler.get().scheduleDeferred(new Command() {
  420. public void execute() {
  421. previousVisibleWidget = tp.getWidget(tp.getVisibleWidget());
  422. DOM.setStyleAttribute(
  423. DOM.getParent(previousVisibleWidget.getElement()),
  424. "visibility", "hidden");
  425. client.updateVariable(id, "selected", tabKeys.get(tabIndex)
  426. .toString(), true);
  427. }
  428. });
  429. waitingForResponse = true;
  430. }
  431. }
  432. public ApplicationConnection getApplicationConnection() {
  433. return client;
  434. }
  435. public void tabSizeMightHaveChanged(Tab tab) {
  436. // icon onloads may change total width of tabsheet
  437. if (isDynamicWidth()) {
  438. updateDynamicWidth();
  439. }
  440. updateTabScroller();
  441. }
  442. void sendTabClosedEvent(int tabIndex) {
  443. client.updateVariable(id, "close", tabKeys.get(tabIndex), true);
  444. }
  445. boolean isDynamicWidth() {
  446. ComponentConnector paintable = ConnectorMap.get(client).getConnector(
  447. this);
  448. return paintable.isUndefinedWidth();
  449. }
  450. boolean isDynamicHeight() {
  451. ComponentConnector paintable = ConnectorMap.get(client).getConnector(
  452. this);
  453. return paintable.isUndefinedHeight();
  454. }
  455. public VTabsheet() {
  456. super(CLASSNAME);
  457. // Tab scrolling
  458. DOM.setStyleAttribute(getElement(), "overflow", "hidden");
  459. tabs = DOM.createDiv();
  460. DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
  461. scroller = DOM.createDiv();
  462. DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
  463. scrollerPrev = DOM.createButton();
  464. DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
  465. + "Prev");
  466. DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
  467. scrollerNext = DOM.createButton();
  468. DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
  469. + "Next");
  470. DOM.sinkEvents(scrollerNext, Event.ONCLICK);
  471. DOM.appendChild(getElement(), tabs);
  472. // Tabs
  473. tp.setStyleName(CLASSNAME + "-tabsheetpanel");
  474. contentNode = DOM.createDiv();
  475. deco = DOM.createDiv();
  476. addStyleDependentName("loading"); // Indicate initial progress
  477. tb.setStyleName(CLASSNAME + "-tabs");
  478. DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content");
  479. DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
  480. add(tb, tabs);
  481. DOM.appendChild(scroller, scrollerPrev);
  482. DOM.appendChild(scroller, scrollerNext);
  483. DOM.appendChild(getElement(), contentNode);
  484. add(tp, contentNode);
  485. DOM.appendChild(getElement(), deco);
  486. DOM.appendChild(tabs, scroller);
  487. // TODO Use for Safari only. Fix annoying 1px first cell in TabBar.
  488. // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM
  489. // .getFirstChild(tb.getElement()))), "display", "none");
  490. }
  491. @Override
  492. public void onBrowserEvent(Event event) {
  493. // Tab scrolling
  494. if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
  495. int newFirstIndex = tb.scrollLeft(scrollerIndex);
  496. if (newFirstIndex != -1) {
  497. scrollerIndex = newFirstIndex;
  498. updateTabScroller();
  499. }
  500. } else if (isClippedTabs() && DOM.eventGetTarget(event) == scrollerNext) {
  501. int newFirstIndex = tb.scrollRight(scrollerIndex);
  502. if (newFirstIndex != -1) {
  503. scrollerIndex = newFirstIndex;
  504. updateTabScroller();
  505. }
  506. } else {
  507. super.onBrowserEvent(event);
  508. }
  509. }
  510. /**
  511. * Checks if the tab with the selected index has been scrolled out of the
  512. * view (on the left side).
  513. *
  514. * @param index
  515. * @return
  516. */
  517. private boolean scrolledOutOfView(int index) {
  518. return scrollerIndex > index;
  519. }
  520. void handleStyleNames(UIDL uidl, ComponentState state) {
  521. // Add proper stylenames for all elements (easier to prevent unwanted
  522. // style inheritance)
  523. if (state.hasStyles()) {
  524. final String style = state.getStyle();
  525. if (currentStyle != style) {
  526. currentStyle = style;
  527. final String[] styles = style.split(" ");
  528. final String tabsBaseClass = TABS_CLASSNAME;
  529. String tabsClass = tabsBaseClass;
  530. final String contentBaseClass = CLASSNAME + "-content";
  531. String contentClass = contentBaseClass;
  532. final String decoBaseClass = CLASSNAME + "-deco";
  533. String decoClass = decoBaseClass;
  534. for (int i = 0; i < styles.length; i++) {
  535. tb.addStyleDependentName(styles[i]);
  536. tabsClass += " " + tabsBaseClass + "-" + styles[i];
  537. contentClass += " " + contentBaseClass + "-" + styles[i];
  538. decoClass += " " + decoBaseClass + "-" + styles[i];
  539. }
  540. DOM.setElementProperty(tabs, "className", tabsClass);
  541. DOM.setElementProperty(contentNode, "className", contentClass);
  542. DOM.setElementProperty(deco, "className", decoClass);
  543. borderW = -1;
  544. }
  545. } else {
  546. tb.setStyleName(CLASSNAME + "-tabs");
  547. DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
  548. DOM.setElementProperty(contentNode, "className", CLASSNAME
  549. + "-content");
  550. DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
  551. }
  552. if (uidl.hasAttribute("hidetabs")) {
  553. tb.setVisible(false);
  554. addStyleName(CLASSNAME + "-hidetabs");
  555. } else {
  556. tb.setVisible(true);
  557. removeStyleName(CLASSNAME + "-hidetabs");
  558. }
  559. }
  560. void updateDynamicWidth() {
  561. // Find width consumed by tabs
  562. TableCellElement spacerCell = ((TableElement) tb.getElement().cast())
  563. .getRows().getItem(0).getCells().getItem(tb.getTabCount());
  564. int spacerWidth = spacerCell.getOffsetWidth();
  565. DivElement div = (DivElement) spacerCell.getFirstChildElement();
  566. int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth();
  567. int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth;
  568. // Find content width
  569. Style style = tp.getElement().getStyle();
  570. String overflow = style.getProperty("overflow");
  571. style.setProperty("overflow", "hidden");
  572. style.setPropertyPx("width", tabsWidth);
  573. boolean hasTabs = tp.getWidgetCount() > 0;
  574. Style wrapperstyle = null;
  575. if (hasTabs) {
  576. wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement()
  577. .getParentElement().getStyle();
  578. wrapperstyle.setPropertyPx("width", tabsWidth);
  579. }
  580. // Get content width from actual widget
  581. int contentWidth = 0;
  582. if (hasTabs) {
  583. contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth();
  584. }
  585. style.setProperty("overflow", overflow);
  586. // Set widths to max(tabs,content)
  587. if (tabsWidth < contentWidth) {
  588. tabsWidth = contentWidth;
  589. }
  590. int outerWidth = tabsWidth + getContentAreaBorderWidth();
  591. tabs.getStyle().setPropertyPx("width", outerWidth);
  592. style.setPropertyPx("width", tabsWidth);
  593. if (hasTabs) {
  594. wrapperstyle.setPropertyPx("width", tabsWidth);
  595. }
  596. contentNode.getStyle().setPropertyPx("width", tabsWidth);
  597. super.setWidth(outerWidth + "px");
  598. updateOpenTabSize();
  599. }
  600. @Override
  601. protected void renderTab(final UIDL tabUidl, int index, boolean selected,
  602. boolean hidden) {
  603. Tab tab = tb.getTab(index);
  604. if (tab == null) {
  605. tab = tb.addTab();
  606. }
  607. tab.updateFromUIDL(tabUidl);
  608. tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index))));
  609. tab.setHiddenOnServer(hidden);
  610. if (scrolledOutOfView(index)) {
  611. // Should not set tabs visible if they are scrolled out of view
  612. hidden = true;
  613. }
  614. // Set the current visibility of the tab (in the browser)
  615. tab.setVisible(!hidden);
  616. /*
  617. * Force the width of the caption container so the content will not wrap
  618. * and tabs won't be too narrow in certain browsers
  619. */
  620. tab.recalculateCaptionWidth();
  621. UIDL tabContentUIDL = null;
  622. ComponentConnector tabContentPaintable = null;
  623. Widget tabContentWidget = null;
  624. if (tabUidl.getChildCount() > 0) {
  625. tabContentUIDL = tabUidl.getChildUIDL(0);
  626. tabContentPaintable = client.getPaintable(tabContentUIDL);
  627. tabContentWidget = tabContentPaintable.getWidget();
  628. }
  629. if (tabContentPaintable != null) {
  630. /* This is a tab with content information */
  631. int oldIndex = tp.getWidgetIndex(tabContentWidget);
  632. if (oldIndex != -1 && oldIndex != index) {
  633. /*
  634. * The tab has previously been rendered in another position so
  635. * we must move the cached content to correct position
  636. */
  637. tp.insert(tabContentWidget, index);
  638. }
  639. } else {
  640. /* A tab whose content has not yet been loaded */
  641. /*
  642. * Make sure there is a corresponding empty tab in tp. The same
  643. * operation as the moving above but for not-loaded tabs.
  644. */
  645. if (index < tp.getWidgetCount()) {
  646. Widget oldWidget = tp.getWidget(index);
  647. if (!(oldWidget instanceof PlaceHolder)) {
  648. tp.insert(new PlaceHolder(), index);
  649. }
  650. }
  651. }
  652. if (selected) {
  653. renderContent(tabContentUIDL);
  654. tb.selectTab(index);
  655. } else {
  656. if (tabContentUIDL != null) {
  657. // updating a drawn child on hidden tab
  658. if (tp.getWidgetIndex(tabContentWidget) < 0) {
  659. tp.insert(tabContentWidget, index);
  660. }
  661. tabContentPaintable.updateFromUIDL(tabContentUIDL, client);
  662. } else if (tp.getWidgetCount() <= index) {
  663. tp.add(new PlaceHolder());
  664. }
  665. }
  666. }
  667. public class PlaceHolder extends VLabel {
  668. public PlaceHolder() {
  669. super("");
  670. }
  671. }
  672. @Override
  673. protected void selectTab(int index, final UIDL contentUidl) {
  674. if (index != activeTabIndex) {
  675. activeTabIndex = index;
  676. tb.selectTab(activeTabIndex);
  677. }
  678. renderContent(contentUidl);
  679. }
  680. private void renderContent(final UIDL contentUIDL) {
  681. final ComponentConnector content = client.getPaintable(contentUIDL);
  682. if (tp.getWidgetCount() > activeTabIndex) {
  683. Widget old = tp.getWidget(activeTabIndex);
  684. if (old != content) {
  685. tp.remove(activeTabIndex);
  686. ConnectorMap paintableMap = ConnectorMap.get(client);
  687. if (paintableMap.isConnector(old)) {
  688. paintableMap.unregisterConnector(paintableMap
  689. .getConnector(old));
  690. }
  691. tp.insert(content.getWidget(), activeTabIndex);
  692. }
  693. } else {
  694. tp.add(content.getWidget());
  695. }
  696. tp.showWidget(activeTabIndex);
  697. VTabsheet.this.iLayout();
  698. content.updateFromUIDL(contentUIDL, client);
  699. /*
  700. * The size of a cached, relative sized component must be updated to
  701. * report correct size to updateOpenTabSize().
  702. */
  703. if (contentUIDL.getBooleanAttribute("cached")) {
  704. client.handleComponentRelativeSize(content.getWidget());
  705. }
  706. updateOpenTabSize();
  707. VTabsheet.this.removeStyleDependentName("loading");
  708. if (previousVisibleWidget != null) {
  709. DOM.setStyleAttribute(
  710. DOM.getParent(previousVisibleWidget.getElement()),
  711. "visibility", "");
  712. previousVisibleWidget = null;
  713. }
  714. }
  715. void updateContentNodeHeight() {
  716. if (!isDynamicHeight()) {
  717. int contentHeight = getOffsetHeight();
  718. contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight");
  719. contentHeight -= tb.getOffsetHeight();
  720. if (contentHeight < 0) {
  721. contentHeight = 0;
  722. }
  723. // Set proper values for content element
  724. DOM.setStyleAttribute(contentNode, "height", contentHeight + "px");
  725. } else {
  726. DOM.setStyleAttribute(contentNode, "height", "");
  727. }
  728. }
  729. public void iLayout() {
  730. updateTabScroller();
  731. tp.runWebkitOverflowAutoFix();
  732. }
  733. /**
  734. * Sets the size of the visible tab (component). As the tab is set to
  735. * position: absolute (to work around a firefox flickering bug) we must keep
  736. * this up-to-date by hand.
  737. */
  738. void updateOpenTabSize() {
  739. /*
  740. * The overflow=auto element must have a height specified, otherwise it
  741. * will be just as high as the contents and no scrollbars will appear
  742. */
  743. int height = -1;
  744. int width = -1;
  745. int minWidth = 0;
  746. if (!isDynamicHeight()) {
  747. height = contentNode.getOffsetHeight();
  748. }
  749. if (!isDynamicWidth()) {
  750. width = contentNode.getOffsetWidth();
  751. } else {
  752. /*
  753. * If the tabbar is wider than the content we need to use the tabbar
  754. * width as minimum width so scrollbars get placed correctly (at the
  755. * right edge).
  756. */
  757. minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth();
  758. }
  759. tp.fixVisibleTabSize(width, height, minWidth);
  760. }
  761. /**
  762. * Layouts the tab-scroller elements, and applies styles.
  763. */
  764. private void updateTabScroller() {
  765. if (!isDynamicWidth()) {
  766. ComponentConnector paintable = ConnectorMap.get(client)
  767. .getConnector(this);
  768. DOM.setStyleAttribute(tabs, "width", paintable.getDeclaredWidth());
  769. }
  770. // Make sure scrollerIndex is valid
  771. if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) {
  772. scrollerIndex = tb.getFirstVisibleTab();
  773. } else if (tb.getTabCount() > 0
  774. && tb.getTab(scrollerIndex).isHiddenOnServer()) {
  775. scrollerIndex = tb.getNextVisibleTab(scrollerIndex);
  776. }
  777. boolean scrolled = isScrolledTabs();
  778. boolean clipped = isClippedTabs();
  779. if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) {
  780. DOM.setStyleAttribute(scroller, "display", "");
  781. DOM.setElementProperty(scrollerPrev, "className",
  782. SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
  783. DOM.setElementProperty(scrollerNext, "className",
  784. SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
  785. } else {
  786. DOM.setStyleAttribute(scroller, "display", "none");
  787. }
  788. if (BrowserInfo.get().isSafari()) {
  789. // fix tab height for safari, bugs sometimes if tabs contain icons
  790. String property = tabs.getStyle().getProperty("height");
  791. if (property == null || property.equals("")) {
  792. tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight());
  793. }
  794. /*
  795. * another hack for webkits. tabscroller sometimes drops without
  796. * "shaking it" reproducable in
  797. * com.vaadin.tests.components.tabsheet.TabSheetIcons
  798. */
  799. final Style style = scroller.getStyle();
  800. style.setProperty("whiteSpace", "normal");
  801. Scheduler.get().scheduleDeferred(new Command() {
  802. public void execute() {
  803. style.setProperty("whiteSpace", "");
  804. }
  805. });
  806. }
  807. }
  808. void showAllTabs() {
  809. scrollerIndex = tb.getFirstVisibleTab();
  810. for (int i = 0; i < tb.getTabCount(); i++) {
  811. Tab t = tb.getTab(i);
  812. if (!t.isHiddenOnServer()) {
  813. t.setVisible(true);
  814. }
  815. }
  816. }
  817. private boolean isScrolledTabs() {
  818. return scrollerIndex > tb.getFirstVisibleTab();
  819. }
  820. private boolean isClippedTabs() {
  821. return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb
  822. .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth()
  823. - (isScrolledTabs() ? scroller.getOffsetWidth() : 0);
  824. }
  825. @Override
  826. protected void clearPaintables() {
  827. int i = tb.getTabCount();
  828. while (i > 0) {
  829. tb.removeTab(--i);
  830. }
  831. tp.clear();
  832. }
  833. @Override
  834. protected Iterator<Widget> getWidgetIterator() {
  835. return tp.iterator();
  836. }
  837. private int borderW = -1;
  838. int getContentAreaBorderWidth() {
  839. if (borderW < 0) {
  840. borderW = Util.measureHorizontalBorder(contentNode);
  841. }
  842. return borderW;
  843. }
  844. @Override
  845. protected int getTabCount() {
  846. return tb.getTabCount();
  847. }
  848. @Override
  849. protected ComponentConnector getTab(int index) {
  850. if (tp.getWidgetCount() > index) {
  851. Widget widget = tp.getWidget(index);
  852. return ConnectorMap.get(client).getConnector(widget);
  853. }
  854. return null;
  855. }
  856. @Override
  857. protected void removeTab(int index) {
  858. tb.removeTab(index);
  859. /*
  860. * This must be checked because renderTab automatically removes the
  861. * active tab content when it changes
  862. */
  863. if (tp.getWidgetCount() > index) {
  864. tp.remove(index);
  865. }
  866. }
  867. }