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

com.smartclient.debug.public.sc.client.widgets.TabSet.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	TabSet
//
// The TabSet class allows components on several panes to share the same space. The tabs at 
// the top can be selected by the user to show each pane. 
// 

// Tabs are configured via the tabs property, each of which has a // pane property which will be displayed in the main pane when that tab is // selected. // // @treeLocation Client Reference/Layout // @visibility external //< isc.ClassFactory.defineClass("TabSet", "Canvas"); isc.TabSet.addProperties({ // NOTE: Setting both the paneContainer and TabSet to overflow:"visible" results in an // auto-expanding TabSet. This may be appropriate as a top-level page layout when an // application is more web-style than desktop-style, eg, allows and utilizes browser-level // scrolling. overflow:"hidden", // TabBar // ---------------------------------------------------------------------------------------- //> @attr tabSet.tabs (Array of Tab : null : IRW) // // An array of tab objects, specifying the title and pane contents of each tab in the // TabSet. When developing in JavaScript, tabs are specified as an array of object // literals, not instances - see +link{Tab}. //

// You can add and remove tabs after creating the TabSet by calling +link{TabSet.addTab} // @visibility external // @example tabsOrientation //< //> @object Tab // Tabs are specified as objects, not class instances. For example, when // developing in JavaScript, a typical initialization block for a TabSet would look like // this: //

    // TabSet.create({
    //     tabs: [
    //         {title: "tab1", pane: "pane1"},
    //         {title: "tab2"}
    //     ]
    // });
    // 
// And in XML: //
    // <TabSet>
    //    <tabs>
    //        <Tab title="tab1" pane="pane1"/>
    //        <Tab title="tab2"/>
    //    </tabs>
    // </TabSet>
    // 
// // @treeLocation Client Reference/Layout/TabSet // @visibility external //< //> @attr tab.title (HTML : null : IRW) // // Specifies the title of the this tab. To change the title after the TabSet has been // created, call +link{TabSet.setTabTitle}. // // @see TabSet.setTabTitle // @visibility external //< //> @attr tab.canEditTitle (boolean : null : IRW) // // If specified, overrides the +link{TabSet.canEditTabTitles} setting, for this one tab // only. // // @see TabSet.canEditTabTitles // @visibility external //< //> @attr tab.prompt (string : null : IRW) // // Specifies the prompt to be displayed when the mouse hovers over the tab. // @visibility external //< //> @attr tab.pickerTitle (HTML : null : IRW) // If +link{tabSet.showTabPicker} is true for this TabSet, if set this property will determine // the title of the picker menu item for this tab. If unset, +link{tab.title} will be used // instead // @see TabSet.showTabPicker // @see tab.title // @group tabBarControls // @visibility external //< //> @attr tab.pane (ID or Canvas: null : IRW) // // Specifies the pane associated with this tab. You have two options for the value of // the pane attribute: //
    //
  • ID - The global ID of an already created Canvas (or subclass). //
  • Canvas - A live instance of a Canvas (or subclass). //
// You can change the pane associated with a given tab after the TabSet has been created by // calling +link{TabSet.updateTab}. // // @see TabSet.updateTab // @visibility external //< //> @attr tab.ID (identifier : null : IRW) // Optional ID for the tab, which can later be used to reference the tab. // APIs requiring a reference to a tab will accept the tabs ID // [including +link{tabSet.selectTab()}, +link{tabSet.updateTab()}, +link{tabSet.removeTab()}].
// The ID will also be passed to the +link{tabSet.tabSelected()} and +link{tabSet.tabDeselected()} // handler functions, if specified. //

// Note that if you provide an ID, it must be globally unique. // // @visibility external //< //> @attr tab.width (number : 100 : IRW) // You can specify an explicit width for the tab using this property. Note that tabs // automatically size to make room for the full title, but if you want to e.g. specify a // uniform width for all tabs in a TabSet, this property enables you to do so. // // @visibility external //< //> @attr tab.disabled (boolean : null : IRW) // If specified, this tab will initially be rendered in a disabled state. To enable or // disable tabs on the fly use the +link{tabSet.enableTab()}, and +link{tabSet.disableTab()} // methods. // @visibility external //< //> @attr tab.icon (SCImgURL : null : IRW) // If specified, this tab will show an icon next to the tab title. Note that as with // +link{Button.icon}, the URL of a tabs icon will be updated to reflect disabled state.
// If desired a click handler may be assigned to the icon, which will be fired when the user // clicks the tab. This method takes a single parameter tab, a pointer to the tab // object. //

// Note that we recommend specifying an explicit size for the icon via +link{tab.iconSize} or // +link{tab.iconWidth} and +link{tab.iconHeight}. Without an explicitly specified size, // tab sizing may be unpredictable the first time the icon image is loaded as its size will // not be known by the browser. // @visibility external // @example tabsOrientation // @see tabSet.tabIconClick //< //> @attr tab.iconSize (integer : null : IRW) // If +link{tab.icon} is specified, this property may be used to specify a size for the icon. // Per side sizing may be specified instead via +link{tab.iconWidth} and +link{tab.iconHeight}. // @visibility external //< //> @attr tab.iconWidth (integer : null : IRW) // If +link{tab.icon} is specified, this property may be used to specify a size for the icon // @visibility external //< //> @attr tab.iconHeight (integer : null : IRW) // If +link{tab.icon} is specified, this property may be used to specify a size for the icon // @visibility external //< //> @attr tab.canClose (boolean : null : IRW) // Determines whether this tab should show an icon allowing the user to dismiss the tab by // clicking on it directly. The URL for this icon's image will be derived from // +link{tabSet.closeTabIcon} by default, but may be overridden by explicitly specifying // +link{tab.closeIcon}. //

// If unset, this property is derived from +link{tabSet.canCloseTabs} // @visibility external // @example closeableTabs // @see TabSet.closeClick() //< //> @attr tab.closeIcon (SCImgURL : null : IRW) // Custom src for the close icon for this tab to display if it is closeable. // See +link{tab.canClose} and +link{tabSet.canCloseTabs}. // @visibility external //< //> @attr tab.closeIconSize (number : null :IRW) // Size of the +link{tab.closeIcon} for this tab. If unspecified the icon will be sized // according to +link{tabSet.closeTabIconSize} // @visibility external //< // --------------------------------------------------------------------------------------- //> @attr tabSet.tabBar (AutoChild : null : R) // TabBar for this TabSet, an instance of +link{TabBar}. // @visibility external //< // NOTE: tabBar is actually not created via autoChild system, but supports the same // defaults. //> @attr tabSet.tabProperties (Tab Properties : null : IR) // Properties to apply to all Tabs created by this TabSet. // @visibility external //< tabProperties:{}, // Simple Tabs // --------------------------------------------------------------------------------------- //> @attr isc.TabSet.useSimpleTabs (boolean : false : IRA) // Should we use simple button based tabs styled with CSS rather than // image based +link{class:ImgTab} tabs? //

// // If set to true the +link{tabSet.simpleTabButtonConstructor} will be used and tabs will // by styled according to +link{tabSet.simpleTabBaseStyle}. // // // If set to true tabs will instances of +link{class:Button}, styled according to the // +link{tabSet.simpleTabBaseStyle}. // // @visibility external //< //useSimpleTabs:false, //> @attr isc.TabSet.simpleTabBaseStyle (CSSStyleName : "tabButton" : [IRW]) // If this.useSimpleTabs is true, simpleTabBaseClass will be the base style used to // determine the css style to apply to the tabs.
// This property will be suffixed with the side on which the tab-bar will appear, followed // by with the tab's state (selected, over, etc), resolving to a className like // "tabButtonTopOver" // @visibility external //< simpleTabBaseStyle:"tabButton", // TabBar placement and sizing // --------------------------------------------------------------------------------------- //> @attr tabSet.tabBarPosition (Side : isc.Canvas.TOP : IR) // Which side of the TabSet the TabBar should appear on. // @group tabBar // @visibility external // @example tabsOrientation //< tabBarPosition:isc.Canvas.TOP, //> @attr tabSet.tabBarAlign (Side : see below : IR) // Alignment of the tabBar. //

// If the position of the tabBar is "top" or "bottom", then alignment must be "left" or // "right" and defaults to "left". //

// If the position of the tabBar is "left" or "right", then the alignment must be "top" or // "bottom" and defaults to "top". // // @group tabBar // @visibility external // @example tabsAlign //< //> @attr tabSet.tabBarThickness (number : 21 : IRW) // Thickness of tabBar, applies to either orientation (specifies height for horizontal, // width for vertical orientation). Note that overriding this value for TabSets that are // skinned with images generally means providing new media for the borders. // @group tabBar // @visibility external //< tabBarThickness:21, // --------------------------------------------------------------------------------------- //> @attr tabSet.selectedTab (number : 0 : IRW) // Specifies the index of the initially selected tab. // @group tabBar // @visibility external //< selectedTab:0, // --------------------------------------------------------------------------------------- //> @attr tabSet.canCloseTabs (boolean : null : IRW) // Should tabs in this tabSet show an icon allowing the user to dismiss the tab by // clicking on it directly. May be overridden for individual tabs by setting // +link{tab.canClose}. //

// The URL for this icon's image will be derived from +link{tabSet.closeTabIcon} by // default, but may be overridden by explicitly specifying +link{tab.closeIcon}. //

// Note: Currently, tabs can only show a single icon, so a closable tab will show // the close icon only even if +link{tab.icon} is set. To work around this, add the icon // as an HTML <img> tag to the +link{tab.title} property, for example: //

    //    title : "" + isc.Canvas.imgHTML("myIcon.png") + " Tab Title"
    // 
// // @see TabSet.closeClick() // @visibility external //< //> @attr tabSet.closeTabIcon (SCImgURL : [SKIN]/TabSet/close.png : IR) // Default src for the close icon for tabs to display if +link{tabSet.canCloseTabs} is true. // @visibility external //< closeTabIcon:"[SKIN]/TabSet/close.png", //> @attr tabSet.closeTabIconSize (int : 16 : IR) // Size in pixels of the icon for closing tabs, displayed when +link{canCloseTabs} is true. // @visibility external //< closeTabIconSize:16, //> @attr tabSet.showMoreTab (boolean : null : IR) // @include tabBar.showMoreTab // @visibility external //< //> @attr tabSet.moreTabCount (number : 5 : IR) // @include tabBar.moreTabCount // @visibility external //< moreTabCount:5, //> @attr tabSet.moreTabTitle (String : "More" : IR) // Title for the "More" tab. // @visibility external //< moreTabTitle:"More", //> @attr tabSet.moreTabImage (SCImgURL : "[SKINIMG]/iOS/more.png" : IR) // If +link{showMoreTab} is enabled this property determines the image to display on // the "More" tab button. // @visibility external //< moreTabImage:"[SKINIMG]/iOS/more.png", //> @attr tabSet.moreTab (AutoChild : null : R) // +link{object:Tab} to be shown when +link{showMoreTab} is enabled // more than +link{moreTabCount} tabs are provided. // @visibility external //< moreTabDefaults: { ariaRole:"tab" }, //> @attr tabSet.moreTabProperties (Tab Properties : null : IR) // Properties to apply to the "more" tab created by this TabSet. // @visibility external //< moreTabProperties:{}, //> @attr tabSet.moreTabPane (AutoChild : null : R) // Pane contents for the "more" tab based on a VLayout. Typically contains // a +link{NavigationBar} and +link{TableView}. // @visibility external //< //> @attr tabSet.moreTabPaneProperties (Canvas Properties : null : IR) // Properties to apply to the "more" tab's pane created by this TabSet. // @visibility external //< moreTabPaneProperties:{}, //> @attr tabSet.moreTabPaneDefaults (Canvas Properties : null : IR) // Default properties for the "more" tab's pane. //

// Currently constructs a VLayout with a +link{NavigationBar} and +link{TableView}. // @visibility external //< moreTabPaneDefaults:{ _constructor: "VLayout", width: "100%", height: "100%", setData : function (newData) { this.creator.moreTabPaneTable.setData(newData); } }, moreTabPaneNavBarDefaults:{ _constructor: "NavigationBar", controls: ["titleLabel"], autoParent: "moreTabPane" }, moreTabPaneTableDefaults:{ _constructor: "TableView", width: "100%", height: "100%", recordNavigationClick : function (record) { this.creator._tabSelected(record.button); }, autoParent: "moreTabPane" }, // ----------------------------------------------------------- // Tab bar controls //> @attr tabSet.tabBarControls (Array : ["tabScroller", "tabPicker"] : [IRA]) // This property determines what controls should show up after the tabBar for this tabSet. // Standard controls can be included using the strings "tabScroller" and // "tabPicker". These show the standard controls to scroll to clipped tabs, // or pick them directly from a menu, and show up only if +link{tabSet.showTabScroller} or // +link{tabSet.showTabPicker} is true and there is not enough space available to show all // the tabs in the tab-bar. //

