All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.smartclient.debug.public.sc.client.widgets.TabBar.js Maven / Gradle / Ivy

The newest version!
/*
 * Isomorphic SmartClient
 * Version SC_SNAPSHOT-2011-08-08 (2011-08-08)
 * Copyright(c) 1998 and beyond Isomorphic Software, Inc. All rights reserved.
 * "SmartClient" is a trademark of Isomorphic Software, Inc.
 *
 * [email protected]
 *
 * http://smartclient.com/license
 */

 





//>	@class	TabBar
// Shows a set of Tabs.  TabBars are automatically created by TabSets and shouldn't be used
// directly.  The TabBar is documented for skinning purposes.
// 
// @treeLocation Client Reference/Layout/TabSet
// @visibility external
//<
isc.ClassFactory.defineClass("TabBar", "Toolbar");

isc.TabBar.addProperties({
    //>	@attr	isc.TabBar.tabs		(Array of Tab Properties : null : IR)
    // Tab for this TabBar.
    // @visibility external
    //<

    //>	@attr	isc.TabBar.breadth	(number : 21 : IRW)
    // Breadth of the tabBar (including baseline breadth)
    // @visibility external
    //<
    breadth: 21,

    //>	@attr	isc.TabBar.buttonConstructor	(class: ImgTab : AIRW)
    // SmartClient component used for the tabs of the tabBar. 
    // Must be Button or Button subclass.
    // @visibility external
    //<
    // Note - if this TabBar is part of a TabSet, this constructor can be overridden by setting 
    // 'useSimpleTabs' on the TabSet - will use buttons instead, styled via CSS to look like
    // tabs.
    buttonConstructor:isc.ImgTab,

    // We want to have arrow keys, not tab-keypresses, move between tabs
    tabWithinToolbar:false,

    //>	@attr	isc.TabBar.skinImgDir		(URL : "images/Tab/" : AIRW)
    //			base path for the tab images, if an image-based
    //			tab is being used.
    //		@group skins, files
    //<
    skinImgDir:"images/Tab/",			
										
    // --------------------------------------------------------------------------------------------
    //> @attr tabBar.showMoreTab (boolean : null : IR)
    // Should tabs exceeding +link{moreTabCount} be shown on a "more" tab?
    // 

// This setting is used to emulate an iPhone-style tab bar "more" button. // @visibility external //< //> @attr tabBar.moreTabCount (int : 5 : IR) // This property defines the number tab buttons that should be shown before // automatically adding a "more" button to handle the remaining tabs. This // property is only used when +link{showMoreTab} is enabled. // @visibility external //< moreTabCount:5, //> @attr tabBar.moreTab (Tab : null : IR) // Tab to show as the "more" tab when +link{showMoreTab} is enabled and the number // of tabs to show exceeds +link{moreTabCount}. // @visibility external //< // Baseline // -------------------------------------------------------------------------------------------- //> @groupDef baseLine // The baseLine is StretchImg that is placed along the edge of the TabBar that borders on // the pane, occluding the pane's actual border but matching it exactly. The selected tab // is in front of the baseLine, and the rest are behind it. // @visibility external //< //> @attr isc.TabBar.baseLineThickness (number : 1 : IR) // Thickness of the baseLine, in pixels. This should be set to match the media specified // by +link{baseLineSrc}. The baseLineThickness also determines the degree of overlap with // the TabSet's paneContainer when using decorative edges - see +link{TabSet.paneContainer} // for details. // // @group baseLine // @visibility external //< baseLineThickness:1, //> @attr isc.TabBar.baseLineSrc (SCImgURL : "[SKIN]baseline.gif" : IR) // Sets +link{stretchImg.src} for the +link{group:baseLine} StretchImg. // @group baseLine // @visibility external //< baseLineSrc:"[SKIN]baseline.gif", //> @attr isc.TabBar.baseLineCapSize (number : 2 : IR) // Set +link{stretchImg.capSize} for the +link{group:baseLine} stretchImg. // @group baseLine // @visibility external //< baseLineCapSize:2, // Positioning and Alignment // -------------------------------------------------------------------------------------------- //> @attr isc.TabBar.tabBarPosition (Side : isc.Canvas.TOP : IRW) // Position of the tabBar in relation to whatever it controls. //< // Not doc'd, do via TabSet tabBarPosition:isc.Canvas.TOP, // -------------------------------------------------------------------------------------------- //> @attr isc.TabBar.selectedTab (number : 0 : IR) // Index of the initially selected tab. Settable at initialization only, afterwards, call // +link{selectTab}. //< // Not doc'd, do via TabSet selectedTab:0, //> @attr isc.TabBar.defaultTabSize (number : 80 : IR) // Default size (length) in pixels for tabs within this tabBar // @visibility external //< defaultTabSize:80 //> @attr TabBar.tabDefaults (Tab : 0 : AIR) // Defaults applied to tabs on init. // Note that these are overlaid over the superclass property // toolBar.buttonDefaults. //< // Null by default - we will set this up in initWidget - this avoids multiple tabBars // pointing to the same tabDefaults object //tabDefaults:{} }); isc.TabBar.addMethods({ //> @method tabBar.initWidget() (A) // Initialize the TabBar object. //

// Setup special button properties and create the baseLine //< initWidget : function () { // if the name of the pane property of a tab is specified as a string, check it // now, and reassign. for (var i = 0; i < this.tabs.length; i++) { var pane = this.tabs[i].pane; if (isc.isA.String(pane) && isc.isA.Canvas(window[pane])) { this.tabs[i].pane = window[pane]; } } // copy tabs over to the buttons array, which is what the superclass uses. this.buttons = this.tabs; if (this.moreTab) { this._moreTabIndex = this.buttons.length; this.buttons[this._moreTabIndex] = this.moreTab; } // Note that the order of the tabs can be reversed by setting the 'reverseOrder' property // on this tabBar [can be done in tabBarDefaults] if this is zrequired. // set up the skinImgDir for the baseline this.skinImgDir = this.skinImgDir + this.tabBarPosition + "/"; var tabDefaults = this.tabDefaults; if (tabDefaults == null) tabDefaults = this.tabDefaults = {}; // tabs are created as "buttons" by Toolbar superclass code; to have tabDefaults applied to // each button, assign to buttonDefaults. // NOTE: if we add properties directly to the buttonDefaults object, we'll side effect all // Toolbars tabDefaults = this.buttonDefaults = isc.addProperties({}, this.buttonDefaults, tabDefaults); // tabs are mutually exclusive tabDefaults.actionType = isc.StatefulCanvas.RADIO; // Default tabs to defaultTabWidth if (this.vertical) { tabDefaults.defaultHeight = this.defaultTabSize; } else { tabDefaults.defaultWidth = this.defaultTabSize; } // .. and allow them to expand to fit content tabDefaults.overflow = isc.Canvas.VISIBLE; // set tab style information directly on the (presumed) ImgTab, so that it may be // overridden by the user in tabProperties. tabDefaults.vertical = (this.tabBarPosition == isc.Canvas.LEFT || this.tabBarPosition == isc.Canvas.RIGHT); // skinImgDir: For image based tabs, we need to update the skin img dir to account for // tab orientation. // Note that we *don't* want per-side media for standard icons like the close icon. // Therefore we don't want to modify the skinImgDir if we're using simple tabs. // In ImgTabs we handle this by having a separate labelSkinImgDir var buttonClass = isc.ClassFactory.getClass(this.buttonConstructor); if (buttonClass && buttonClass.isA("ImgTab")) { tabDefaults.skinImgDir = buttonClass.getInstanceProperty("skinImgDir") + this.tabBarPosition + "/"; } // have iconClick close the tabs if appropriate tabDefaults.iconClick = this._tabIconClickHandler; // Override the click and doubleClick handler as necessary to implement title editing // Note if _editTabTitle returns false this indicates we're editing the title - in this // case suppress any doubleClick etc handler defined by the developer directly on the // tab tabDefaults.handleDoubleClick = function () { var tabSet = this.parentElement.parentElement; if (tabSet && tabSet.titleEditEvent == "doubleClick" && tabSet._editTabTitle(this)) return; return this.Super("handleDoubleClick", arguments); } tabDefaults.handleClick = function () { var tabSet = this.parentElement.parentElement; if (tabSet && tabSet.titleEditEvent == "click" && tabSet._editTabTitle(this)) return; return this.Super("handleClick", arguments); }, tabDefaults._generated = true; var perSideStyleProperty = this.tabBarPosition + "StyleName"; if (this[perSideStyleProperty]) this.setStyleName(this[perSideStyleProperty]); this.Super(this._$initWidget); if (this._baseLine == null) this.makeBaseLine(); }, isShowingMoreTab : function () { return (this.showMoreTab && this.moreTab && this._moreTabIndex >= 0 && this.getMembers(this._moreTabIndex).isVisible && this.getMembers(this._moreTabIndex).isVisible() ); }, // _tabIconClickHandler - method applied directly to the tabs _tabIconClickHandler : function () { return this.parentElement.tabIconClick(this); }, tabIconClick : function (tab) { var ts = this.parentElement; return ts._tabIconClick(tab); }, // Override to add "more" button and hide buttons that are now on "more" tab setButtons : function (newButtons) { this.Super("setButtons", arguments); if (isc.Browser.isSGWT) { var liveButtons = this.getMembers(); for (var i = 0; i < liveButtons.length; i++) { liveButtons[i].__ref = null; } } // If "more" tab is enabled and needed, hide the tabs that will show on the "more" tab if (this.showMoreTab && this.buttons.length-1 > this.moreTabCount) { for (var i = this.moreTabCount-1; i < this.buttons.length; i++) { this.getMember(i).hide(); } // Make "more" tab visible. It will be hidden above this.getMember(this._moreTabIndex).show(); } else if (this.showMoreTab && this.moreTab) { this.getMember(this._moreTabIndex).hide(); } }, // override makeButton to show the icon for the button // Also set up locator parent for autoTest APIs makeButton : function (properties, a,b,c,d) { var canClose = this.parentElement.canCloseTab(properties); isc.addProperties(properties, this.getCloseIconProperties(properties, canClose)); properties.locatorParent = this.parentElement; return this.invokeSuper("TabBar", "makeButton", properties, a,b,c,d); }, getCloseIconProperties : function(properties, canClose) { var override = {}; if (properties.canClose == true || (properties.canClose == null && canClose)) { override.icon = (properties.closeIcon || this.parentElement.closeTabIcon); override.iconSize = (properties.closeIconSize || this.parentElement.closeTabIconSize); // close icon should appear at the end of the tab override.iconOrientation = isc.Page.isRTL() ? "left" : "right"; override.iconAlign = override.iconOrientation; } else { // Explicitly override icon-related properties to the tab properties passed in. // This is so we can use TabSet.setCanCloseTab() to toggle the canClose state of a // live Tab without touching any other properties on the live object. override.icon = (properties.icon); override.iconSize = (properties.iconSize); override.iconOrientation = properties.iconOrientation; override.iconAlign = properties.iconAlign; } return override; }, addTabs : function (tabs, position) { if (!position && this.tabBarPosition == isc.Canvas.LEFT) position = 0; this.addButtons(tabs, position); if (isc.Browser.isSGWT) { var liveButtons = this.getMembers(); for (var i = 0; i < liveButtons.length; i++) { liveButtons[i].__ref = null; } } // Hide any new buttons that belong on "more" tab and show "more" if needed if (this.showMoreTab && this.moreTab) { var buttons = this.getMembers(); if (buttons.length-1 > this.moreTabCount) { for (var i = this.moreTabCount-1; i < buttons.length; i++) { buttons[i].hide(); } this._moreTabIndex = buttons.length - 1; buttons[this._moreTabIndex].show(); } } // ensure the tabs initially show up behind the baseline if (this._baseLine != null) { this._baseLine.bringToFront(); var selectedTab = this.getButton(this.getSelectedTab()); if (selectedTab) selectedTab.bringToFront(); } }, removeTabs : function (tabs) { // get the list of tab widgets to be removed if (tabs == null) return; if (!isc.isAn.Array(tabs)) tabs = [tabs]; var tabWidgets = this.map("getButton", tabs); // remove the tabs this.removeButtons(tabs); if (this.showMoreTab && this.moreTab && this._moreTabIndex > 0) { var buttons = this.getMembers(); for (var i = 0; i < buttons.length; i++) { if (i < this.moreTabCount) buttons[i].show(); else buttons[i].hide(); } if (buttons.length-1 <= this.moreTabCount) { // Don't need more tab anymore. Make sure all tabs are shown // and more tab is hidden. this._moreTabIndex = null; buttons[buttons.length-1].hide(); } else { this._moreTabIndex = buttons.length-1; } } // destroy each of the buttons we removed; it's appropriate/okay to do this because the buttons // were automatically created by this tabBar for (var i = 0; i < tabWidgets.length; i++) { if (tabWidgets[i] != null) tabWidgets[i].destroy(); } }, //> @method tabBar.draw() (A) // Extended to do layout and handle the selected tab. // @group drawing //< draw : function (a,b,c,d) { arguments.__this = this; this.fixLayout(); this.invokeSuper(isc.TabBar, "draw", a,b,c,d); this.bringToFront(); var selectedTab = this.getButton(this.selectedTab); // now that the buttons have all drawn, bring the baseline in front of them, then count on // the setSelected() behavior to bring the selected tab in front of the baseLine if (selectedTab) { selectedTab.setSelected(true); } }, //> @method tabBar.makeBaseLine() (A) // The baseline exists to create the appearance that one of the tabs is part of the pane whereas // the other tabs are behind the pane. // // The baseline is an image that runs along the edge of the TabBar that borders on the pane, // occluding the pane's actual border but matching it exactly. The selected tab is in front // of the baseline, and the rest are behind it. //< makeBaseLine : function () { // create the baseline stretchImg and add as child. this._baseLine = this.addAutoChild("baseLine", { ID:this.getID() + "_baseLine", vertical:(this.tabBarPosition == isc.Canvas.LEFT || this.tabBarPosition == isc.Canvas.RIGHT), skinImgDir:this.skinImgDir, src:this.baseLineSrc, capSize:this.baseLineCapSize, imageType:isc.Img.STRETCH, overflow:"hidden", // since the baseline can be a Canvas if it doesn't need to display images addAsChild:true, autoDraw:false }, isc.StretchImg); this.ignoreMemberZIndex(this._baseline); }, // when scrolling shift the baseLine so it's always inside the viewport. scrollTo : function (x,y,a,b,c,d) { this.invokeSuper(isc.TabBar, "scrollTo", x,y,a,b,c,d); if (this._baseLine) this.fixLayout(); }, //> @method tabBar.fixLayout() (A) // Places the baseLine on the side of the TabBar adjacent to the tabbedPane, according to which // side the tabs are on. //< fixLayout : function () { var bl = this._baseLine; if (bl == null) return; var ts = this.parentElement, //edge = ts ? ts._edgedCanvas : null, edgeOffset = 0; // Canvas.TOP if (this.tabBarPosition == isc.Canvas.TOP) { //edgeOffset = edge ? edge._rightMargin : 0; // HACK 040910 bl.setRect(this.getScrollLeft(), this.getHeight() - this.baseLineThickness, this.parentElement.getWidth()-edgeOffset, this.baseLineThickness); // Canvas.BOTTOM } else if (this.tabBarPosition == isc.Canvas.BOTTOM) { //edgeOffset = edge ? edge._leftMargin : 0; // HACK 040910 bl.setRect(this.getScrollLeft(), 0, this.parentElement.getWidth()-edgeOffset, this.baseLineThickness); // Canvas.LEFT } else if (this.tabBarPosition == isc.Canvas.LEFT) { //edgeOffset = edge ? edge._bottomMargin : 0; // HACK 040910 bl.setRect(this.getWidth() - this.baseLineThickness, this.getScrollTop(), this.baseLineThickness, this.parentElement.getHeight()-edgeOffset); // Canvas.RIGHT } else if (this.tabBarPosition == isc.Canvas.RIGHT) { //edgeOffset = edge ? edge._bottomMargin : 0; // HACK 040910 bl.setRect(0, this.getScrollTop(), this.baseLineThickness, this.parentElement.getHeight()-edgeOffset); } }, // fix layout on a change of size layoutChildren : function (a,b,c,d) { this.invokeSuper(isc.TabBar, "layoutChildren", a,b,c,d); this.fixLayout(); }, //> @method tabBar.buttonSelected() (A) // This method overrides the toolBar's buttonSelected method. // Differences are as follows: // - assumes tab is of type "radio", as all tabBar tabs are set to be a radiogroup // - handles z-axis reorderings of tabs and baseLine // - overlaps tabs by expanding and contracting them // // Note: we assume here that buttonSelected is only fired when the button is initially // set to "selected." Repeated clicks should not fire this method. // This assumption can be overridden by setting allowButtonReselect:true. // // @param tab (tab) tab that has been selected. //< buttonSelected : function (tab) { this.ignoreMemberZIndex(tab); // bring tab and label to front. tab.bringToFront(); // store the currently selected tab as the lastSelectedButton. this.lastSelectedButton = tab; // Make sure we never tab to an unselected button // Note that deselection of the other tabs is handled by built in Toolbar / Radiogroup // behavior. this._updateFocusButton(this.lastSelectedButton); }, // Override buttonDeselected() to send the tab to the back (behind the baseLine image) buttonDeselected : function (tab) { tab.sendToBack(); this.stopIgnoringMemberZIndex(tab); }, //> @method tabBar.getSelectedTab() (A) // Get the tab object currently selected. // @return // tab object //< getSelectedTab : function () { return this.getButtonNumber(this.getSelectedButton()); }, //> @method tabBar.selectTab() // Select a tab // @param tabNum (number) index of tab to select // @visibility external //< selectTab : function (tabNum) { this.selectedTab = tabNum; this.selectButton(tabNum); }, // Override setupButtonFocusProperties to ensure that this.selectedTab is the initial // focusButton (will then be selected by _updateFocusButton()) setupButtonFocusProperties : function () { // sync up the focus with the selection this._updateFocusButton(this.getButton(this.selectedTab)); return this.Super("setupButtonFocusProperties", arguments); }, // override the internal _updateFocusButton method to always ensure the focused tab is selected _updateFocusButton : function (buttonNum) { // Suppress selection of tabs on right-click by forcing focus back to whatever tab had // it before (this method is fired as focus tries to move to the right-clicked tab) if (!this.selectTabOnContextClick && isc.EH.rightButtonDown()) { if (this._currentFocusButton != null && this.getButton(buttonNum) != this._currentFocusButton) { var targetButtonNum = this.getButtonNumber(this._currentFocusButton); this._currentFocusButton.focus(); var _this = this; isc.Timer.setTimeout(function () { var targetTab = _this.getButton(targetButtonNum); if (!targetTab) return; if (!isc.EH.targetIsMasked(targetTab)) { targetTab.focus(); } else { _this.selectTab(targetButtonNum); } }, 0); } return; } // Note: despite the method name - this method may be called directly from button selection // which can be triggered programmatically, without a focus change. // Ensure this button actually has focus var button = this.getButton(buttonNum); if (button) button.focus(); this.Super("_updateFocusButton", arguments); if (this._currentFocusButton != null && !this._currentFocusButton.selected) { this.selectTab(this._currentFocusButton); } }, _scrollForward : function (backward, animated) { if (this.overflow == isc.Canvas.VISIBLE || !this.members || this.members.length == 0) return; var nextTab, nextTabEdge; // If we're in the process of scrolling to a tab - jump straight to the one after it if (this._scrollingToTab != null) { nextTab = this.members[this._scrollingToTab + (backward ? -1 : 1)]; if (nextTab == null) { return; } nextTabEdge = (backward ? (this.vertical ? nextTab.getTop() : nextTab.getLeft()) : (this.vertical ? nextTab.getBottom() : nextTab.getRight())); } else { var scrollSize = (this.vertical ? this.getScrollHeight() : this.getScrollWidth()); if (scrollSize <= (this.vertical ? this.getViewportHeight() : this.getViewportWidth())) return; var scrollStart = (this.vertical ? this.getScrollTop() : this.getScrollLeft()), viewSize = (this.vertical ? this.getViewportHeight() : this.getViewportWidth()); var scrollThreshold = 5; for (var i = 0; i < this.members.length; i++) { nextTab = (backward ? this.members[this.members.length - (i+1)] : this.members[i]); nextTabEdge = (backward ? (this.vertical ? nextTab.getTop() : nextTab.getLeft()) : (this.vertical ? nextTab.getBottom() : nextTab.getRight())); // Determine which tab is currently clipped var clipped = backward ? (nextTabEdge + scrollThreshold < scrollStart) : (nextTabEdge - scrollThreshold > (scrollStart + viewSize)); if (clipped) break; } } if (animated) { this._scrollingToTab = this.members.indexOf(nextTab); this.scrollTabIntoView(nextTab, backward, true, "this._completeScroll(" + this._scrollingToTab + ")"); } else this.scrollTabIntoView(nextTab, backward); }, _completeScroll : function (scrolledToTab) { if (this._scrollingToTab == scrolledToTab) delete this._scrollingToTab; }, //> @method tabBar.scrollTabIntoView() // If a tab is not currently visible for this tab-bar, scroll it into view. // Can specify where you want the tab to appear. // edge it was clipped on. // @param tab (number or ImgTab) tab to scroll into view (or index of tab to scroll into view) // @param [start] (boolean) Should the tab be scrolled to the start or end of the viewport? // If not specified the tab will be scrolled to whichever end it is // currently clipped by. // @param [animated] (boolean) If true, do an animated scroll. // @param [callback] (callback) If specified this will fire when the tab has been scrolled into // view. Will be synchronously fired if this is not an animated // scroll, or if the tab is already in view, so no scrolling occurs. //< scrollTabIntoView : function (tab, start, animated, callback) { var tabNum; if (isc.isA.Number(tab)) { tabNum = tab; tab = this.members[tab]; } else { tabNum = this.members.indexOf(tab); } if (!tab) return; // if _layoutIsDirty or _layoutInProgress, we can't guarantee that the tab is in the right place // (EG the tab may be currently drawn offscreen). // In this case wait for the layout to complete if (this._layoutIsDirty || this._layoutInProgress) { this._scrollOnLayoutComplete = [tab,start,animated,callback]; return; } var rect = tab.getRect(), xPos, yPos; // If not pased "start" parameter, we'll scroll the tab to the start of our viewport // iff its clipped to the left, otherwise to the end of our viewport. var vertical = this.vertical; if (start == null) { if (tabNum == 0) start = true; else if (tabNum == (this.members.getLength() -1)) start = false; else { if (vertical) { if (this.getScrollTop() > rect[1]) start = true; else start = false; } else { if (this.getScrollLeft() > rect[0]) start = true; else start = false; } } } if (vertical) { yPos = (start ? "top" : "bottom"); // don't scroll horizontally - leave at zero xPos = "left"; rect[2] = 0; } else { xPos = (start ? "left" : "right"); // Don't scroll vertically yPos = "top"; rect[3] = 0; } // When scrolling to the first tab, actually scroll to 0,0, rather than the edge of the // tab. if (tabNum == 0) rect[0] = rect[1] = 0; this.scrollIntoView(rect[0], rect[1], rect[2], rect[3], xPos, yPos, animated, callback); }, // Override _layoutChildrenDone -- if we have a pending scrollTabIntoView, allow it to proceed _layoutChildrenDone : function (reason, a,b,c,d) { this.invokeSuper(isc.TabBar, "_layoutChildrenDone", reason, a,b,c,d); if (this._scrollOnLayoutComplete != null) { var args = this._scrollOnLayoutComplete; this.scrollTabIntoView(args[0],args[1],args[2],args[3]); delete this._scrollOnLayoutComplete; } }, scrollForward : function (animated) { this._scrollForward(false, animated); }, scrollBack : function (animated) { this._scrollForward(true, animated); } });





© 2015 - 2025 Weber Informatics LLC | Privacy Policy