/* * Ext GWT - Ext for GWT * Copyright(c) 2007-2009, Ext JS, LLC. * licensing@extjs.com * * http://extjs.com/license */ package com.extjs.gxt.ui.client.widget; import java.util.ArrayList; import java.util.List; import java.util.Stack; import com.extjs.gxt.ui.client.GXT; import com.extjs.gxt.ui.client.aria.FocusFrame; import com.extjs.gxt.ui.client.core.El; import com.extjs.gxt.ui.client.core.Template; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.ComponentEvent; import com.extjs.gxt.ui.client.event.ContainerEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.FxEvent; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.event.MenuEvent; import com.extjs.gxt.ui.client.event.SelectionListener; import com.extjs.gxt.ui.client.event.TabPanelEvent; import com.extjs.gxt.ui.client.fx.FxConfig; import com.extjs.gxt.ui.client.util.KeyNav; import com.extjs.gxt.ui.client.util.Params; import com.extjs.gxt.ui.client.util.Size; import com.extjs.gxt.ui.client.widget.layout.CardLayout; import com.extjs.gxt.ui.client.widget.menu.Menu; import com.extjs.gxt.ui.client.widget.menu.MenuItem; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DeferredCommand; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Accessibility; /** * A basic tab container. * * <pre> * TabPanel panel = new TabPanel(); * panel.setResizeTabs(true); * panel.setEnableTabScroll(true); * panel.setAnimScroll(true); * * TabItem item = new TabItem(); * item.setClosable(true); * item.setText("Tab Item"); * * item.setLayout(new FitLayout()); * item.add(new Label("Test Content")); * * panel.add(item); * </pre> * * <dl> * <dt><b>Events:</b></dt> * * <dd><b>BeforeSelect</b> : TabPanelEvent(container, item)<br> * <div>Fires after an item is selected. Listeners can cancel the action by calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>container : this</li> * <li>item : the item about to be selected.</li> * </ul> * </dd> * * <dd><b>Select</b> : TabPanelEvent(container, item)<br> * <div>Fires after a item is selected.</div> * <ul> * <li>container : this</li> * <li>item : the item that was selected</li> * </ul> * </dd> * * <dd><b>BeforeAdd</b> : TabPanelEvent(container, item, index)<br> * <div>Fires before a item is added or inserted. Listeners can cancel the action by calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>container : this</li> * <li>item : the item being added</li> * <li>index : the index at which the item will be added</li> * </ul> * </dd> * * <dd><b>BeforeRemove</b> : TabPanelEvent(container, item)<br> * <div>Fires before a item is removed. Listeners can cancel the action by calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>container : this</li> * <li>item : the item being removed</li> * </ul> * </dd> * * <dd><b>Add</b> : TabPanelEvent(container, item, index)<br> * <div>Fires after a item has been added or inserted.</div> * <ul> * <li>container : this</li> * <li>item : the item that was added</li> * <li>index : the index at which the item will be added</li> * </ul> * </dd> * * <dd><b>Remove</b> : TabPanelEvent(container, item)<br> * <div>Fires after a item has been removed.</div> * <ul> * <li>container : this</li> * <li>item : the item being removed</li> * </ul> * </dd> * </dl> * * <dl> * <dt>Inherited Events:</dt> * <dd>BoxComponent Move</dd> * <dd>BoxComponent Resize</dd> * <dd>Component Enable</dd> * <dd>Component Disable</dd> * <dd>Component BeforeHide</dd> * <dd>Component Hide</dd> * <dd>Component BeforeShow</dd> * <dd>Component Show</dd> * <dd>Component Attach</dd> * <dd>Component Detach</dd> * <dd>Component BeforeRender</dd> * <dd>Component Render</dd> * <dd>Component BrowserEvent</dd> * <dd>Component BeforeStateRestore</dd> * <dd>Component StateRestore</dd> * <dd>Component BeforeStateSave</dd> * <dd>Component SaveState</dd> * </dl> */ public class TabPanel extends Container<TabItem> { /** * TabPanel messages. */ public class TabPanelMessages { private String closeText = GXT.MESSAGES.tabPanelItem_closeText(); private String closeOtherText = GXT.MESSAGES.tabPanelItem_closeOtherText(); /** * Returns the close other text. * * @return the close other text */ public String getCloseOtherText() { return closeOtherText; } /** * Returns the close text. * * @return the close text */ public String getCloseText() { return closeText; } /** * Sets the close other text for the close context menu (defaults to 'Close all other tabs'). * * @param closeOtherText * the close other text */ public void setCloseOtherText(String closeOtherText) { this.closeOtherText = closeOtherText; } /** * Sets the close text for the close context menu (default to 'Close this tab'). * * @param closeText * the close text */ public void setCloseText(String closeText) { this.closeText = closeText; } } /** * Tab position enumeration. */ public enum TabPosition { TOP, BOTTOM; } private class AccessStack { Stack<TabItem> stack = new Stack<TabItem>(); void add(TabItem item) { if (stack.contains(item)) { stack.remove(item); } stack.add(item); if (stack.size() > 10) { stack.remove(0); } } void clear() { stack.clear(); } TabItem next() { return stack.size() > 0 ? stack.pop() : null; } void remove(TabItem item) { stack.remove(item); } } /** * Default tab item template. */ public static Template itemTemplate; protected Menu closeContextMenu; private boolean bodyBorder = true; private boolean border = true; private int tabMargin = 2; private int scrollIncrement = 100; private int minTabWidth = 30; private int scrollDuration = 150; private boolean resizeTabs = false; private TabPosition tabPosition = TabPosition.TOP; private int tabWidth = 120; private boolean tabScroll = false; private boolean autoSelect = true; private boolean animScroll = false; private TabItem activeItem; private El body, bar, stripWrap, strip; private El edge, scrollLeft, scrollRight; private CardLayout cardLayout; private boolean scrolling; private AccessStack stack; private boolean plain; private boolean closeMenu = false; private TabPanelMessages messages; // the position where the edge of possible scrolling private int stripLeft; /** * Creates a new tab panel. */ public TabPanel() { baseStyle = "x-tab-panel"; cardLayout = new CardLayout(); setLayout(cardLayout); enableLayout = true; setMessages(new TabPanelMessages()); setDeferHeight(true); } /** * Adds a tab item. Fires the <i>BeforeAdd</i> event before inserting, then fires the <i>Add</i> event after the widget has been * inserted. * * @param item * the item to be added */ public boolean add(TabItem item) { return super.add(item); } /** * Searches for an item based on its id and optionally the item's text. * * @param id * the item id * @param checkText * true to match the items id and text * @return the item */ public TabItem findItem(String id, boolean checkText) { int count = getItemCount(); for (int i = 0; i < count; i++) { TabItem item = getItem(i); if (item.getItemId().equals(id) || item.getId().equals(id) || (checkText && item.getText().equals(id))) { return item; } } return null; } /** * Returns true if scrolling is animated. * * @return the anim scroll state */ public boolean getAnimScroll() { return animScroll; } /** * Returns true if the body border is enabled. * * @return the body border state */ public boolean getBodyBorder() { return bodyBorder; } /** * Returns true if the border style is enabled. * * @return the border style */ public boolean getBorderStyle() { return border; } @Override public CardLayout getLayout() { return cardLayout; } @Override public El getLayoutTarget() { return body; } /** * Returns the tab panel messages. * * @return the messages */ public TabPanelMessages getMessages() { return messages; } /** * Returns the minimum tab width. * * @return the minimum tab width */ public int getMinTabWidth() { return minTabWidth; } /** * Returns true if tab resizing is enabled. * * @return the tab resizing state */ public boolean getResizeTabs() { return resizeTabs; } /** * Returns the scroll duration in milliseconds. * * @return the duration */ public int getScrollDuration() { return scrollDuration; } /** * Returns the current selection tab item. * * @return the selected item */ public TabItem getSelectedItem() { return activeItem; } /** * Returns the panel's tab margin. * * @return the margin */ public int getTabMargin() { return tabMargin; } /** * Returns the tab position. * * @return the tab position */ public TabPosition getTabPosition() { return tabPosition; } /** * Returns true if tab scrolling is enabled. * * @return the tab scroll state */ public boolean getTabScroll() { return tabScroll; } /** * Returns the default tab width. * * @return the width */ public int getTabWidth() { return tabWidth; } /** * Adds a tab item. Fires the <i>BeforeAdd</i> event before inserting, then fires the <i>Add</i> event after the widget has been * inserted. * * @param item * the item to be inserted * @param index * the insert position */ @Override public boolean insert(TabItem item, int index) { return super.insert(item, index); } /** * Returns true if auto select is enabled. * * @return the auto select state */ public boolean isAutoSelect() { return autoSelect; } /** * Returns true if close context menu is enabled. * * @return the close menu state */ public boolean isCloseContextMenu() { return closeMenu; } /** * Returns true if children items are rendered when first accessed. * * @return true to defer rendering */ public boolean isDeferredRender() { return cardLayout.isDeferredRender(); } /** * Returns true if the tab strip will be rendered without a background. * * @return the plain state */ public boolean isPlain() { return plain; } /** * Handle the component event, scrolling left or right on the tab panel */ public void onComponentEvent(ComponentEvent ce) { super.onComponentEvent(ce); if (ce.getEventTypeInt() == Event.ONCLICK) { El target = ce.getTargetEl(); if (target.is(".x-tab-scroller-left")) { ce.cancelBubble(); if (!target.is(".x-tab-scroller-left-disabled")) onScrollLeft(); } if (target.is(".x-tab-scroller-right")) { ce.cancelBubble(); if (!target.is(".x-tab-scroller-right-disabled")) onScrollRight(); } } if (ce.getEventTypeInt() == Event.ONMOUSEOVER || ce.getEventTypeInt() == Event.ONMOUSEOUT) { // the scrollTo is a workaround an IE problem. IE resets position of the layout when the flywhight object is accessed. Element target = ce.getTarget(); int beforeEventHandle = stripWrap.getScrollLeft(); if (target != null && !fly(target).getStyleName().equals("x-tab-strip-close")) { if (scrollLeft != null && scrollLeft.dom.getId().equals(target.getId())) { fly(target).setStyleName("x-tab-scroller-left-over", ce.getEventTypeInt() == Event.ONMOUSEOVER); if (GXT.isIE && stripWrap.getScrollLeft() != beforeEventHandle) stripWrap.scrollTo("left", beforeEventHandle); } if (scrollRight != null && scrollRight.dom.getId().equals(target.getId())) { fly(target).setStyleName("x-tab-scroller-right-over", ce.getEventTypeInt() == Event.ONMOUSEOVER); if (GXT.isIE && stripWrap.getScrollLeft() != beforeEventHandle) stripWrap.scrollTo("left", beforeEventHandle); } } } if (ce.getEventTypeInt() == Event.ONBLUR && GXT.isAriaEnabled()) { onBlur(ce); } else if (ce.getEventTypeInt() == Event.ONFOCUS && GXT.isAriaEnabled()) { onFocus(ce); } } /** * Removes the tab item. Fires the <i>BeforeRemove</i> event before removing, then fires the <i>Remove</i> event after the widget has * been removed. * * @param item * the item to be removed */ public boolean remove(TabItem item) { boolean removed = super.remove(item); if (removed) { if (stack != null) { stack.remove(item); } if (rendered) { if (item.header.isRendered()) { item.header.removeStyleName("x-tab-strip-active"); strip.dom.removeChild(item.header.getElement()); ComponentHelper.doDetach(item.header); } if (item == activeItem) { activeItem = null; TabItem next = this.stack.next(); if (next != null) { setSelection(next); } else if (getItemCount() > 0) { setSelection(getItem(0)); } else { getLayout().activeItem = null; } } } delegateUpdates(); } return removed; } @Override public boolean removeAll() { int count = getItemCount(); TabItem current = getSelectedItem(); for (int i = count - 1; i >= 0; i--) { TabItem item = getItem(i); if (current != item) { remove(item); } } if (current != null) { remove(current); } return getItemCount() == 0; } /** * Scrolls to a particular tab if tab scrolling is enabled. * * @param item * the item to scroll to * @param animate * true to animate the scroll */ public void scrollToTab(TabItem item, boolean animate) { if (item == null) return; // pos is the edge position where the unseen part of the tab bar starts int barWidth = stripWrap.getWidth(); int barLeft = stripWrap.dom.getAbsoluteLeft(); int rightMostPosition = stripLeft + bar.dom.getAbsoluteLeft(); int scrollSize = stripLeft - bar.getWidth() + 18; int edgeOfVisible = barLeft + barWidth; int pos = getScollPos(); El itemEl = item.header.el(); int left = itemEl.dom.getAbsoluteLeft(); int right = left + itemEl.getWidth(); if (LocaleInfo.getCurrentLocale().isRTL()) { // scrolling is from highest 0 to lowest scrollSize * -1) if (right > edgeOfVisible) { // right side is outside the visible scope int scrollTo = Math.max(0, rightMostPosition - right); scrollTo(scrollTo, animate); } else if (left <= barLeft) { // left side is outside the visible scope int scrollTo = Math.min(scrollSize, (left - barLeft)); scrollTo(scrollTo, animate); } } else { // scrolling is from highest scrollSize to lowest 0) if (right > edgeOfVisible) { // right side is outside the visible scope int scrollTo = right - edgeOfVisible; if (scrollTo <= tabWidth) // this may happen when right is outside and left is inside the visible area. scrollTo = rightMostPosition - pos + scrollTo; scrollTo(scrollTo, animate); } else if (left <= barLeft) { // left side is outside the visible scope int scrollTo = barLeft - left; if (scrollTo > 0) // this may happen when left is outside and right is inside the visible area. scrollTo = rightMostPosition - pos - scrollTo; scrollTo(scrollTo, animate); } } } /** * True to animate tab scrolling so that hidden tabs slide smoothly into view (defaults to true). Only applies when {@link #tabScroll} = * true. * * @param animScroll * the anim scroll state */ public void setAnimScroll(boolean animScroll) { this.animScroll = animScroll; } @Override public void setAutoHeight(boolean autoHeight) { super.setAutoHeight(autoHeight); for (TabItem item : getItems()) { item.setAutoHeight(autoHeight); } } /** * True to have the first item selected when the panel is displayed for the first time if there is not selection (defaults to true). * * @param autoSelect * the auto select state */ public void setAutoSelect(boolean autoSelect) { this.autoSelect = autoSelect; } /** * True to display an interior border on the body element of the panel, false to hide it (defaults to true, pre-render). * * @param bodyBorder the body border style */ public void setBodyBorder(boolean bodyBorder) { this.bodyBorder = bodyBorder; } /** * True to display a border around the tabs (defaults to true). * * @param border true for borders */ public void setBorderStyle(boolean border) { this.border = border; } /** * True to show the close context menu (defaults to false). * * @param closeMenu true to show it */ public void setCloseContextMenu(boolean closeMenu) { this.closeMenu = closeMenu; } /** * True to render each child tab item when it accessed, false to render all (defaults to true). Setting to false would be useful when * using forms as validation would need to be applied to all children even if they had not been selected. * * @param deferredRender * true to defer rendering */ public void setDeferredRender(boolean deferredRender) { cardLayout.setDeferredRender(deferredRender); } /** * Sets the tab panel messages. * * @param messages * the messages */ public void setMessages(TabPanelMessages messages) { this.messages = messages; } /** * The minimum width in pixels for each tab when {@link #resizeTabs} = true (defaults to 30). * * @param minTabWidth * the minimum tab width */ public void setMinTabWidth(int minTabWidth) { this.minTabWidth = minTabWidth; } /** * True to render the tab strip without a background container image (defaults to false, pre-render). * * @param plain */ public void setPlain(boolean plain) { assertPreRender(); this.plain = plain; } /** * True to automatically resize each tab so that the tabs will completely fill the tab strip (defaults to false). Setting this to true * may cause specific widths that might be set per tab to be overridden in order to fit them all into view (although * {@link #minTabWidth} will always be honored). * * @param resizeTabs * true to enable tab resizing */ public void setResizeTabs(boolean resizeTabs) { this.resizeTabs = resizeTabs; } /** * Sets the number of milliseconds that each scroll animation should last (defaults to 150). * * @param scrollDuration * the scroll duration */ public void setScrollDuration(int scrollDuration) { this.scrollDuration = scrollDuration; } /** * Sets the number of pixels to scroll each time a tab scroll button is pressed (defaults to 100, or if {@link #setResizeTabs(boolean)} * = true, the calculated tab width). Only applies when {@link #setTabScroll(boolean)} = true. * * @param scrollIncrement * the scroll increment */ public void setScrollIncrement(int scrollIncrement) { this.scrollIncrement = scrollIncrement; } /** * Sets the selected tab item. Fires the <i>BeforeSelect</i> event before selecting, then fires the <i>Select</i> event after the widget * has been selected. * * @param item * the item to be selected */ public void setSelection(TabItem item) { TabPanelEvent tpe = new TabPanelEvent(this, item); if (item == null || !fireEvent(Events.BeforeSelect, tpe) || !item.fireEvent(Events.BeforeSelect, tpe)) { return; } if (!rendered) { activeItem = item; return; } if (activeItem != item) { if (activeItem != null) { activeItem.header.removeStyleName("x-tab-strip-active"); } item.header.addStyleName("x-tab-strip-active"); activeItem = item; stack.add(activeItem); cardLayout.setActiveItem(activeItem); if (scrolling) { scrollToTab(item, getAnimScroll()); } focusTab(activeItem, false); fireEvent(Events.Select, tpe); item.fireEvent(Events.Select, tpe); } if (GXT.isAriaEnabled() && activeItem == item) { focusTab(activeItem, false); } } /** * The number of pixels of space to calculate into the sizing and scrolling of tabs (defaults to 2). * * @param tabMargin * the tab margin */ public void setTabMargin(int tabMargin) { this.tabMargin = tabMargin; } /** * Sets the position where the tab strip should be rendered (defaults to TOP, pre-render). The only other supported value is BOTTOM. * Note that tab scrolling is only supported for position TOP. * * @param tabPosition * the tab position */ public void setTabPosition(TabPosition tabPosition) { this.tabPosition = tabPosition; } /** * True to enable scrolling to tabs that may be invisible due to overflowing the overall TabPanel width. Only available with tabs on * top. (defaults to false). * * @param tabScroll * true to enable tab scrolling */ public void setTabScroll(boolean tabScroll) { this.tabScroll = tabScroll; } /** * Sets the initial width in pixels of each new tab (defaults to 120). * * @param tabWidth */ public void setTabWidth(int tabWidth) { this.tabWidth = tabWidth; } protected void close(TabItem item) { TabPanelEvent e = new TabPanelEvent(this, item); if (item.isClosable() && item.fireEvent(Events.BeforeClose, e) && remove(item)) { item.fireEvent(Events.Close, new TabPanelEvent(this, item)); } } @Override protected ComponentEvent createComponentEvent(Event event) { return new TabPanelEvent(this); } @Override @SuppressWarnings("unchecked") protected ContainerEvent createContainerEvent(TabItem item) { return new TabPanelEvent(this, item); } @Override protected void doAttachChildren() { super.doAttachChildren(); for (TabItem item : getItems()) { ComponentHelper.doAttach(item.header); } } @Override protected void doDetachChildren() { super.doDetachChildren(); for (TabItem item : getItems()) { ComponentHelper.doDetach(item.header); } } @Override protected void onAfterLayout() { super.onAfterLayout(); delegateUpdates(); } @Override protected void onAttach() { super.onAttach(); bar.disableTextSelection(true); if (activeItem != null) { TabItem item = activeItem; activeItem = null; setSelection(item); } else if (activeItem == null && autoSelect && getItemCount() > 0) { setSelection(getItem(0)); } if (GXT.isAriaEnabled()) { FocusFrame.get().unframe(); } if (resizeTabs) { autoSizeTabs(); } } protected void onBlur(ComponentEvent ce) { FocusFrame.get().unframe(); } @Override protected void onDetach() { bar.disableTextSelection(false); super.onDetach(); } protected void onFocus(ComponentEvent ce) { FocusFrame.get().frame(this); if (getItemCount() > 0 && getSelectedItem() == null) { setSelection(getItem(0)); } else if (getSelectedItem() != null) { focusTab(getSelectedItem(), true); DeferredCommand.addCommand(new Command() { public void execute() { FocusFrame.get().frame(TabPanel.this); } }); } } protected void onInsert(TabItem item, int index) { super.onInsert(item, index); item.tabPanel = this; item.setAutoHeight(isAutoHeight()); if (rendered) { renderItem(item, index); if (isAttached()) { ComponentHelper.doAttach(item.header); } if (activeItem == null && autoSelect) { setSelection(item); } delegateUpdates(); if (getItemCount() == 1) { syncSize(); } } } protected void onItemContextMenu(TabItem item, int x, int y) { if (closeMenu) { if (closeContextMenu == null) { closeContextMenu = new Menu(); closeContextMenu.add(new MenuItem(messages.getCloseText(), new SelectionListener<MenuEvent>() { @Override public void componentSelected(MenuEvent ce) { TabItem item = (TabItem) ce.getContainer().getData("tab"); close(item); } })); closeContextMenu.add(new MenuItem(messages.getCloseOtherText(), new SelectionListener<MenuEvent>() { @Override public void componentSelected(MenuEvent ce) { TabItem item = (TabItem) ce.getContainer().getData("tab"); List<TabItem> items = new ArrayList<TabItem>(); items.addAll(getItems()); for (TabItem currentItem : items) { if (currentItem != item && currentItem.isClosable()) { close(currentItem); } } } })); } closeContextMenu.getItem(0).setEnabled(item.isClosable()); closeContextMenu.setData("tab", item); boolean hasClosable = false; for (TabItem item2 : getItems()) { if (item2.isClosable() && item2 != item) { hasClosable = true; break; } } closeContextMenu.getItem(1).setEnabled(hasClosable); closeContextMenu.showAt(x, y); } } protected void onItemTextChange(TabItem tabItem, String oldText, String newText) { delegateUpdates(); } protected void onKeyPress(ComponentEvent ce) { int code = ce.getKeyCode(); switch (code) { case KeyCodes.KEY_RIGHT: { if (LocaleInfo.getCurrentLocale().isRTL()) { onLeft(ce); break; } onRight(ce); break; } case KeyCodes.KEY_PAGEDOWN: onRight(ce); break; case KeyCodes.KEY_LEFT: { if (LocaleInfo.getCurrentLocale().isRTL()) { onRight(ce); break; } onLeft(ce); break; } case KeyCodes.KEY_PAGEUP: onLeft(ce); break; case KeyCodes.KEY_HOME: if (ce.getTarget() == activeItem.getHeader().getElement() && getItemCount() > 0 && activeItem != getItem(0)) { setSelection(getItem(0)); } break; case KeyCodes.KEY_END: if (ce.getTarget() == activeItem.getHeader().getElement()) { setSelection(getItem(getItemCount() - 1)); } break; } } protected void onLeft(ComponentEvent ce) { if (activeItem != null && ce.getTarget() == activeItem.getHeader().getElement()) { int idx = indexOf(activeItem); if (idx > 0) { setSelection(getItem(idx - 1)); focusTab(activeItem, true); } } } @Override protected void onRender(Element target, int index) { setElement(DOM.createDiv(), target, index); stack = new AccessStack(); if (tabPosition == TabPosition.TOP) { bar = el().createChild("<div class='" + baseStyle + "-header'></div>"); body = el().createChild("<div class='" + baseStyle + "-body " + baseStyle + "-body-top'></div"); } else { body = el().createChild("<div class='" + baseStyle + "-body " + baseStyle + "-body-bottom'></div"); bar = el().createChild("<div class='" + baseStyle + "-footer'></div>"); } if (!bodyBorder) { body.setStyleAttribute("border", "none"); } if (!border && tabPosition == TabPosition.TOP) { bar.setStyleAttribute("borderLeft", "none"); bar.setStyleAttribute("borderRight", "none"); bar.setStyleAttribute("borderTop", "none"); } if (!border && tabPosition == TabPosition.BOTTOM) { bar.setStyleAttribute("borderLeft", "none"); bar.setStyleAttribute("borderRight", "none"); bar.setStyleAttribute("borderBottom", "none"); } String pos = tabPosition == TabPosition.BOTTOM ? "bottom" : "top"; stripWrap = bar.createChild("<div class=x-tab-strip-wrap></div>"); Accessibility.setRole(stripWrap.dom, "presentation"); bar.createChild("<div class=x-tab-strip-spacer></div>"); strip = stripWrap.createChild("<ul class='x-tab-strip x-tab-strip-" + pos + "'></ul>"); edge = strip.createChild("<li class=x-tab-edge role='presentation'></li>"); strip.createChild("<div class='x-clear' role='presentation'></div>"); if (plain) { String p = tabPosition == TabPosition.BOTTOM ? "bottom" : "header"; bar.addStyleName(baseStyle + "-" + p + "-plain"); } if (itemTemplate == null) { StringBuffer sb = new StringBuffer(); sb.append("<li class='{style}' id={id} role='tab' tabindex='0'><a class=x-tab-strip-close role='presentation'></a>"); sb.append("<a class='x-tab-right' role='presentation'><em role='presentation' class='x-tab-left'>"); sb.append("<span class='x-tab-strip-inner' role='presentation'><span class='x-tab-strip-text {textStyle} {iconStyle}'>{text}</span></span>"); sb.append("</em></a></li>"); itemTemplate = new Template(sb.toString()); itemTemplate.compile(); } renderAll(); new KeyNav<ComponentEvent>(this) { @Override public void onKeyPress(ComponentEvent ce) { TabPanel.this.onKeyPress(ce); } }; el().setTabIndex(0); el().setElementAttribute("hideFocus", "true"); if (GXT.isAriaEnabled()) { Accessibility.setRole(getElement(), Accessibility.ROLE_TABLIST); if (!getTitle().equals("")) { Accessibility.setState(getElement(), "aria-label", getTitle()); } } sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.ONKEYUP | Event.FOCUSEVENTS); } @Override protected void onResize(int width, int height) { super.onResize(width, height); Size frameWidth = el().getFrameSize(); height -= frameWidth.height + bar.getHeight(); width -= frameWidth.width; body.setSize(width, height, true); bar.setWidth(width, true); delegateUpdates(); } protected void onRight(ComponentEvent ce) { if (activeItem != null && ce.getTarget() == activeItem.getHeader().getElement()) { ce.stopEvent(); int idx = indexOf(activeItem); if (idx < getItemCount()) { setSelection(getItem(idx + 1)); focusTab(activeItem, true); } } } protected void onUnload() { super.onUnload(); if (stack != null) { stack.clear(); } } void onItemClick(TabItem item, ComponentEvent ce) { ce.stopEvent(); Element target = ce.getTarget(); if (fly(target).getStyleName().equals("x-tab-strip-close")) { close(item); } else if (item != activeItem) { setSelection(item); if (GXT.isIE) { int scrollLeftBefore = stripWrap.getScrollLeft(); focusTab(item, true); if (stripWrap.getScrollLeft() != scrollLeftBefore) stripWrap.setScrollLeft(scrollLeftBefore); } else focusTab(item, true); } else if (item == activeItem) { if (GXT.isIE) { int scrollLeftBefore = stripWrap.getScrollLeft(); focusTab(item, true); if (stripWrap.getScrollLeft() != scrollLeftBefore) stripWrap.setScrollLeft(scrollLeftBefore); } else focusTab(item, true); } } void onItemOver(TabItem item, boolean over) { item.header.el().setStyleName("x-tab-strip-over", over); } void onItemRender(TabItem item, Element target, int pos) { item.header.disabledStyle = "x-item-disabled"; String style = item.isClosable() ? "x-tab-strip-closable " : ""; if (!item.header.isEnabled()) { style += " x-item-disabled"; } Params p = new Params(); p.set("id", getId() + "__" + item.getId()); p.set("text", item.getText()); p.set("style", style); p.set("textStyle", item.getTextStyle()); if (item.template == null) { item.template = itemTemplate; } item.header.setElement(item.template.create(p)); item.header.sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS); if (item.getIcon() != null) { item.setIcon(item.getIcon()); } DOM.insertChild(target, item.header.getElement(), pos); } /** * determine if we need to place scroll arrows. */ private void autoScrollTabs() { // number of items int count = getItemCount(); int tw = bar.getClientWidth(); int cw = stripWrap.getWidth(); if (!getTabScroll() || count < 1 || cw < 20) { return; } int barLeft = bar.dom.getAbsoluteLeft(); int visibleEdgePosition = barLeft + tw; // if the edge of the tabs is inside the visible area then we don't need to scroll. if (barLeft <= edge.dom.getAbsoluteLeft() && edge.dom.getAbsoluteLeft() <= visibleEdgePosition && (stripLeft + barLeft) <= visibleEdgePosition) { stripWrap.setWidth(tw); if (scrolling) { stripWrap.setScrollLeft(0); scrolling = false; bar.removeStyleName("x-tab-scrolling"); scrollLeft.setVisible(false); scrollRight.setVisible(false); } } else { if (!scrolling) { bar.addStyleName("x-tab-scrolling"); } tw -= 36; stripWrap.setWidth(tw > 20 ? tw : 20); if (!scrolling) { if (scrollLeft == null) { createScrollers(); } else { scrollLeft.setVisible(true); scrollRight.setVisible(true); } } scrolling = true; scrollToTab(activeItem, false); //updateScrollButtons(); } } private void autoSizeTabs() { int count = getItemCount(); if (count == 0) return; int aw = bar.getClientWidth(); int each = (int) Math.max(Math.min(Math.floor((aw - 4) / count) - getTabMargin(), tabWidth), getMinTabWidth()); stripLeft = tabWidth * count; for (int i = 0; i < count; i++) { El el = getItem(i).header.el(); Element inner = el.childNode(1).firstChild().firstChild().dom; int tw = el.getWidth(); int iw = fly(inner).getWidth(); fly(inner).setWidth(each - (tw - iw)); } } private void createScrollers() { int h = stripWrap.getHeight(); scrollLeft = bar.insertFirst("<div class='x-tab-scroller-left'></div>"); scrollLeft.setHeight(h); scrollLeft.setVisible(true); scrollRight = bar.insertFirst("<div class='x-tab-scroller-right'></div>"); scrollRight.setHeight(h); scrollRight.setVisible(true); } private void delegateUpdates() { if (resizeTabs && rendered) { autoSizeTabs(); } if (tabScroll && rendered) { autoScrollTabs(); } } private void focusTab(TabItem item, boolean setFocus) { if (setFocus) { item.getHeader().el().focus(); } if (GXT.isAriaEnabled()) { item.getHeader().el().focus(); FocusFrame.get().frame(this); } } /** * returns the position of the edge * * @return */ private int getScollPos() { return edge.dom.getAbsoluteLeft() + edge.getWidth(); } private int getScrollIncrement() { return scrollIncrement; } private void onScrollLeft() { int pos = getScollPos(); int s; int barLeft = bar.dom.getAbsoluteLeft(); if (LocaleInfo.getCurrentLocale().isRTL()) { int edgeOfVisible = stripWrap.dom.getAbsoluteLeft() + stripWrap.getWidth(); int scrollSize = (stripLeft + bar.dom.getAbsoluteLeft() - edgeOfVisible) * -1; s = Math.max(scrollSize, (barLeft + scrollSize) - pos - getScrollIncrement()); if (s >= scrollSize) { scrollTo(s, getAnimScroll()); } } else { s = Math.max(0, (stripLeft + barLeft - pos) - getScrollIncrement()); if (s >= 0) { scrollTo(s, getAnimScroll()); } } } private void onScrollRight() { int pos = getScollPos(); int visibleEdgePosition = stripWrap.dom.getAbsoluteLeft() + stripWrap.getWidth(); int scrollSize = stripLeft + bar.dom.getAbsoluteLeft() - visibleEdgePosition; int barLeft = bar.dom.getAbsoluteLeft(); int s; if (LocaleInfo.getCurrentLocale().isRTL()) { s = Math.min(0, ((barLeft - scrollSize) - pos) + getScrollIncrement()); if (s <= 0) scrollTo(s, getAnimScroll()); } else { s = Math.min(scrollSize, (stripLeft + barLeft - pos) + getScrollIncrement()); if (s <= scrollSize) { scrollTo(s, getAnimScroll()); } } } private void renderAll() { int count = getItemCount(); for (int i = 0; i < count; i++) { TabItem item = getItem(i); renderItem(item, i); } } private void renderItem(TabItem item, int index) { if (item.header.isRendered()) { strip.insertChild(item.header.getElement(), index); } else { item.header.render(strip.dom, index); } if (!GXT.isAriaEnabled()) { item.header.el().setElementAttribute("hideFocus", "true"); item.header.el().setStyleAttribute("-moz-outline", "none"); } } private void scrollTo(int pos, boolean animate) { if (animate) { stripWrap.scrollTo("left", pos, new FxConfig(new Listener<FxEvent>() { public void handleEvent(FxEvent fe) { updateScrollButtons(); } })); } else { stripWrap.scrollTo("left", pos); updateScrollButtons(); } } private void updateScrollButtons() { int pos = getScollPos(); int barLeft = bar.dom.getAbsoluteLeft(); int visibleEdgePosition = barLeft + bar.getWidth(); int scrollSize = stripLeft + barLeft - visibleEdgePosition; if (LocaleInfo.getCurrentLocale().isRTL()) { if (GXT.isIE) { // this is an IE hack. For some reason ie resets the scrollLeft after changing the style. We fix the position manually int beforeStyleChange = stripWrap.getScrollLeft(); scrollLeft.setStyleName("x-tab-scroller-left-disabled", pos >= barLeft + 4); scrollRight.setStyleName("x-tab-scroller-right-disabled", pos <= barLeft - scrollSize + 4); if (stripWrap.getScrollLeft() != beforeStyleChange) { stripWrap.scrollTo("left", beforeStyleChange); } } else { scrollLeft.setStyleName("x-tab-scroller-left-disabled", pos >= barLeft + 4); scrollRight.setStyleName("x-tab-scroller-right-disabled", pos <= barLeft - scrollSize + 4); } } else { scrollLeft.setStyleName("x-tab-scroller-left-disabled", pos >= stripLeft + barLeft - 4); scrollRight.setStyleName("x-tab-scroller-right-disabled", pos <= visibleEdgePosition - 4); } } }