// Additional controls can be included by adding any widget to this array. Controls will // show up in the order in which they are specified. For example, the following code would // add a button in the tabBar area, while preserving the normal behavior of the tabScroller // and tabPicker: //

    // isc.TabSet.create({
    //     width:300,
    //     tabs : [
    //         { title: "Tab one" }
    //     ],
    //     tabBarControls : [
    //         isc.ImgButton.create({
    //             src:"[SKINIMG]/actions/add.png",
    //             width:16, height:16,
    //             layoutAlign:"center"
    //         }),
    //         "tabScroller", "tabPicker"
    //     ]
    // });
    // 
// // @group tabBarControls // @visibility external //< tabBarControls : ["tabScroller", "tabPicker"], //> @attr tabSet.showTabScroller (boolean : true : [IR]) // If there is not enough space to display all the tab-buttons in this tabSet, should // scroller buttons be displayed to allow access to tabs that are clipped? // @visibility external // @group tabBarControls //< showTabScroller:true, //> @attr tabSet.showTabPicker (boolean : true : [IR]) // If there is not enough space to display all the tab-buttons in this tabSet, should // a drop-down "picker" be displayed to allow selection of tabs that are clipped? // @visibility external // @group tabBarControls //< showTabPicker:true, //> @attr tabSet.tabBarControlLayout (AutoChild : null : IR) // +link{AutoChild} of type +link{Layout} that holds the +link{tabBarControls} as well as // the built-in controls such as the +link{showTabPicker,tab picker menu}. // @visibility external //< tabBarControlLayoutConstructor:"Layout", tabBarControlLayoutDefaults:{}, //>Animation //> @attr tabSet.animateTabScrolling (boolean : true : [IR]) // If +link{tabSet.showTabScroller} is true, should tabs be scrolled into view via an // animation when the user interacts with the scroller buttons? // @visibility external // @group tabBarControls //< animateTabScrolling:true, // @attr tabSet.scrollerButtonSize (number : 16 : [IR]) // If +link{tabSet.showTabScroller} is true, this property governs the size of scroller // buttons. Applied as the width of buttons if the tabBar is horizontal, or the height // if tabBar is vertical. Note that the other dimension is determined by // +link{tabBarThickness,this.tabBarThickness} // @group tabBarControls // @visibility external //< scrollerButtonSize:16, //> @attr tabSet.pickerButtonSize (number : 16 : [IR]) // If +link{tabSet.showTabPicker} is true, this property governs the size of tab-picker // button. Applied as the width of buttons if the tabBar is horizontal, or the height // if tabBar is vertical. Note that the other dimension is determined by // +link{tabBarThickness,this.tabBarThickness} // @group tabBarControls // @visibility external //< pickerButtonSize:16, //> @attr tabSet.skinImgDir (string : "images/TabSet/" : [IR]) // @include Canvas.skinImgDir //< skinImgDir:"images/TabSet/", //> @attr tabSet.symmetricScroller (boolean : true : [IR]) // If this TabSet is showing +link{tabSet.showTabScroller,tab scroller buttons}, this property // determines whether the +link{tabSet.scrollerHSrc} and +link{tabSet.scrollerVSrc} media // will be used for vertical and horizontal tab-bar scroller buttons, or whether separate // media should be used for each possible +link{tabSet.tabBarPosition,tabBarPosition} based // on the +link{tabSet.scrollerSrc} property for this tabSet. // @group tabBarScrolling // @visibility external //< symmetricScroller:true, //> @attr tabSet.scrollerSrc (SCImgURL : "[SKIN]/scroll.gif" : [IR]) // If this TabSet is showing +link{tabSet.showTabScroller,tab scroller buttons}, and // +link{tabSet.symmetricScroller,symmetricScroller} is false, this property governs the base // URL for the tab bar back and forward scroller button images. //

// Note that if +link{tabSet.symmetricScroller,symmetricScroller} is true, // +link{tabSet.scrollerHSrc} and +link{tabSet.scrollerVSrc} will be used instead. //

// To get the path to the image to display, this base URL will be modified as follows: //

    //
  • If appropriate a state suffix of "Down" or "Disabled" will be // appended.
  • //
  • The +link{tabSet.tabBarPosition,tabBarPosition} for this tabSet will be appended.
  • //
  • A suffix of "forward" or "back" will be appended for the // forward or backward scrolling button.
  • //
// For example - if the scrollerSrc is set to "[SKIN]scroll.gif", the image // displayed for the back-scroller button on a tabSet with tabBarPosition set to // "top" and symmetricScroller set to false would be one of // "[SKIN]scroll_top_back.gif", "[SKIN]scroll_Down_top_back.gif", // and "[SKIN]scroll_Disabled_top_back.gif". //

// Note that for best results the media should be sized to match the scroller button sizes, // determined by +link{tabSet.tabBarThickness} and +link{tabSet.scrollerButtonSize}. // @see tabSet.symmetricScroller // @group tabBarScrolling // @visibility external //< scrollerSrc:"[SKIN]/scroll.gif", //> @attr tabSet.scrollerHSrc (SCImgURL :"[SKIN]hscroll.gif" : [IR]) // If this TabSet is showing +link{tabSet.showTabScroller,tab scroller buttons}, and // +link{tabSet.symmetricScroller,symmetricScroller} is true, this property governs the base // URL for the tab bar back and forward scroller button images for horizontal tab bars [IE for // tab sets with +link{tabSet.tabBarPosition,tabBarPosition} set to "top" or "bottom"]. //

// Note that if +link{tabSet.symmetricScroller,symmetricScroller} is false, // +link{tabSet.scrollerSrc} will be used instead. //

// To get the path to the image to display, this base URL will be modified as follows: //

    //
  • If appropriate a state suffix of "Down" or "Disabled" will be // appended.
  • //
  • A suffix of "forward" or "back" will be appended for the // forward or backward scrolling button.
  • //
// For example - if the scrollerHSrc is set to "[SKIN]hscroll.gif", the image // displayed for the back-scroller button on a tabSet with tabBarPosition set to // "top" and symmetricScroller set to true would be one of // "[SKIN]hscroll_back.gif", "[SKIN]hscroll_Down_back.gif", // and "[SKIN]hscroll_Disabled_back.gif". //

// Note that for best results the media should be sized to match the scroller button sizes, // determined by +link{tabSet.tabBarThickness} and +link{tabSet.scrollerButtonSize}. // @see tabSet.symmetricScroller // @group tabBarScrolling // @visibility external //< scrollerHSrc:"[SKIN]hscroll.gif", //> @attr tabSet.scrollerVSrc (SCImgURL :"[SKIN]vscroll.gif" : [IR]) // If this TabSet is showing +link{tabSet.showTabScroller,tab scroller buttons}, and // +link{tabSet.symmetricScroller,symmetricScroller} is true, this property governs the base // URL for the tab bar back and forward scroller button images for vertical tab bars [IE for // tab sets with +link{tabSet.tabBarPosition,tabBarPosition} set to "left" or "right"]. //

// Note that if +link{tabSet.symmetricScroller,symmetricScroller} is false, // +link{tabSet.scrollerSrc} will be used instead. //

// To get the path to the image to display, this base URL will be modified as follows: //

    //
  • If appropriate a state suffix of "Down" or "Disabled" will be // appended.
  • //
  • A suffix of "forward" or "back" will be appended for the // forward or backward scrolling button.
  • //
// For example - if the scrollerVSrc is set to "[SKIN]vscroll.gif", the image // displayed for the back-scroller button on a tabSet with tabBarPosition set to // "left" and symmetricScroller set to true would be one of // "[SKIN]vscroll_back.gif", "[SKIN]vscroll_Down_back.gif", // and "[SKIN]vscroll_Disabled_back.gif". //

// Note that for best results the media should be sized to match the scroller button sizes, // determined by +link{tabSet.tabBarThickness} and +link{tabSet.scrollerButtonSize}. // @see tabSet.symmetricScroller // @group tabBarScrolling // @visibility external //< scrollerVSrc:"[SKIN]vscroll.gif", //> @attr tabSet.showScrollerRollOver (boolean : false : [IR]) // set this to true to show scroller rollover images when the mouse is over the scroller // buttons // @group tabBarScrolling //< //> @attr tabSet.scrollerProperties (Object : null : [IR]) // Properties set here override those supplied by default when creating // the scroller control. // @group tabBarScrolling //< //> @attr tabSet.symmetricPickerButton (boolean : true : [IR]) // If this TabSet is showing a +link{tabSet.showTabPicker,tab picker button}, this // property determines whether the +link{tabSet.pickerButtonHSrc} and // +link{tabSet.pickerButtonVSrc} media will be used for vertical and horizontal tab-bar // picker buttons, or whether separate media should be used for each possible // +link{tabSet.tabBarPosition,tabBarPosition} based on the +link{tabSet.pickerButtonSrc} // property for this tabSet. // @group tabBarScrolling // @visibility external //< symmetricPickerButton:true, //> @attr tabSet.pickerButtonSrc (SCImgURL : "[SKIN]/picker.gif" : [IR]) // If +link{tabSet.showTabPicker} is true, this property governs the base URL for the picker // button image, when +link{tabSet.symmetricPickerButton} is set to false //

// Note that if symmetricPickerButton is true, the +link{tabSet.pickerButtonHSrc} // and +link{tabSet.pickerButtonVSrc} properties will be used instead. //

// To get the path to the image to display, this base URL will be modified as follows: //

    //
  • If appropriate a state suffix of "Down" or "Disabled" will be // appended.
  • //
  • The +link{tabSet.tabBarPosition,tabBarPosition} for this tabSet will be appended.
  • //
// @see tabSet.symmetricPickerButton // @group tabBarScrolling // @visibility external //< pickerButtonSrc:"[SKIN]/picker.gif", //> @attr tabSet.pickerButtonHSrc (SCImgURL : "[SKIN]hpicker.gif" : [IR]) // If +link{tabSet.showTabPicker} is true, and +link{tabSet.symmetricPickerButton} is // set to true, this property governs the base URL for the picker // button image, when displayed in a horizontal tab-bar [IE +link{tabSet.tabBarPosition} is // set to "top" or "bottom"]. //

// Note that if symmetricPickerButton is false, the +link{tabSet.pickerButtonSrc} // property will be used instead. //

// This base URL will have a suffix of "Down" appended when the user holds the // mouse down over the button, and "Disabled" if the tabset as a whole is // disabled. // @see tabSet.symmetricPickerButton // @group tabBarScrolling // @visibility external //< pickerButtonHSrc:"[SKIN]hpicker.gif", //> @attr tabSet.pickerButtonVSrc (SCImgURL : "[SKIN]vpicker.gif" : [IR]) // If +link{tabSet.showTabPicker} is true, and +link{tabSet.symmetricPickerButton} is // set to true, this property governs the base URL for the picker // button image, when displayed in a verricaL tab-bar [IE +link{tabSet.tabBarPosition} is // set to "LEFT" or "right"]. //

// Note that if symmetricPickerButton is false, the +link{tabSet.pickerButtonSrc} // property will be used instead. //

// This base URL will have a suffix of "Down" appended when the user holds the // mouse down over the button, and "Disabled" if the tabset as a whole is // disabled. // @see tabSet.symmetricPickerButton // @group tabBarScrolling // @visibility external //< pickerButtonVSrc:"[SKIN]vpicker.gif", //> @attr tabSet.tabPickerProperties (Object : null : [IR]) // Properties set here override those supplied by default when creating // the picker control. // @group tabBarScrolling //< // PaneContainer // ---------------------------------------------------------------------------------------- //> @attr tabSet.paneContainer (AutoChild : null : R) // Container where the component specified by +link{tab.pane} is shown. //

// Note: paneContainer and showEdges:true for rounded tabsets: you can enable decorative // image-based edges on the paneContainer by setting +link{Canvas.showEdges,showEdges:true} // via paneContainerDefaults (to skin all tabsets) or paneContainerProperties (to use // edges on one instance). In this structure, the +link{group:baseLine} should use media // that matches the appearance of the decorative edges and fully overlaps the edge of the // paneContainer that it is adjacent to. In the most typical appearance (symmetric edges // on all 4 sides), both +link{tabBar.baseLineCapSize} and +link{tabBar.baseLineThickness} // match the +link{canvas.edgeSize,edgeSize} set on the paneContainer. See the // load_skin.js file for the "SmartClient" skin for an example of setting all relevant // properties. //

// To disable edges for a particular TabSet, which you may want to do for a TabSet that // is already within a clearly defined container, configure the paneContainer to show only // it's top edge: //

    //      paneContainerProperties : { customEdges:["T"] },
    // 
// To completely flatten even the top edge of the TabSet: //
    //      paneContainerProperties : { customEdges:["T"] },
	//      tabBarProperties :{ baseLineCapSize:0 },
    // 
// This "flattens" the baseLine so that only the center image is used. // // @visibility external //< // XXX: advice above suboptimal: // - in general, the StretchImg baseline is using different media names for the same media. // Could be fixed by passing custom sib.items to the baseline // - when we "flatten" as above, the paneContainer is still rendering a top edge and still // using 3 pieces of media, it's just occluded by the baseline. Ideally, we'd turn the // edges off entirely, but by default this would cause the baseline to actually overlap // widgets show in the paneContainer, so a margin would need to be set in CSS to // compensate - more complicated to explain paneContainerConstructor:"PaneContainer", //> @attr tabSet.paneContainerClassName (CSSStyleName : null : IRW) // CSS style used for the paneContainer. // @visibility external //< paneContainerClassName:"tabSetContainer", //> @attr tabSet.paneContainerOverflow (Overflow : isc.Canvas.AUTO : IRWA) // Specifies the overflow of the pane container (the component that holds the pane contents // for all tabs). By default this is set to "auto", meaning the pane container will // automatically introduce scrolling when the pane contents exceed the TabSet's specified // size. //

// For other values and their meaning, see +link{Overflow} // // @visibility external //< paneContainerOverflow:isc.Canvas.AUTO, //> @method tabSet.setPaneContainerOverflow() // Update +link{paneContainerOverflow} after creation. // // @param newOverflow (Overflow) new overflow setting // @visibility external //< setPaneContainerOverflow : function (newOverflow) { this.paneContainerOverflow = newOverflow; if (this.paneContainer) this.paneContainer.setOverflow(newOverflow); }, //> @attr tabSet.symmetricEdges (boolean : true : IR) // If this tabSet will +link{tabSet.showPaneContainerEdges,show edges} for the paneContainer, // this property determines whether the same edge media will be used regardless of the tab // bar position, or whether different media should be used (necessary if the edge appearance is // not symmetrical on all sides). //

// If this property is set to false the paneContainer edge image URLs will be prefixed with // the tabBarPosition of the tabSet - for example "[SKIN]edge_top_T.gif" rather // than just "[SKIN]edge_T.gif". //

// When symmetricEdges is false, custom edge sizes for the pane container may be // specified via +link{tabSet.topEdgeSizes} et al, and custom edge offsets via // +link{tabSet.topEdgeOffsets} et al. // @visibility external //< symmetricEdges:true, //> @type EdgeSizes // Object used to specify custom edge sizes or offsets. // Specified as an object where defaultSize will map to the default edge size or // offset for the canvas (+link{canvas.edgeSize}, or +link{canvas.edgeOffset} and // top, left, right and // bottom will map to the // +link{edgedCanvas.edgeTop,edgeTop}/+link{edgedCanvas.edgeOffsetTop,edgeOffsetTop}, // +link{edgedCanvas.edgeLeft,edgeLeft}/+link{edgedCanvas.edgeOffsetLeft,edgeOffsetLeft}, // +link{edgedCanvas.edgeRight,edgeRight}/+link{edgedCanvas.edgeOffsetRight,edgeOffsetRight}, // and +link{edgedCanvas.edgeBottom,edgeBottom}/+link{edgedCanvas.edgeOffsetBottom,edgeOffsetBottom} // attributes on the paneContainer respectively. Note that not all these properties have to be // set - if unset standard edge sizing rules will apply. // @visibility external //< //> @attr tabSet.leftEdgeSizes (EdgeSizes : null : IR) // If this tabSet will +link{tabSet.showPaneContainerEdges,show edges} for the paneContainer, // and +link{tabSet.symmetricEdges} is set to false, the leftEdgeSizes, // rightEdgeSizes, topEdgeSizes and bottomEdgeSizes // properties allow the sizes of edges for the paneContainer to be customized depending on // the +link{tabSet.tabBarPosition}. //

// The attribute should be specified an +link{type:EdgeSizes,edgeSizes map}, specifying the // desired edge sizes where for the appropriate +link{tabSet.tabBarPosition}. // @visibility external //< //> @attr tabSet.topEdgeSizes (EdgeSizes : null : IR) // @include tabSet.leftEdgeSizes // @visibility external //< //> @attr tabSet.bottomEdgeSizes (EdgeSizes : null : IR) // @include tabSet.leftEdgeSizes // @visibility external //< //> @attr tabSet.rightEdgeSizes (EdgeSizes : null : IR) // @include tabSet.leftEdgeSizes // @visibility external //< //> @attr tabSet.leftEdgeOffsets (EdgeSizes : null : IR) // If this tabSet will +link{tabSet.showPaneContainerEdges,show edges} for the paneContainer, // and +link{tabSet.symmetricEdges} is set to false, the leftEdgeOffsets, // rightEdgeOffsets, topEdgeOffsets and bottomEdgeOffsets // properties allow the offsets of edges for the paneContainer to be customized depending on // the +link{tabSet.tabBarPosition}. //

// The attribute should be specified an +link{type:EdgeSizes,edgeSizes map}, specifying the // desired edge offsets where for the appropriate +link{tabSet.tabBarPosition}. // @visibility external //< //> @attr tabSet.rightEdgeOffsets (EdgeSizes : null : IR) // @include tabSet.leftEdgeOffsets // @visibility external //< //> @attr tabSet.topEdgeOffsets (EdgeSizes : null : IR) // @include tabSet.leftEdgeOffsets // @visibility external //< //> @attr tabSet.bottomEdgeOffsets (EdgeSizes : null : IR) // @include tabSet.leftEdgeOffsets // @visibility external //< //> @attr tabSet.showPaneContainerEdges (boolean : null : IRWA) // Should the paneContainer for this tabset show +link{Canvas.showEdges,edges}. // // @visibility external //< // set to null not false by default so we pick up the value from paneContainerDefaults // for backCompat (pre 6.1) //> @attr tabSet.paneMargin (integer : 0 : IR) // Space to leave around the panes in our paneContainer // @visibility external //< //paneMargin:0 //> @attr tabSet.canEditTabTitles (boolean : false : IRW) // If true, users can edit the titles of tabs in this TabSet when the // +link{titleEditEvent,titleEditEvent} fires. You can override this behavior per tab // with the +link{Tab.canEditTitle} property. // @visibility external //< //> @attr tabSet.titleEditEvent (TabTitleEditEvent : "doubleClick" : IRW) // The event that triggers title editing on this TabSet. // @see canEditTabTitles // @see Tab.canEditTitle // @visibility external //< //> @type TabTitleEditEvent // An event that triggers title editing in a TabSet. // @value "click" Start editing when the user single-clicks a tab title // @value "doubleClick" Start editing when the user double-clicks a tab title // @visibility external //< //> @attr tabSet.titleEditor (AutoChild : null : R) // TextItem we use to edit tab titles in this TabSet. You can override this property // using the normal +link{groupDef:AutoChild} facilities. // @see canEditTabTitles // @see Tab.canEditTitle // @see TabSet.editTabTitle // @visibility external //< // Explicitly call out titleEditorProperties as TextItem config so it gets // picked up in SGWT //> @attr tabSet.titleEditorProperties (TextItem properties : null : IR) // Properties for the auto-generated +link{tabSet.titleEditor}. This is the text item // we use to edit tab titles in this tabSet. // @see tabSet.titleEditor // @see canEditTabTitles // @visibility external //< //> @attr tabSet.titleEditorLeftOffset (Integer : null : IRW) // If set, offsets the tab title editor further in from the left-hand edge of the tab, by // the number of pixels set in this property. Note that the editor is always offset to // avoid overlapping the endcaps of the tab; this property is applied on top of that // default offset. // @see titleEditorRightOffset // @see titleEditorTopOffset // @visibility external //< //> @attr tabSet.titleEditorRightOffset (Integer : null : IRW) // If set, offsets the tab title editor further in from the right-hand edge of the tab, by // the number of pixels set in this property. Note that the editor is always offset to // avoid overlapping the endcaps of the tab; this property is applied on top of that // default offset. // @see titleEditorLeftOffset // @see titleEditorTopOffset // @visibility external //< //> @attr tabSet.titleEditorTopOffset (Integer : null : IRW) // If set, offsets the tab title editor further down from the top edge of the tab, by the // number of pixels set in this property. You can use this property, together with the // left and right offset properties, to fine tune positioning of the editor within or // around the tab button.

// Note: The height of the editor is an attribute of the editor itself, and can be // set by specifying a "height" property in +link{titleEditor,titleEditorDefaults}. // @see titleEditorLeftOffset // @see titleEditorRightOffset // @visibility external //< titleEditorDefaults: { name: "title", type: "text", showTitle: false } }); isc.TabSet.addMethods({ //> @attr tabSet.simpleTabButtonConstructor (Class : Button : IRA) // Tab button constructor if +link{tabSet.useSimpleTabs} is true. // @visibility external //< simpleTabButtonConstructor: isc.Button, //> @method tabSet.initWidget() (A) // Initialize the TabSet object //< initWidget : function () { // disallow 'showEdges:true' on tabSets - this is an effect the user essentially never wants // as edges would encompass the tab-bar as well as the (rectangular) pane container. this.showEdges = false; // call the superclass function this.Super("initWidget",arguments); if (this.tabs == null) this.tabs = []; if (this.tabBarDefaults == null) this.tabBarDefaults = {}; // NOTE: tabInstanceDefaults is old name this.tabProperties = this.tabProperties || this.tabInstanceDefaults || {}; var pos = this.tabBarPosition; // if tabBarAlign is unset, set default based on tabBarPosition if (this.tabBarAlign == null) { this.tabBarAlign = ((pos == "left" || pos == "right") ? "top" : "left"); } // If this has the 'useSimpleTabs' property set to true, create buttons rather than imgTabs // as tabs in the tab bar. Saves on creating a number of widgets for performance. if (this.useSimpleTabs) { // also update the styling this.tabBarDefaults.buttonConstructor = this.simpleTabButtonConstructor; // eg base + "Right" (derived from "right") this.tabProperties.baseStyle = this.simpleTabBaseStyle + pos.substring(0,1).toUpperCase() + pos.substring(1); this.tabProperties.ariaRole = "tab"; } this.makeTabBar(); this.makePaneContainer(); this.createPanes(); }, tabBarConstructor:isc.TabBar, //> @method tabSet.makeTabBar() (A) // Instantiates a tabBar for this tabSet, and then adds it as a child of // the tabSet. starts with tabBarDefaults and adds additional, tabSet-specific properties // @visibility internal //< makeTabBar : function () { if (this.tabs == null) return; var tabBarIsVertical = (this.tabBarPosition == isc.Canvas.LEFT || this.tabBarPosition == isc.Canvas.RIGHT), align = this.tabBarAlign; var tabs = this.tabs.duplicate(), undef; for (var i = 0; i < tabs.length; i++) { for (var j in this.tabProperties) { if (tabs[i][j] === undef) tabs[i][j] = this.tabProperties[j]; } } // assemble tabBar properties var tabBarProperties = isc.addProperties({ // selectTabOnContextClick: we suppress this behavior by default - this is an undocumented // flag to allow selection of tabs on context click selectTabOnContextClick:this.selectTabOnContextClick, ID:this.getID() + "_tabBar", width: (tabBarIsVertical ? this.tabBarThickness : "100%"), height: (tabBarIsVertical ? "100%" : this.tabBarThickness), // Default the tab bar to having the same accessKey as the tabSet accessKey: this.accessKey, // If the user has specified a tabIndex for the tabSet, apply it to the tabBar as well tabIndex: this.tabIndex, // Passes in the user-specified tabs array. // This is a simple way for the developer to specify title / size / etc. for each tab // Note - we copy the tabs array rather than pointing at the same array. // the tabSet should manage the tabs and call the appropriate actions on the tabBar. tabs:tabs, align:this.tabBarAlign, // tabBar is set vertical or not depending on the value of tabBarPosition. vertical: tabBarIsVertical ? true : false, // the initially selectedTab is passed in. selectedTab:this.selectedTab, // More tab settings showMoreTab:this.showMoreTab, moreTabCount:this.moreTabCount, moreTab:this.createMoreTab(), // When showing a "more" button, allow buttons to be re-selected. allowButtonReselect: this.showMoreTab ? true : false, // Override buttonSelected() to fire _tabSelected() on this widget // Note: this method is only fired on actual selection change - repeated clicks on // the buttons should not fire these methods. // _tabSelected will handle firing the public tabSelected/tabDeselected handlers // as well as hiding/showing panes. // Note that standard TabBar buttonSelected/deselected already handles moving deselected // tab behind the baseline image, etc. buttonSelected : function (button) { this.Super("buttonSelected", arguments); //call _tabSelected() on this tabSet to trigger any selection actions if (this.parentElement != null) { this.parentElement._tabSelected(button); } }, // notify the tabset if a tab resizes childResized : function () { this.Super("childResized", arguments); if (this.parentElement != null) { this.parentElement._tabResized(); } }, // Override showContextMenu -- if this event was bubbled up a right click on one of our tabs, // fire the special showTabContextMenu method showContextMenu : function () { var target = isc.EH.getTarget(); if (this.getButtons().contains(target)) { var tabSet = this.parentElement, tabObj = tabSet.getTabObject(target); if (tabSet.showTabContextMenu(tabSet, tabObj) == false) return false; } return this.Super("showContextMenu", arguments); }, // other properties tabBarPosition:this.tabBarPosition, tabBarAlign:this.tabBarAlign, autoDraw:false }, this.tabBarDefaults, this.tabBarProperties); // create tabBar and add as child. NOTE: make available as this.tabBar as well since it's // declared as an autoChild. For the same reason, add a "creator" property tabBarProperties.creator = this; this.tabBar = this._tabBar = this.tabBarConstructor.create(tabBarProperties); this.addChild(this._tabBar); }, // Documented under registerStringMethods showTabContextMenu:function () {}, createMoreTab : function () { if (!this.showMoreTab) return null; // Hold onto pane independently of the tab because the pane will change // to show tab panes of the selected "more" tab. this.moreTabPane = this.createAutoChild("moreTabPane", this.moreTabPaneProperties); this.addAutoChild("moreTabPaneNavBar", {title: this.moreTabTitle}); this.moreTabPaneTable = this.addAutoChild("moreTabPaneTable"); var moreTab = isc.addProperties({ title: this.moreTabTitle, icon: this.moreTabImage, pane: this.moreTabPane, // Mark more tab so it can be recognized in the tabbar moreTab: true }, this.moreTabDefaults, this.moreTabProperties); var undef; for (var j in this.tabProperties) { if (moreTab[j] === undef) moreTab[j] = this.tabProperties[j]; } this.moreTab = moreTab; return moreTab; }, rebuildMorePane : function () { this.moreTabPane.setData(this.getMorePaneRecords()); }, getMorePaneRecords : function () { var tabSet = this, records = [] ; for (var i = 0; i < this.tabs.length; i++) { var tabButton = this.getTab(this.tabs[i]); if (tabButton.isVisible()) continue; var tabObject = this.getTabObject(tabButton); var icon = (tabObject.icon != null ? isc.Page.getImgURL(tabObject.icon) : null); records[records.length] = { icon: icon, title: tabObject.title, pane: tabObject.pane, button: tabButton }; } return records; }, // override setAccessKey and setTabIndex to manage the accessKey / tabIndex of the // tab-bar setTabIndex : function (index) { this.Super("setTabIndex", arguments) if (this._tabBar != null) this._tabBar.setTabIndex(index); }, // setAccessKey() // apply the accessKey to the tabBar, which will in turn apply it to the focus-tab. setAccessKey : function (accessKey) { this.Super("setAccessKey", arguments); if (this._tabBar != null) this._tabBar.setAccessKey(accessKey); }, //> @method tabSet.createPanes() // converts any tab.pane object literals to canvii // @visibility internal //< createPanes : function () { for (var i = 0; i < this.tabs.length; i++) { var tab = this.tabs[i], pane = tab.pane ; if (pane == null) continue; tab.pane = this.createPane(pane, tab); } }, //> @attr tabSet.disablePaneWithTab (boolean : true : IRW) // If true when a tab is enabled or disabled it's pane will also be enabled / disabled. // @visibility internal //< disablePaneWithTab:true, //> @method tabSet.createPane() // (Internal method) // Given a pane object, create a canvas from it, and prepare it to be made a pane of this // object. // Creates canvas from properties object. // Ensures canvas is deparented / hidden. // Returns canvas. // @param pane (object | canvas) object literal / canvas to be made into a pane // @param tab (object | ImgTab) tab to which the pane is being applied // @visibility internal //< createPane : function (pane, tab) { if (pane == null) return pane; // handle string name, autoChild, props object if (!isc.isA.Canvas(pane)) pane = this.createCanvas(pane); if (pane == null) return pane; // make sure the pane is hidden before we add it to the pane container - otherwise it will // draw before the tab is actually selected pane.hide(); // If the tab is disabled, disable the pane (if appropriate) if (this.disablePaneWithTab && tab && tab.disabled) { pane.setDisabled(tab.disabled); } // add the pane as a member to the paneContainer right away. // // Note: previously we did the addMember in updateTab() and _showTab(). Now we also do it // here - the reason is that it immediately establishes the parent-child relationship that // the ExampleViewer relies on to correctly render a view. In the ExampleViewer we scan // for top-level Canvases and add them to a view Canvas - if this addMember isn't here, // we'll mistakenly add panes declared inline in a TabSet constructor block as top-level // canvases. // // We still must do the addMember in updateTab() and _showTab() because tabSelected() may // be overridden to provide a new pane. this.paneContainer.addMember(pane); return pane; }, makePaneContainer : function () { var props = { ID: this.getID() + "_paneContainer", _generated: false, className:this.paneContainerClassName, layoutMargin:(this.paneMargin || 0), overflow:this.paneContainerOverflow, _createEdgedCanvas : function () { var edgedCanvas = this.Super("_createEdgedCanvas", arguments); edgedCanvas.addMethods({ _asymmetricEdgePrefixes:{top:"_top",left:"_left",bottom:"_bottom",right:"_right"}, getEdgePrefix : function (edgeName) { var pc = this.eventProxy, tabSet = pc ? pc.creator : null; if (tabSet && !tabSet.symmetricEdges) { return this._asymmetricEdgePrefixes[tabSet.tabBarPosition]; } } }); return edgedCanvas; } }; // NOTE: these dynamic defaults will override any static defaults defined in // this.paneContainerDefaults, (but may be overridden by attributes in // this.paneContainerProperties) // For back-compat, if showPaneContainerEdges / getPaneContainerCustomEdges() don't have // an explicit value, don't apply them to this object so we continue to pick up // showEdges/customEdges from the paneContainerDefaults block if (this.showPaneContainerEdges != null) props.showEdges = this.showPaneContainerEdges; if (this.getPaneContainerEdges && this.getPaneContainerEdges() != null) { props.customEdges = this.getPaneContainerEdges(); } // asymmetricEdges needs support for asymmetric edge sizes and offsets if (!this.symmetricEdges) { var sizes = this[this._asymmetricEdgeSizePropertyMap[this.tabBarPosition]]; if (sizes && sizes.defaultSize != null) props.edgeSize = sizes.defaultSize; if (sizes && sizes.bottom != null) props.edgeBottom = sizes.bottom; if (sizes && sizes.top != null) props.edgeTop = sizes.top; if (sizes && sizes.left != null) props.edgeLeft = sizes.left; if (sizes && sizes.right != null) props.edgeRight = sizes.right; var offsets = this[this._asymmetricEdgeOffsetPropertyMap[this.tabBarPosition]]; if (offsets && offsets.defaultSize != null) props.edgeOffset = offsets.defaultSize; if (offsets && offsets.bottom != null) props.edgeOffsetBottom = offsets.bottom; if (offsets && offsets.top != null) props.edgeOffsetTop = offsets.top; if (offsets && offsets.left != null) props.edgeOffsetLeft = offsets.left; if (offsets && offsets.right != null) props.edgeOffsetRight = offsets.right; } this.addAutoChild("paneContainer", props); }, // For efficiency avoid assembling asymmetric edge size / offset property names on the fly _asymmetricEdgeSizePropertyMap : { top:"topEdgeSizes", bottom:"bottomEdgeSizes", left:"leftEdgeSizes", right:"rightEdgeSizes" }, _asymmetricEdgeOffsetPropertyMap : { top:"topEdgeOffsets", bottom:"bottomEdgeOffsets", left:"leftEdgeOffsets", right:"rightEdgeOffsets" }, //> @attr tabSet.showPartialEdges (boolean : false : [IRA]) // If the paneContainer for this tab set is showing +link{Canvas.showEdges,edges}, setting this // attribute to true will set the paneContainer to show // +link{canvas.customEdges,customEdges} for the three sides opposing the tabBarPosition. // @visibility external //< //> @method tabSet.getPaneContainerEdges() [A] // If the paneContainer for this tab set is showing +link{Canvas.showEdges,edges}, this // method can be used to specify (dynamically) which +link{canvas.customEdges,customEdges} to // show. Called when the pane creator is created. //

// Default implementation will return null unless +link{tabSet.showPartialEdges,showPartialEdges} // is true, in which case it will return the three edges opposite the // +link{tabSet.tabBarPosition,tabBarPosition}. // @return (array) array of custom edges to show // @visibility external //< getPaneContainerEdges : function () { if (this.showPartialEdges) { if (this.tabBarPosition == "bottom") return ["T","L","R"]; else if (this.tabBarPosition == "left") return ["T","B","R"]; else if (this.tabBarPosition == "right") return ["T","B","L"]; else return ["B","L","R"]; } return null; }, // override draw to make sure we have a tab selected, and to fire 'tabSelected()' on the tab draw : function (a,b,c,d) { if (this.tabs && this.tabs.length > 0) { var selectedTab = this.getSelectedTabNumber(); // Don't allow a bad selectedTab value to persist. if (!isc.isA.Number(selectedTab) || selectedTab < 0) selectedTab = this.selectedTab = 0; // Ensure it's selected in the tab-bar - will no op if already selected, otherwise // will perform selection and fire our handlers this._tabBar.selectTab(selectedTab); } this.invokeSuper(isc.TabSet, "draw", a,b,c,d); this.fixLayout(); }, //> @method tabSet.setTabTitle() (A) // Changes the title of a tab // @param tab (Tab | number | ID) // @param title (HTML) new title // @visibility external // @example titleChange //< setTabTitle : function (tab, title) { this.getTabObject(tab).title = title; this.getTab(tab).setTitle(title); // reset the menu to pick up the new title this.resetTabPickerMenu(); }, //> @method tabSet.setTabIcon() (A) // Changes the icon for a tab // @param tab (Tab | number | ID) tab to update // @param icon (SCImgURL) new icon // @visibility external //< setTabIcon : function (tab, icon) { this.setTabProperties(tab, {icon:icon}); }, //>@method tabSet.enableTab() // If the specified tab is disabled, enable it now. // @param tab (Tab | number | ID) // @see tab.disabled // @visibility external //< enableTab : function (tab) { this.setTabDisabled(tab, false); }, //>@method tabSet.disableTab() // If the specified tab is enabled, disable it now. // @param tab (Tab | number | ID) // @see tab.disabled // @visibility external //< disableTab : function (tab) { this.setTabDisabled(tab, true); }, //>@method tabSet.setTabProperties() (A) // Apply properties to an existing tab in a tabSet. // @param tab (Tab | number | ID) Identifier for the tab to be modified // @param properties (object) Javascript object containing the set of properties to be applied // to the tab. // @visibility external //< setTabProperties : function (tab, properties) { if (!properties) return; if (properties.ID != null) { this.logWarn("setTabProperties(): Unable to modify ID for an existing tab - ignoring " + "this property"); delete properties.ID; } // A couple of properties require special APIs if (properties.pane != null) { this.updateTab(tab, properties.pane); delete properties.pane; } if (properties.disabled != null) { this.setTabDisabled(tab, properties.disabled); delete properties.disabled; } var tabObject = this.getTabObject(tab), tab = this.getTab(tab); if (!tabObject) return; isc.addProperties(tabObject, properties); if (tab) { tab.setProperties(properties); } // If we have a pickerMenu, destroy it so it gets rebuilt when next required // Ensures we pick up title / icon etc changes this.resetTabPickerMenu(); }, // Actually set the disabled property on a tab. Handled by just disabling the button. setTabDisabled : function (tab, disabled) { var tabObject = this.getTabObject(tab); if (tabObject) tabObject.disabled = disabled; var tab = this.getTab(tab); if (tab) { // disable the tab so you can't access it. tab.setDisabled(disabled); // Also disable the pane in case it's showing. // Alternative approach would be to deselect the tab, if selected. The problem with // this is we may only have one tab in the tabSet. var pane = tab.pane; if (pane && this.disablePaneWithTab) { if (isc.isA.Canvas(pane)) pane.setDisabled(disabled); else pane.disabled = disabled; } } // rebuild the picker menu so the item in question shows up disabled this.resetTabPickerMenu(); }, //> @method tabSet.addTab() (A) // Add a tab // @param tab (Tab) new tab // @param [position] (number) position where tab should be added // @see TabSet.addTabs // @visibility external // @example tabsAddAndRemove //< addTab : function (tab, position) { return this.addTabs(tab, position); }, //> @method tabSet.addTabs() (A) // Add one or more tabs // @param tabs (Tab or Array of Tab) new tab or tabs // @param position (number) position where tab should be added (or array of positions) // @see TabSet.addTab // @visibility external //< addTabs : function (newTabs, position) { if (!isc.isAn.Array(newTabs)) newTabs = [newTabs]; var oldSelectedTab = this.getTabObject(this.getSelectedTabNumber()), forceSelection = (this.getSelectedTabNumber() == -1); if (position == null || position > this.tabs.length) position = this.tabs.length; for (var i = 0; i < newTabs.length; i++) { // use 'createPane' to turn the pane into a hidden, deparented canvas. newTabs[i].pane = this.createPane(newTabs[i].pane, newTabs[i]); // apply tabProperties (see comment in makeTabBar) var undef; for (var propName in this.tabProperties) { if (newTabs[i][propName] === undef) { newTabs[i][propName] = this.tabProperties[propName]; } } // Actually add the tab to the config this.tabs.addAt(newTabs[i], (position + i)) } this._tabBar.addTabs(newTabs, position); // If we have a pickerMenu, destroy it so it gets rebuilt when next required this.resetTabPickerMenu(); // call fixLayout on a delay // Necessary in case the new tabs introduced clipping of the tab-bar // Delay required as layout reflow is asynch this.delayCall("fixLayout"); if (forceSelection) { // If we didn't have a selected tab at the start of this method, ensure we select the // first of the new tabs this.selectTab(0); } else { // otherwise, update this.selectedTab (an index) in case tabs were added before the old // selected tab this.selectedTab = this.getTabNumber(oldSelectedTab); } //>EditMode this.addTabsEditModeExtras(newTabs); // @method tabSet.setTabPane() // Apply a new +link{tab.pane,pane} to an existing tab in this tabSet // @param tab (number | string | Tab) Tab to update (may be referenced by ID or index) // @param pane (Canvas) new Pane for the tab // @visibility external //< setTabPane : function (tab, pane) { return this.updateTab(tab,pane); }, //> @attr tabSet.destroyPanes (boolean : null : IR) // Whether +link{canvas.destroy,destroy()} should be called on +link{tab.pane} when it a tab is // removed via +link{removeTab()}}. //

// An application might set this to false in order to re-use panes in different tabs or in // different parts of the application. // // @visibility external //< //> @method tabSet.removeTab() (A) // Remove a tab. //

// The pane associated with the removed tab is automatically destroyed when you // call this method. To avoid this, call +link{updateTab()} with null as the new // pane immediately before removing the tab. // // @param tabs (Tab | ID | number | Array of Tab) list of tabs, tabIDs, or tab numbers // // @see TabSet.removeTabs // @visibility external // @example tabsAddAndRemove //< removeTab : function (tab, dontDestroy) { return this.removeTabs(tab, dontDestroy); }, //> @method tabSet.removeTabs() (A) // Remove one or more tabs. The pane(s) associated with the removed tab(s) is automatically // destroyed when you call this method. // // @param tabs (Tab | ID | number) list of tabs, tabIDs, or tab numbers // // @see TabSet.removeTab // @visibility external //< removeTabs : function (tabs, dontDestroy) { if (!isc.isAn.Array(tabs)) tabs = [tabs]; // get the actual tab button object from whatever was passed in. // We can pass this to tabBar.removeTabs() tabs = this.map("getTab", tabs); var removedSelected = false, selectedTab = this.getSelectedTab(), autoSelectTab = 0; for (var i = 0; i < tabs.length; i++) { // remove the tab from the config var tab = tabs[i], index = this.getTabNumber(tab), tabObject = this.tabs[index]; // if we remove the selected tab we want to just select another one near it if (tabObject == selectedTab) { removedSelected = true; // auto-select the next tab to the left if there is one, or the current // index otherwise if (index > 0) autoSelectTab = index - 1; else if (index < this.tabs.length + 1) autoSelectTab = index; // otherwise we may need to update our internal 'selectedTab' index value // to reflect the new position of the already selected tab } else { if (index < this.selectedTab) { this.selectedTab -= 1; } } this.tabs.removeAt(index); // remove the pane var pane = tabObject.pane; if (pane && pane.parentElement == this.paneContainer) { this.paneContainer.removeChild(pane); if (!dontDestroy && this.destroyPanes !== false) { pane.destroy(); } } // remove the tab button this._tabBar.removeTabs(tab); } // if the selected tab was removed, select the first tab if we have any if (removedSelected && this.tabs.length > 0) { // if the new selected-tab index is beyond the tab-count, select the last tab if (autoSelectTab >= this.tabs.length) autoSelectTab = this.tabs.length - 1; this.selectTab(autoSelectTab); } // If we have a pickerMenu, destroy it so it gets rebuilt when next required this.resetTabPickerMenu(); // call fixLayout on a delay // Necessary in case the removed tabs get rid of clipping of the tab-bar // Delay required as layout reflow is asynch this.delayCall("fixLayout", 0); //>EditMode this.removeTabsEditModeExtras(); // @method tabSet.canCloseTab() // Returns true if this tab is closeable. Determined by checking +link{tab.canClose} and // +link{tabSet.canCloseTabs}. // @param tab (int | ID | Tab) tab to check // @return (boolean) true if tab is closeable //< canCloseTab : function (tab) { tab = this.getTabObject(tab); if (tab && tab.canClose != null) return tab.canClose; return this.canCloseTabs; }, //> @method tabSet.setCanCloseTab() // Sets +link{tab.canClose} to the boolean parameter "canClose". If "canClose" is null, this // will have the effect of causing this tab to fall back to +link{tabSet.canCloseTabs} // @param tab (int | ID | Tab) tab to set // @param canClose (boolean) new value for the tab's canClose property, or null to clear it //< setCanCloseTab : function (tab, canClose) { tab = this.getTabObject(tab); var liveTab = this.getTab(tab); tab.canClose = canClose; var liveTabProperties = isc.addProperties({}, tab, {canClose: canClose}); if (liveTab) { liveTab.setProperties(this.getTabBar().getCloseIconProperties(liveTabProperties)); } }, _tabIconClick : function(tab) { var shouldClose = this.canCloseTab(tab); if (shouldClose) { this.closeClick(tab); return false; } else return this.tabIconClick(tab); }, //> @method tabSet.closeClick() // When +link{canCloseTabs} is set, method fired when the user clicks the "close" icon for a // tab. //

// Default implementation will remove the tab from the tabSet via +link{removeTab()}. // // @param tab (Tab) tab to close // @visibility external //< closeClick : function (tab) { // if "onCloseClick" exists, allow it to cancel the default behavior if (this.onCloseClick && (this.onCloseClick(tab) == false)) { return; } this.removeTab(tab); }, //> @method tabSet.tabIconClick() // Method fired when the user clicks the icon for a tab, as specified via +link{tab.icon}. //

// Default behavior will fire icon.click() if specified, with two parameters // tab (a pointer to the tab object and tabSet a pointer to the tabSet // instance. // @param tab (Tab) with click handler being fired // @visibility external //< tabIconClick : function (tab) { var icon = tab.icon; if (icon && icon.click) return this.fireCallback(icon.click, 'tab,tabSet', [tab,this]); }, //> @method tabSet.getTabObject() // Get the tab Object originally passed to +link{tabSet.tabs}, by index or ID. // If passed a tab Object, just returns it. // // @param tab (int | ID | Tab) // @return (Tab) the tab, or null if not found // @visibility external //< // NOTE: this returns the tab configuration object, not the button, since there may not be a // Button. getTabObject : function (tab) { // passed the tab button - determine it's index (use this below) tab = this.getTabNumber(tab); if (tab >= this.tabs.length) { var button = this.tabBar.getButton(tab); if (button && button.moreTab) return this.moreTab; } return this.tabs[tab]; }, //> @method tabSet.getTab() // Get the live Canvas representing a tab by index or ID. // If passed a tab Canvas, just returns it. //

// Note that live Tab instances are not available until +link{Canvas.draw,draw()}. //

// The returned Tab is considered an internal component of the TabSet. In order to maximize // forward compatibility, manipulate tabs through APIs such as a +link{setTabTitle()} instead. // Also note that a super-lightweight TabSet implementation may not use a separate Canvas per // Tab, and code that accesses an manipulates Tabs as Canvases won't be compatible with that // implementation. // // @param tab (int | ID | Canvas) // @return (Tab) the tab Canvas, or null if not found or TabSet not drawn yet // // @visibility external //< getTab : function (tab) { // already the tab button, return it if (isc.isAn.Canvas(tab)) return tab; if (!this.tabs) return null; // if we have a tab-config block, convert it to an index, since the tabBar doesn't see our // 'tabs' array if (this.tabs.contains(tab)) tab = this.tabs.indexOf(tab); // getButton on the tabBar handles the various possible types of the tab identifier passed in tab = this.getTabBar().getButton(tab); return tab; }, //> @method tabSet.getTabPane() // Returns the pane for a given tab. // // @param tab (object | number | ID | Tab) // @return (Canvas) the tab pane // @visibility external //< getTabPane : function (tab) { return this.getTabObject(tab).pane; }, //> @method tabSet.findTab() // Returns a the first tab in the list that matches the user-passed property name/value pair. // // @param propertyName (String) name of the property to look for // @param propertyValue (Any) value of the property //< findTabObject : function (propertyName, propertyValue) { return this.tabs.find(propertyName, propertyValue); }, //> @method tabSet.getTabNumber() // Get the index of a tab, from the tab or tabID. If passed a number, just returns it. // @param tab (number | ID | tab) // @return (number) the index of the tab, or -1 if not found // @visibility external //< // Note - we don't call this 'getTabIndex', even though it is an index, because of the conflict // with the 'tabIndex' of the widget as a whole getTabNumber : function (tab) { if (isc.isA.Number(tab)) return tab; if (!this.tabs) return null; var index = this.tabs.indexOf(tab); if (index != -1) return index; if (isc.isA.String(tab)) return this.tabs.findIndex("ID", tab); // At this point it must be a pointer to the tab button, so fall through to // tabBar.getButtonNumber() return this.getTabBar().getButtonNumber(this.getTab(tab)); }, //> @method tabSet.updateTab() // Set the pane for a tab. //

// Pass in the index of a tab (or a tab object), and a new pane. //

// NOTE: the old pane for the tab is not destroy()d // // @param tab (number | ID | Tab) tab to update // @param pane (Canvas | ID) new pane for the tab // @visibility external //< updateTab : function (tab, pane) { // if we were passed a tab init block, for a new tab, call addTabs instead if (isc.isAn.Object(tab) && !isc.isA.Canvas(tab) && this.tabs.indexOf(tab) == -1) { if (pane != null) tab.pane = pane; return this.addTabs(tab); } // get the index for the tab (whatever way the "tab" is passed) var tabIndex = this.getTabNumber(tab); // bad tab specification if (tabIndex == -1) { this.logWarn("no such tab: " + this.echo(tab)); return; } // get rid of the old pane var tabObject = this.getTabObject(tabIndex), oldPane = tabObject ? tabObject.pane : null; if (tabObject && tabObject.pane == pane) return; // no-op if (oldPane != null) { oldPane.hide(); oldPane.deparent(); } // NOTE: keep tabCanvas.pane and tabObject.pane in sync for EditMode where the Tab needs to // be able to respond to getProperty("pane") var tabCanvas = this.getTab(tab); // if the new pane is null, we're done if (pane == null) { if (tabCanvas != null) tabCanvas.pane = null; return tabObject.pane = null; } // add the new pane to init block (Using createPane to instantiate as a Canvas if necessary) // this makes sure the pane is hidden and not a child of anything except the paneContainer pane = tabObject.pane = this.createPane(pane, tabObject); // tabCanvas won't exist if we're not drawn yet if (tabCanvas != null) tabCanvas.pane = pane; // if the currently visible tab is being updated, ensure the new pane is // a member of the paneContainer with the appropriate visibility // (If undrawn it'll show up when the tabSet as a whole gets drawn) if (this.getSelectedTabNumber() == tabIndex) { if (!this.paneContainer.hasMember(pane)) this.paneContainer.addMember(pane); pane.setVisibility(isc.Canvas.INHERIT); } }, //> @method tabSet.fixLayout() (A) // lay out the children of the tabSet. // this method takes into account the position of the tabBar in the tabSet, // and lays out the tabBar and the paneContainer accordingly. //< fixLayout : function () { // abbreviations var tb = this._tabBar, // round corners: for layout only, manipulate the edgedCanvas instead of the // paneContainer pc = this._edgedCanvas || this.paneContainer ; // check for nulls, and exit if found. // this method requires that both the tabBar and the paneContainer be instantiated before // it is called. if (tb == null || pc == null) return; // make sure paneContainer is below tabBar if (pc.getZIndex(true) >= tb.getZIndex(true)) pc.moveBelow(tb); var tbOverlap = this._firstNonNull(this.tabBarOverlap, tb.borderThickness, tb.baseLineThickness); // lay out the tabBar and paneContainer, depending on where the tabBar is. var vertical; switch (this.tabBarPosition) { case isc.Canvas.TOP : vertical = false; pc.setRect(0, tb.getHeight() - tbOverlap, this.getWidth(), this.getHeight() - tb.getHeight() + tbOverlap ); break; case isc.Canvas.BOTTOM : vertical = false; tb.setTop(this.getHeight() - tb.getHeight()); pc.setRect(0, 0, this.getWidth(), this.getHeight() - tb.getHeight() + tbOverlap ); break; case isc.Canvas.LEFT : vertical = true; pc.setRect(tb.getWidth() - tbOverlap, 0, this.getWidth() - tb.getWidth() + tbOverlap, this.getHeight() ); break; case isc.Canvas.RIGHT : vertical = true; tb.setLeft(this.getWidth() - tb.getWidth()); pc.setRect(0, 0, this.getWidth() - tb.getWidth() + tbOverlap, this.getHeight() ); break; } // showControls will show (or hide) the control layout, and return true if showing. var showControls = this.showControls(); // If we're showing the control layout adjust our tab-bar size to take it into account if (showControls) { // Force clipping so we can scroll the tb as expected // Required even if we were already showing the scroller - we may have resized if (vertical) tb.setHeight(this.getViewportHeight() - this.tabBarControlLayout.getHeight()); else tb.setWidth(this.getViewportWidth() - this.tabBarControlLayout.getWidth()); this.tabBarControlLayout.bringToFront(); } else { tb.resizeTo(vertical ? null : "100%", vertical ? "100%" : null); } // If the tab bar is currently scrolled, but there is enough space to display all its // tabs, force a scroll back to zero/zero var totalTabs = this._getTabSizes(); if (vertical) { if (tb.getScrollTop() > 0 && totalTabs <= tb.getViewportHeight()) tb.scrollTo(null,0,"descrollTabs"); } else { if (tb.getScrollLeft() > 0 && totalTabs <= tb.getViewportWidth()) tb.scrollTo(0,null,"descrollTabs"); } }, //>@method tabSet.shouldShowControl() // Should a specific control as specified in +link{tabSet.tabBarControls} be displayed? // Default implementation will evaluate the +link{Canvas.showIf()} property for custom controls // included as canvases. Standard controls for scrolling the tabBar will be included if // the relevant +link{tabSet.showTabScroller} or +link{tabSet.showTabPicker} property is not // false, and there is not enough space in the tab-bar to display all the tabs. // @parameter (control) control from the +link{tabSet.tabBarControls} array // @return (boolean) true if the control shoudl be displayed // @group tabBarControls //< shouldShowControl : function (control) { // The standard controls only show if the tabs are clipped if ((control == "tabScroller") || (control == "tabPicker")) { if (this.showMoreTab) return false; if (!this.showTabScroller && control == "tabScroller") return false; if (!this.showTabPicker && control == "tabPicker") return false; // If the member width exceeds the available space for the tab-bar we need to show // scroller buttons var contentSize = this._getTabSizes(); if (contentSize == 0) return; var otherControlSize=0; for (var i = 0; i < this.tabBarControls.length; i++) { var otherControl = this.tabBarControls[i]; if (otherControl == "tabScroller" || otherControl == "tabPicker") continue; if (this.shouldShowControl(otherControl)) { if (!isc.isA.Canvas(otherControl)) otherControl = this.getControl(otherControl); otherControlSize += vertical ? otherControl.getVisibleHeight() : otherControl.getVisibleWidth(); } } var vertical = (this._tabBar.orientation == isc.Layout.VERTICAL), clipTabs = (contentSize > (vertical ? (this.getViewportHeight() - otherControlSize) : (this.getViewportWidth() - otherControlSize))); return clipTabs; } var control = this.getControl(control); if (isc.isA.Canvas(control)) { if (control.showIf) return control.fireCallback(control.showIf, [control]); else return true; } }, _getTabSizes : function () { if (!this._tabBar) return 0; var contentSize = this._tabBar.getMemberSizes(), vertical = this._tabBar.vertical; if (contentSize == null || contentSize.length == 0) return 0; contentSize = contentSize.sum(); var sizeAdjustment = (vertical ? (this._tabBar._topMargin || 0) + (this._tabBar._bottomMargin || 0) : (this._tabBar._leftMargin || 0) + (this._tabBar._rightMargin || 0)); return contentSize + sizeAdjustment; }, //>@method tabSet.getControl() // Given an entry in the +link{tabSet.tabBarControls} array, this method will return a pointer // to the actual widget to display in the control layout.
// If passed a canvas, it will be returned intact.
// Will also map the special strings "tabPicker" and "tabScroller" to // standard tab picker and scroller controls. // @param control (string or canvas) Control from +link{tabSet.tabBarControls} array. // @return (canvas) Control widget to include in the control layout for this tabset // @group tabBarControls //< getControl : function (control) { if (isc.isA.Canvas(control)) return control; var vertical = (this._tabBar.orientation == isc.Layout.VERTICAL); if (control == "tabScroller") { if (!this.scroller) { // Make the scroller a stretchImgButton with 2 "buttons" var sbsize = this.scrollerButtonSize; var scrollerSrc; if (this.symmetricScroller) { scrollerSrc = vertical ? this.scrollerVSrc : this.scrollerHSrc; } else { scrollerSrc = this.scrollerSrc; } var backName = this.symmetricScroller ? "back" : this.tabBarPosition + "_back", forwardName = this.symmetricScroller ? "forward" : this.tabBarPosition +"_forward"; this.scroller = isc.StretchImgButton.create({ // set noDoubleClicks - this means if the user clicks repeatedly on the // scroller we'll move forward 1 tab for each click rather than appearing // to swallow every other click noDoubleClicks:true, tabSet:this, vertical:vertical, width:vertical ? (this.tabBarThickness - this._tabBar.baseLineThickness) : (2*sbsize), height:vertical ? (2*sbsize) : (this.tabBarThickness - this._tabBar.baseLineThickness), items:[{name:backName, width:vertical ? null : sbsize, height:vertical ? sbsize : null}, {name:forwardName, width:vertical ? null : sbsize, height:vertical ? sbsize : null}], skinImgDir:this.skinImgDir, src:scrollerSrc, // Disable normal over/down styling as that would style both buttons at once showRollOver:false, showDown:false, backPartName:backName, forwardPartName:forwardName, mouseMove : function () { if (!this.tabSet.showScrollerRollOver) return; var currPart = this.inWhichPart(); var otherPart = currPart == this.backPartName ? this.forwardPartName : this.backPartName; this.setState(isc.StatefulCanvas.STATE_UP, otherPart); this.setState(isc.StatefulCanvas.STATE_OVER, currPart); }, mouseOut : function () { if (!this.tabSet.showScrollerRollOver) return; this.setState(isc.StatefulCanvas.STATE_UP, this.forwardPartName); this.setState(isc.StatefulCanvas.STATE_UP, this.backPartName); }, mouseDown : function () { this.clickPart = this.inWhichPart(); this.setState(isc.StatefulCanvas.STATE_DOWN, this.clickPart); }, mouseUp : function () { this.setState(isc.StatefulCanvas.STATE_UP, this.clickPart); }, mouseStillDown : function () { this.click(); }, click : function () { var back = this.clickPart == this.backPartName; // figure out which part they clicked in and remember it if (back) this.tabSet.scrollBack(); else this.tabSet.scrollForward(); return false; } }, this.scrollerProperties); } return this.scroller; } else if (control == "tabPicker") { var tabPickerSize = this.pickerButtonSize; if (!this.tabPicker) { var tabSrc; if (this.symmetricPickerButton) { tabSrc = vertical ? this.pickerButtonVSrc : this.pickerButtonHSrc; } else { tabSrc = this.pickerButtonSrc; } this.tabPicker = isc.ImgButton.create({ // use customState to append the tab bar position if necessary customState:this.symmetricPickerButton ? null : this.tabBarPosition, tabSet:this, showRollOver:false, skinImgDir:this.skinImgDir, src:tabSrc, height:(vertical ? tabPickerSize : (this.tabBarThickness - this._tabBar.baseLineThickness)), width:(vertical ? (this.tabBarThickness - this._tabBar.baseLineThickness) : tabPickerSize), click:function () { this.tabSet.showTabPickerMenu() } }, this.tabPickerProperties); } return this.tabPicker; } // If the control is a string, check for it being a widget's global ID if (isc.isA.String(control) && isc.isA.Canvas(window[control])) return window[control]; // At this point we don't recognize the controller - log a warning and bail this.logWarn("Unable to resolve specified tabBarControl:" + isc.Log.echo(control) + " to a valid control. Not displaying."); return null; }, // For autoTest: if we are showing tabBarControlLayout, access it directly by name namedLocatorChildren:["tabBarControlLayout"], // Method to actually show the controlLayout if required. // If no controls are to be displaye this method falls through to hideControls() // Returns true if any controls are displayed, false otherwise showControls : function () { var controlSet = this.tabBarControls, controlSize = 0, barPos = this.tabBarPosition, vertical = barPos == isc.Canvas.RIGHT || barPos == isc.Canvas.LEFT, visibleControlIndex = 0; for (var i = 0; i< controlSet.length; i++) { var control = controlSet[i]; if (!this.shouldShowControl(control)) continue; // Turn the control identifier into a pointer to a Canvas if necessary control = this.getControl(control); if (!control) continue; // At this point the control should be a pointer to a canvas - // Ensure the layout is showing, and that the control shows up in the right spot var controlLayout = this.tabBarControlLayout; // controls should all be housed in a layout if (!controlLayout) { // create the tabBarControls as an autoChild this.tabBarControlLayout = controlLayout = this.createAutoChild("tabBarControlLayout", {styleName:this.tabBarControlLayoutDefaults.styleName || this.tabBar.styleName, // if a control is resized while visible, ensure the tabSet // is notified so it can keep us right-aligned in the tab-bar childResized : function () { this.Super("childResized", arguments); this.creator._controlLayoutChildResized(); }, vertical:vertical // For autoTest APIs ,locatorParent:this }); } if (controlLayout.getMemberNumber(control) != visibleControlIndex) { controlLayout.addMember(control, visibleControlIndex); } visibleControlIndex ++; // Remember how much space the controls take up controlSize += vertical ? control.getVisibleHeight() : control.getVisibleWidth(); } if (controlLayout && controlLayout.members) { // remove any members of the controlLayout beyond the end of the current set of visible // controls var membersToRemove = []; for (var i = visibleControlIndex; i < controlLayout.members.length; i++) { membersToRemove.add(i); } controlLayout.removeMembers(membersToRemove); // Note: we're not destroying these members, just deparenting them } // If we are NOT showing any controls, hide the layout and return false if (visibleControlIndex == 0) { this.hideControls(); return false; } this.placeControlLayout(controlSize); // TabBar baseline: We're truncating the tabBar in order to draw the controlLayout after // it. // The controlLayout is as thick as the tabs, excluding the baseLine (this is appropriate - // we want control buttons to appear above the baseLine). However since the baseLine // is written into the tabBar rather than being a direct child of the tabSet, it will be // truncated along with the tabs, so the space under the control layout will be empty (the // baseLine will not extend underneath the controls). // Therefore if we are showing the controlLayout, create a new baseLine image to // sit below it so the baseLine extends beyond the (truncated) tabs in the tab-bar. // Note that we're not destroying the existing tab-bar baseline // (set up via tabBar.makeBaseline) - we're essentially duplicating it with some different // defaults and adding it to a different position in the DOM. if (!this._tabBarBaseLine) { var tb = this._tabBar; this._tabBarBaseLine = this._tabBar.createAutoChild("baseLine", { vertical:(barPos == isc.Canvas.LEFT || barPos == isc.Canvas.RIGHT), _generated:true, skinImgDir:tb.skinImgDir, src:tb.baseLineSrc, capSize:tb.baseLineCapSize, imageType:isc.Img.STRETCH, overflow:"hidden", // since the baseline can be a Canvas if it doesn't need to display images autoDraw:false }); this.addChild(this._tabBarBaseLine); } var tb = this._tabBar, tbThickness = (this.tabBarThickness - tb.baseLineThickness); // Position the tabBarBaseline under the controls. if (barPos == isc.Canvas.LEFT) { this._tabBarBaseLine.setRect(tbThickness, 0, tb.baseLineThickness, this.getHeight()); } else if (barPos == isc.Canvas.RIGHT) { this._tabBarBaseLine.setRect(this.getWidth() -this.tabBarThickness, 0, tb.baseLineThickness, this.getHeight()); } else if (barPos == isc.Canvas.TOP) { this._tabBarBaseLine.setRect(0, tbThickness, this.getWidth(), tb.baseLineThickness); } else if (barPos == isc.Canvas.BOTTOM) { this._tabBarBaseLine.setRect(0, this.getHeight() - this.tabBarThickness, this.getWidth(), tb.baseLineThickness); } if (!controlLayout.isVisible()) controlLayout.show(); // Always position the baseLine behind the tabBar so we only see the edge that protrudes // past the end of the tabs. this._tabBarBaseLine.moveBelow(tb); if (!this._tabBarBaseLine.isVisible()) this._tabBarBaseLine.show(); return true; }, placeControlLayout : function (controlSize) { // Now figure out the desired sizing / position of the controlLayout and put it in the right // place var left,top,width,height, // Ensure that we don't cover the baseline tb = this._tabBar, // TabBar.getBreadth() != tabBarThickness if an app explicitly sets the tabbar's height // differently in properties/defaults. Notably, this occurs in the Feature Explorer, // where the skin switcher is thicker than the tabBarThickness under Enterprise and // related skins. getBreadth() is the more accurate distance tbThickness = tb.getBreadth() - tb.baseLineThickness, barPos = this.tabBarPosition; if (barPos == isc.Canvas.LEFT) { left = 0; top = this.getHeight() - controlSize; width = tbThickness; height = controlSize; } else if (barPos == isc.Canvas.RIGHT) { left = this.getWidth() - tbThickness; top = this.getHeight() - controlSize; width = tbThickness; height = controlSize; } else if (barPos == isc.Canvas.BOTTOM) { width = controlSize; left = this.getWidth() - controlSize; top = this.getHeight() - tbThickness; height = tbThickness; // Last possibility is TOP } else { width = controlSize; left = this.getWidth() - controlSize; top = 0; height = tbThickness; } this.tabBarControlLayout.setRect(left, top, width, height); if (!this.children.contains(this.tabBarControlLayout)) this.addChild(this.tabBarControlLayout); }, _controlLayoutChildResized : function () { var layout = this.tabBarControlLayout; if (!layout || !layout.isDrawn() || !layout.isVisible()) return; var controlSize = 0; for (var i = 0; i < layout.members.length; i++) { if (layout.vertical) controlSize += layout.members[i].getVisibleHeight(); else controlSize += layout.members[i].getVisibleWidth(); } this.placeControlLayout(controlSize); var tb = this.tabBar; if (tb) { var vertical = (this.tabBarPosition == isc.Canvas.LEFT || this.tabBarPosition == isc.Canvas.RIGHT); if (vertical) { tb.setHeight(this.getViewportHeight() - this.tabBarControlLayout.getVisibleHeight()); } else { tb.setWidth(this.getViewportWidth() - this.tabBarControlLayout.getVisibleWidth()); } } }, // Hide the controlLayout and special tabBarBaseLine that displayes underneath it. hideControls : function () { if (this.tabBarControlLayout && this.tabBarControlLayout.isVisible()) this.tabBarControlLayout.hide(); if (this._tabBarBaseLine && this._tabBarBaseLine.isVisible()) this._tabBarBaseLine.hide(); }, //>@method tabSet.scrollForward() // If there is not enough space to display all the tabs in this tabSet, this method will // scroll the next tab (that first tab that is clipped at the end of the tab-bar) into view. // @visibility external //< scrollForward : function () { this._tabBar.scrollForward(this.animateTabScrolling); }, //>@method tabSet.scrollBack() // If there is not enough space to display all the tabs in this tabSet, this method will // scroll the previous tab (that first tab that is clipped at the beginning of the tab-bar) // into view. // @visibility external //< scrollBack : function () { this._tabBar.scrollBack(this.animateTabScrolling); }, // Called from click on the tabPicker control. Displays a menu with options to select // a tab from the tabSet showTabPickerMenu : function () { if (!this._pickerMenu) { var tabs = this.tabs, items = []; for (var i = 0; i < tabs.length; i++) { items[i] = {index:i, enabled:!this.tabs[i].disabled, checkIf:"this.tabSet.getSelectedTabNumber() == " + i, title:tabs[i].pickerTitle || tabs[i].title, // Note: We show the tab's icon in the menu, if there is one. // This will show instead of the check-mark which we normally use to // indicate selection icon:(this.canCloseTab(tabs[i]) ? null : tabs[i].icon), // Calling selectTab will automagically scroll the tab into view if // necessary click:"menu.tabSet.selectTab(item.index)"} } this._pickerMenu = this.getMenuConstructor().create({tabSet:this, data:items}) } // Show it under the button this._pickerMenu._showOffscreen(); this._pickerMenu.placeNear(this.tabPicker.getPageLeft(), this.tabPicker.getPageBottom()) this._pickerMenu.show(); }, // resetTabPickerMenu - helper to destroy the tab picker menu so it will be rebuilt when next shown // This ensures it picks up new details from the current set of tabs. resetTabPickerMenu : function () { if (this._pickerMenu) { this._pickerMenu.destroy(); delete this._pickerMenu; } }, // fix layout on a change of size layoutChildren : function (reason,b,c,d) { this.invokeSuper(isc.TabSet, "layoutChildren", reason,b,c,d); this.fixLayout(); }, _tabResized : function () { this.fixLayout(); }, // NOTE: this is internal because it only shows a new tab, it does not hide the previous tab. // The external API is selectTab(); _showTab : function (tab) { // Ensure we're working with a tab object rather than a tabButton instance // (We're keeping this.tabs up to date rather than working with the buttons directly) if (isc.isA.Canvas(tab)) tab = this.getTabObject(tab); if (tab == this.moreTab) { this.rebuildMorePane(); } this.paneContainer.scrollTo(0,0,"showTab"); if (tab && tab.pane) { if (!this.paneContainer.hasMember(tab.pane)) this.paneContainer.addMember(tab.pane); tab.pane.show(); } this.paneContainer.adjustOverflow(); }, //> @method tabSet._tabSelected(tab) (A) // Perform actions when a tab is selected. // This method is "bound" to the tabBar's buttonSelected method, so that is will fire // whenever a button on the tabBar is seleced. it performs the following functions: // - show the associated pane // - scroll to (0,0) // // @see this.tabBar.buttonSelected // @param tab (tab) tab that has been selected. //< _tabSelected : function (tab) { // fire handler (fire it first so it has an opportunity to alter the tab, eg add a pane on // the fly) var cancelSelection; var currentTabObject = this.getSelectedTab(), currentTabNum = this.getSelectedTabNumber(), tabNum = this._tabBar.getButtonNumber(tab), tabObject = this.getTabObject(tabNum), tabDeselected = (currentTabObject != null) && (tabObject != currentTabObject); // currentTabNum may already be set to the tab being selected, before this // method has run. // This can occur on initial selection when tab is added/drawn and // on selection due to other tab being removed. // Therefore store another flag "_selectedTabObj" to indicate we've actually run // our tabSelected handlers and shown the pane. // If this flag is set to the tab passed in, no-op. var isMoreTab = this.showMoreTab && this.tabBar.isShowingMoreTab() && tabObject == this.moreTab; if (!isMoreTab) { if (tabObject == this._selectedTabObj) return; this._selectedTabObj = tabObject; } if (tabDeselected && !this._suppressTabSelectedHandlers) { // fire deselected and selected handlers. // Note: If this is the first time the thing is drawn we'll have tabSelected being // fired on the initially selected tab but the "currentTabObject" will also point to that // tab -- in this case don't fire the deselected handler // Also note: if a tab is removed programmatically it is deselected. In this case // currentTabObject can be expected to be unset at this point. if (currentTabObject.tabDeselected != null) { if (this.fireCallback( currentTabObject.tabDeselected, "tabSet,tabNum, tabPane, ID, tab, newTab", [ this, // deselected tab details this.selectedTab, currentTabObject.pane, currentTabObject.ID, currentTabObject, // new tab tabObject ] ) == false) { cancelSelection = true; } } if (!cancelSelection && this.tabDeselected != null) { cancelSelection = (this.tabDeselected(this.selectedTab, currentTabObject.pane, currentTabObject.ID, currentTabObject, tabObject) == false) } if (!cancelSelection && currentTabObject.pane) { currentTabObject.pane.hide(); } } // force the tab to go back to selected state but don't fire any handlers / show or hide // tabs, etc. if (cancelSelection) { this._suppressTabSelectedHandlers = true; this.selectTab(this.getSelectedTab()); delete this._suppressTabSelectedHandlers; return; } // Remember the selected tabNum - used by this.getSelectedTabNumber() etc. this.selectedTab = tabNum; if (!this._suppressTabSelectedHandlers) { var handlerChangedTab; if (tabObject.tabSelected != null) { this.fireCallback( tabObject.tabSelected, "tabSet,tabNum,tabPane,ID,tab", [this,tabNum,tabObject.pane,tabObject.ID,tabObject] ); // If this tab is no longer marked as selected, tabSelected() may have shown a // different tab. In this case don't call _showTab! if (this.getSelectedTabNumber() != tabNum) { return; } } // fire the notification functions if (this.tabSelected) { this.tabSelected(tabNum, tabObject.pane, tabObject.ID, tabObject); // Once againk, if this tab is no longer marked as selected, tabSelected() // may have shown a different tab. In this case don't call _showTab! if (this.getSelectedTabNumber() != tabNum) { return; } } } this._showTab(tab); // ensure the tab button is scrolled into view var tb = this._tabBar; // leave the second param as null - tab bar will automatically scroll to appropriate // position var tabSet = this; tb.scrollTabIntoView(tabNum, null, this.animateTabScrolling, function() { // If the tab title editor form is showing for this item we may have // delayed actually drawing the form pending the animation completing // in this case show when the animation completes. if (tabSet.titleEditorForm != null && tabSet.titleEditorForm._hiddenPendingAnimation) { tabSet.placeTitleEditor(tabSet.titleEditorForm.targetTab); } if (isc.isA.Function(tabSet.tabScrolledIntoView)) tabSet.tabScrolledIntoView(); }); }, //> @method tab.tabSelected() // Optional handler to fire when a tab is selected. As with +link{TabSet.tabSelected()} this // method only fires when the tabset is drawn. // // @param tabSet (TabSet) the tabSet containing the tab. // @param tabNum (integer) the index of the newly selected tab // @param tabPane (Canvas) the newly selected tab's pane if set // @param ID (String) the ID of the newly selected tab // @param tab (tab) pointer to the selected tab object // // @see tab.tabDeselected // @visibility external //< //> @method tab.tabDeselected() // Optional handler to fire when a tab is deselected. Returning false will cancel the // new selection, leaving this tab selected. As with +link{TabSet.tabSelected()} this // method only fires when the tabset is drawn. // // @param tabSet (TabSet) the tabSet containing the tab. // @param tabNum (integer) the index of the deselected tab // @param tabPane (Canvas) the deselected tab's pane if set // @param ID (String) the ID of the deselected tab // @param tab (tab) pointer to the tab being deselected // @param newTab (tab) pointer to the new tab being selected // // @return (boolean) return false to cancel the tab selection // // @see tab.tabSelected // @visibility external //< //> @method tabSet.getSelectedTab() ([A]) // Returns the currently selected tab object. This is the object literal used to configure the // tab, rather than the tab button widget. // @return (Tab) the currently selected Tab object // @visibility external //< getSelectedTab : function () { if (this.selectedTab >= this.tabs.length) return this.moreTab; return this.tabs[this.selectedTab]; }, //> @method tabSet.getSelectedTabNumber() ([A]) // Returns the index of the currently selected tab object. // @return (number) the index of the currently selected tab object // @visibility external //< getSelectedTabNumber : function () { if (!isc.isA.Number(this.selectedTab)) this.selectedTab = this.getTabNumber(this.selectedTab); // If the specified selectedTabNum doesn't correspond to a tab don't return it. if (!this.tabs || !this.tabs[this.selectedTab]) return -1; return this.selectedTab; }, //> @method tabSet.selectTab() ([]) // Select a tab // @param tab (number | ID | Tab) tab to select // @visibility external // @example tabsOrientation //< selectTab : function (tab) { var tabIndex = this.getTabNumber(tab); if (tabIndex != -1) { // calling 'selectTab()' on the tab bar will actually select the button. // this handles firing our tabSelected() notification functions if (this._tabBar) { this._tabBar.selectTab(tabIndex); } // TabBar (subclass of Toolbar) initializes its members (buttons) lazily on draw() // We won't get any _tabSelected notifications until after this has happened. // Therefore if the tab bar hasn't initialized yet, simply record this.selected tab // so methods like this.getSelectedTabNum() / getselectedTabObject() work // // Note that we explicitly call tabBar.selectTab(this.selectedTab) on draw() to ensure // the tab-bar stays in synch if (this._tabBar == null || !this._tabBar._buttonsInitialized) { this.selectedTab = tabIndex; } } }, //> @method tabSet.getTabBar() // Returns handle to the TabBar used by this tabset // @return (TabBar) the tab bar //< getTabBar : function () { return this._tabBar; }, _editTabTitle : function (tab) { tab = this.getTab(tab); var canEdit; if (this.canEditTabTitles) { if (tab.canEditTitle !== false) { canEdit = true; } } else { if (tab.canEditTitle === true) { canEdit = true; } } if (canEdit) this.editTabTitle(tab); return canEdit; }, //> @method tabSet.editTabTitle() // Places an editor in the title of the parameter tab and allows the user to edit the title. // Note that this programmatic method will allow editing of the specified tab's // title, regardless of the settings of +link{canEditTabTitles} or +link{Tab.canEditTitle}. // @param tab (Tab | String | integer) The tab whose title should be edited (may be // specified by ID or index) // @see TabSet.canEditTabTitles // @see Tab.canEditTitle // @visibility external //< editTabTitle : function (tab) { tab = this.getTab(tab); if (!isc.isA.DynamicForm(this.titleEditorForm)) { var titleEditorConfig = isc.addProperties( {}, this.titleEditorDefaults, this.titleEditorProperties, { handleKeyPress : function (event,eventInfo) { var rv = this.Super("handleKeyPress", arguments); var keyName = event.keyName; if (keyName == "Escape") { this.form.targetTabSet.cancelTabTitleEditing(); } else if (keyName == "Enter") { this.form.targetTabSet.saveTabTitle(); } return rv; } } ); titleEditorConfig.name = "title"; this.titleEditorForm = isc.DynamicForm.create({ autoDraw: false, margin: 0, padding: 0, cellPadding: 0, fields: [ titleEditorConfig ] }); // Make the item directly available as a read-only form item (as documented) this.titleEditor = this.titleEditorForm.getItem("title"); } var editor = this.titleEditorForm; editor.setProperties({targetTabSet: this, targetTab: tab}); var item = editor.getItem("title"); var title = tab.title; item.setValue(title); this.placeTitleEditor(tab); if (this.tabBar._runningAnimations == 0) { this.showTitleEditor(); } else { editor._hiddenPendingAnimation = true; } }, //> @method tabSet.cancelTabTitleEditing() // If the user is currently editing a tab title (see +link{tabSet.canEditTabTitles}), dismiss // the editor and discard the edit value entered by the user. // @visibility external //< // We'll fire this from standard end edit event (Escape keypress) too cancelTabTitleEditing : function () { if (this.titleEditorForm != null) { this.clearTitleEditorForm(); } }, //> @method tabSet.saveTabTitle() // If the user is currently editing a tab title (see +link{tabSet.canEditTabTitles}), save // the edited tab title and hide the editor. // @visibility external //< // Also fired internally from standard end edit event (click outside / enter keypress); saveTabTitle : function () { if (this.titleEditorForm != null && this.titleEditorForm.isVisible() && this.titleEditorForm.isDrawn()) { var cancelEdit = false, form = this.titleEditorForm, tab = form.targetTab, newTitle = form.getValue("title") ; if (newTitle != tab.title && (this.titleChanged != null)) { if (this.fireCallback( this.titleChanged, "newTitle, oldTitle, tab", [newTitle, tab.title,tab] ) == false) { cancelEdit = true; } } if (!cancelEdit) this.setTabTitle(form.targetTab, newTitle); } // Dismiss the editor even if the titleChanged callback returned false, cancelling the // edit. // If we leave the editor up we're likely to get into tricky situations where // for example the developer can change tab with the editor still showing on another tab, this.clearTitleEditorForm(); }, clearTitleEditorForm : function () { if (this.titleEditorForm == null) return; this.titleEditorForm.clear(); if (this.titleEditorForm._titleEditClickEvent != null) { isc.Page.clearEvent(this._titleEditClickEvent); delete this._titleEditClickEvent; } }, placeTitleEditor : function(tab) { var editor = this.titleEditorForm; if (!editor) return; // the editor will be a peer of the TabSet (shares the same parentElement) // The tab is a child of the TabBar // so left top should be tab left/top within the tabBar + tabBar left + tabBar border/margin var left = this.tabBar.getLeft() + this.tabBar.getLeftMargin() - this.tabBar.getScrollLeft() + this.tabBar.getLeftBorderSize() + tab.getLeft() + tab.capSize, width = tab.getVisibleWidth() - tab.capSize * 2; if (this.titleEditorLeftOffset) { left += this.titleEditorLeftOffset; width -= this.titleEditorLeftOffset; } if (this.titleEditorRightOffset) { width -= this.titleEditorRightOffset; } var item = editor.getItem("title"); item.setWidth(width); // Editor form will be a peer of the tabSet - needs to float over the content of // the tab (nested inside the tabBar). var top = this.getTop() + this.tabBar.getTop() + this.tabBar.getTopMargin() - this.tabBar.getScrollTop() + this.tabBar.getTopBorderSize() + tab.getTop(); if (this.titleEditorTopOffset) { top += this.titleEditorTopOffset; } editor.setTop(top); editor.setLeft(left); if (editor._hiddenPendingAnimation) { this.showTitleEditor(); editor._hiddenPendingAnimation = false; } }, showTitleEditor: function() { var editor = this.titleEditorForm, item = editor.getItem("title"); // make the editor a peer so it moves with us. // This will also handle showing / hiding / clearing / drawing with us - however // we'll also need to clear up the click-outside event on clear/hide so we'll // explicitly cancel title editing when we hide / clear instead of relying on this. if (editor.masterElement != this) { editor._moveWithMaster = true; editor._resizeWithMaster = false; editor._showWithMaster = false; this.addPeer(editor); } else { editor.draw(); } item.focusInItem(); item.delayCall("selectValue", [], 100); // Save edits on click outside title editor if (this._titleEditClickEvent == null) { var tabSet = this; var mouseDownHandler = function () { if (!tabSet.destroyed) { tabSet._clickOutsideDuringTitleEdit(); } } this._titleEditClickEvent = isc.Page.setEvent("mouseDown", mouseDownHandler); } }, _clickOutsideDuringTitleEdit : function () { if (isc.EH.getTarget() == this.titleEditorForm) return; this.saveTabTitle(); }, // On clear / hide / parent visibility change cancel title editing // Clear is called recursively so this'll pick up parents clearing too clear : function (a,b,c,d) { if (this.titleEditorForm != null && this.titleEditorForm.isDrawn()) { this.cancelTitleEditing(); } this.invokeSuper("TabSet", "clear", a,b,c,d); }, setVisibility : function (newVisibility, a,b,c,d) { this.invokeSuper("TabSet", "setVisibility", newVisibility, a,b,c,d); if (!this.isVisible() && this.titleEditorForm != null && this.titleEditorForm.isDrawn()) { this.cancelTitleEditing(); } }, parentVisibilityChanged : function (newVisibility, a,b,c,d) { this.invokeSuper("TabSet", "parentVisibilityChanged", newVisibility, a,b,c,d); if (!this.isVisible() && this.titleEditorForm != null && this.titleEditorForm.isDrawn()) { this.cancelTitleEditing(); } } }); isc.TabSet.registerStringMethods({ //> @method tabSet.tabSelected() // Notification fired when a tab is selected. Note that this will only fire if // this tabSet is drawn. If a tab is selected before TabSet.draw() // is called, the tabSelected() notification will fire on // draw() // @param tabNum (number) number of the tab // @param tabPane (Canvas) pane for this tab // @param ID (id) id of the tab // @param tab (tab) the tab object (not tab button instance) // @visibility external //< tabSelected:"tabNum,tabPane,ID,tab", //> @method tabSet.tabDeselected() // Notification fired when a tab is deselected. // @param tabNum (number) number of the deselected tab // @param tabPane (Canvas) pane for this deselected tab // @param ID (id) id of the deselected tab // @param tab (tab) the deselected tab object (not tab button instance) // @param newTab (tab) the tab object being selected // @return (boolean) return false to cancel the tab deselection // @visibility external //< tabDeselected:"tabNum,tabPane,ID,tab,newTab", // getPaneContainerEdges - documented by default implementation getPaneContainerEdges:"", //> @method tabSet.onCloseClick() // When +link{canCloseTabs} is set, this notification method fired when the user clicks // the "close" icon for a tab. // Return false to cancel default behavior of removing the tab from the TabSet // @param tab (Tab) the tab to be removed // @return (boolean) return false to suppress removal of the tab // @visibility sgwt //< onCloseClick : "tab", //> @method tabSet.titleChanged() // This notification method fired when the user changes the title of a tab in this TabSet. // This can happen either through user interaction with the UI if // +link{canEditTabTitles,canEditTabTitles} is set, or programmatically if application // code calls +link{editTabTitle,editTabTitle}.

// Return false from this method to cancel the change. // @param newTitle (String) the new title // @param oldTitle (String) the old title // @param tab (Tab) the tab whose title has changed // @return (boolean) return false to suppress the title change // @visibility external //< titleChanged : "newTitle,oldTitle,tab", //> @method tabSet.showTabContextMenu() // Notification fired when the user right-clicks on a tab. // Event may be cancelled by returning false // @param tabSet (TabSet) This tabset // @param tab (Tab) the tab object that recieved the context click event // @return (boolean) return false to cancel default right-click behavior // @visibility external //< showTabContextMenu : "tabSet,tab" }); isc.defineClass("PaneContainer", "VLayout").addMethods({ // override handleKeyPress to allow for navigation between tabs when focus'd on the // pane container or its children (via bubbled handleKeyPress events) // ctrl+tab - move one pane forward (or back to the first pane) // ctrl+shift+tab - move one pane back // (This is the Windows behavior - see Windows control panel) handleKeyPress : function (event, eventInfo) { if (event.keyName == "Tab" && event.ctrlKey) { var tabSet = this.parentElement, lastTabIndex = tabSet.tabs.length-1, currentSelection = tabSet.getSelectedTabNumber(); if (event.shiftKey) { if (currentSelection > 0) currentSelection -=1; else currentSelection = lastTabIndex; } else { if (currentSelection < lastTabIndex) currentSelection +=1; else currentSelection = 0; } tabSet.selectTab(currentSelection); tabSet.getTabBar().getButton(currentSelection).focus(); return false; } if (this.convertToMethod("keyPress")) return this.keyPress(event, eventInfo) } }); // Register "tabs" as duplicate properties // This means if a tabset subclass is created with tabs explicitly set to a bunch of config // objects they'll be duplicated on instances rather than copied across directly. // Ditto if Defaults is used in the autoChild subsystem. // Also register the 'pane' sub property so if tab.pane is set it will be duplicated // rather than shared across tabs isc.TabSet.registerDupProperties("tabs", ["pane"]);





© 2015 - 2024 Weber Informatics LLC | Privacy Policy