diff options
Diffstat (limited to 'lib/dijit/layout')
-rw-r--r-- | lib/dijit/layout/AccordionContainer.js | 686 | ||||
-rw-r--r-- | lib/dijit/layout/AccordionPane.js | 28 | ||||
-rw-r--r-- | lib/dijit/layout/BorderContainer.js | 830 | ||||
-rw-r--r-- | lib/dijit/layout/ContentPane.js | 860 | ||||
-rw-r--r-- | lib/dijit/layout/LayoutContainer.js | 96 | ||||
-rw-r--r-- | lib/dijit/layout/LinkPane.js | 51 | ||||
-rw-r--r-- | lib/dijit/layout/ScrollingTabController.js | 662 | ||||
-rw-r--r-- | lib/dijit/layout/SplitContainer.js | 913 | ||||
-rw-r--r-- | lib/dijit/layout/StackContainer.js | 460 | ||||
-rw-r--r-- | lib/dijit/layout/StackController.js | 484 | ||||
-rw-r--r-- | lib/dijit/layout/TabContainer.js | 77 | ||||
-rw-r--r-- | lib/dijit/layout/TabController.js | 216 | ||||
-rw-r--r-- | lib/dijit/layout/_ContentPaneResizeMixin.js | 260 | ||||
-rw-r--r-- | lib/dijit/layout/_LayoutWidget.js | 411 | ||||
-rw-r--r-- | lib/dijit/layout/_TabContainerBase.js | 193 |
15 files changed, 4322 insertions, 1905 deletions
diff --git a/lib/dijit/layout/AccordionContainer.js b/lib/dijit/layout/AccordionContainer.js index 5bad370f5..6177583ba 100644 --- a/lib/dijit/layout/AccordionContainer.js +++ b/lib/dijit/layout/AccordionContainer.js @@ -1,220 +1,498 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.AccordionContainer"]){ -dojo._hasResource["dijit.layout.AccordionContainer"]=true; +if(!dojo._hasResource["dijit.layout.AccordionContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.AccordionContainer"] = true; dojo.provide("dijit.layout.AccordionContainer"); -dojo.require("dojo.fx"); dojo.require("dijit._Container"); dojo.require("dijit._Templated"); dojo.require("dijit._CssStateMixin"); dojo.require("dijit.layout.StackContainer"); dojo.require("dijit.layout.ContentPane"); dojo.require("dijit.layout.AccordionPane"); -dojo.declare("dijit.layout.AccordionContainer",dijit.layout.StackContainer,{duration:dijit.defaultDuration,buttonWidget:"dijit.layout._AccordionButton",_verticalSpace:0,baseClass:"dijitAccordionContainer",postCreate:function(){ -this.domNode.style.overflow="hidden"; -this.inherited(arguments); -dijit.setWaiRole(this.domNode,"tablist"); -},startup:function(){ -if(this._started){ -return; -} -this.inherited(arguments); -if(this.selectedChildWidget){ -var _1=this.selectedChildWidget.containerNode.style; -_1.display=""; -_1.overflow="auto"; -this.selectedChildWidget._wrapperWidget.set("selected",true); -} -},_getTargetHeight:function(_2){ -var cs=dojo.getComputedStyle(_2); -return Math.max(this._verticalSpace-dojo._getPadBorderExtents(_2,cs).h-dojo._getMarginExtents(_2,cs).h,0); -},layout:function(){ -var _3=this.selectedChildWidget; -if(!_3){ -return; -} -var _4=_3._wrapperWidget.domNode,_5=dojo._getMarginExtents(_4),_6=dojo._getPadBorderExtents(_4),_7=this._contentBox; -var _8=0; -dojo.forEach(this.getChildren(),function(_9){ -if(_9!=_3){ -_8+=dojo.marginBox(_9._wrapperWidget.domNode).h; -} -}); -this._verticalSpace=_7.h-_8-_5.h-_6.h-_3._buttonWidget.getTitleHeight(); -this._containerContentBox={h:this._verticalSpace,w:this._contentBox.w-_5.w-_6.w}; -if(_3){ -_3.resize(this._containerContentBox); -} -},_setupChild:function(_a){ -_a._wrapperWidget=new dijit.layout._AccordionInnerContainer({contentWidget:_a,buttonWidget:this.buttonWidget,id:_a.id+"_wrapper",dir:_a.dir,lang:_a.lang,parent:this}); -this.inherited(arguments); -},addChild:function(_b,_c){ -if(this._started){ -dojo.place(_b.domNode,this.containerNode,_c); -if(!_b._started){ -_b.startup(); -} -this._setupChild(_b); -dojo.publish(this.id+"-addChild",[_b,_c]); -this.layout(); -if(!this.selectedChildWidget){ -this.selectChild(_b); -} -}else{ -this.inherited(arguments); -} -},removeChild:function(_d){ -_d._wrapperWidget.destroy(); -delete _d._wrapperWidget; -dojo.removeClass(_d.domNode,"dijitHidden"); -this.inherited(arguments); -},getChildren:function(){ -return dojo.map(this.inherited(arguments),function(_e){ -return _e.declaredClass=="dijit.layout._AccordionInnerContainer"?_e.contentWidget:_e; -},this); -},destroy:function(){ -dojo.forEach(this.getChildren(),function(_f){ -_f._wrapperWidget.destroy(); + + +//dojo.require("dijit.layout.AccordionPane "); // for back compat, remove for 2.0 + +// Design notes: +// +// An AccordionContainer is a StackContainer, but each child (typically ContentPane) +// is wrapped in a _AccordionInnerContainer. This is hidden from the caller. +// +// The resulting markup will look like: +// +// <div class=dijitAccordionContainer> +// <div class=dijitAccordionInnerContainer> (one pane) +// <div class=dijitAccordionTitle> (title bar) ... </div> +// <div class=dijtAccordionChildWrapper> (content pane) </div> +// </div> +// </div> +// +// Normally the dijtAccordionChildWrapper is hidden for all but one child (the shown +// child), so the space for the content pane is all the title bars + the one dijtAccordionChildWrapper, +// which on claro has a 1px border plus a 2px bottom margin. +// +// During animation there are two dijtAccordionChildWrapper's shown, so we need +// to compensate for that. + +dojo.declare( + "dijit.layout.AccordionContainer", + dijit.layout.StackContainer, + { + // summary: + // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time, + // and switching between panes is visualized by sliding the other panes up/down. + // example: + // | <div dojoType="dijit.layout.AccordionContainer"> + // | <div dojoType="dijit.layout.ContentPane" title="pane 1"> + // | </div> + // | <div dojoType="dijit.layout.ContentPane" title="pane 2"> + // | <p>This is some text</p> + // | </div> + // | </div> + + // duration: Integer + // Amount of time (in ms) it takes to slide panes + duration: dijit.defaultDuration, + + // buttonWidget: [const] String + // The name of the widget used to display the title of each pane + buttonWidget: "dijit.layout._AccordionButton", + +/*===== + // _verticalSpace: Number + // Pixels of space available for the open pane + // (my content box size minus the cumulative size of all the title bars) + _verticalSpace: 0, +=====*/ + baseClass: "dijitAccordionContainer", + + buildRendering: function(){ + this.inherited(arguments); + this.domNode.style.overflow = "hidden"; // TODO: put this in dijit.css + dijit.setWaiRole(this.domNode, "tablist"); // TODO: put this in template + }, + + startup: function(){ + if(this._started){ return; } + this.inherited(arguments); + if(this.selectedChildWidget){ + var style = this.selectedChildWidget.containerNode.style; + style.display = ""; + style.overflow = "auto"; + this.selectedChildWidget._wrapperWidget.set("selected", true); + } + }, + + layout: function(){ + // Implement _LayoutWidget.layout() virtual method. + // Set the height of the open pane based on what room remains. + + var openPane = this.selectedChildWidget; + + if(!openPane){ return;} + + // space taken up by title, plus wrapper div (with border/margin) for open pane + var wrapperDomNode = openPane._wrapperWidget.domNode, + wrapperDomNodeMargin = dojo._getMarginExtents(wrapperDomNode), + wrapperDomNodePadBorder = dojo._getPadBorderExtents(wrapperDomNode), + wrapperContainerNode = openPane._wrapperWidget.containerNode, + wrapperContainerNodeMargin = dojo._getMarginExtents(wrapperContainerNode), + wrapperContainerNodePadBorder = dojo._getPadBorderExtents(wrapperContainerNode), + mySize = this._contentBox; + + // get cumulative height of all the unselected title bars + var totalCollapsedHeight = 0; + dojo.forEach(this.getChildren(), function(child){ + if(child != openPane){ + totalCollapsedHeight += dojo._getMarginSize(child._wrapperWidget.domNode).h; + } + }); + this._verticalSpace = mySize.h - totalCollapsedHeight - wrapperDomNodeMargin.h + - wrapperDomNodePadBorder.h - wrapperContainerNodeMargin.h - wrapperContainerNodePadBorder.h + - openPane._buttonWidget.getTitleHeight(); + + // Memo size to make displayed child + this._containerContentBox = { + h: this._verticalSpace, + w: this._contentBox.w - wrapperDomNodeMargin.w - wrapperDomNodePadBorder.w + - wrapperContainerNodeMargin.w - wrapperContainerNodePadBorder.w + }; + + if(openPane){ + openPane.resize(this._containerContentBox); + } + }, + + _setupChild: function(child){ + // Overrides _LayoutWidget._setupChild(). + // Put wrapper widget around the child widget, showing title + + child._wrapperWidget = new dijit.layout._AccordionInnerContainer({ + contentWidget: child, + buttonWidget: this.buttonWidget, + id: child.id + "_wrapper", + dir: child.dir, + lang: child.lang, + parent: this + }); + + this.inherited(arguments); + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + if(this._started){ + // Adding a child to a started Accordion is complicated because children have + // wrapper widgets. Default code path (calling this.inherited()) would add + // the new child inside another child's wrapper. + + // First add in child as a direct child of this AccordionContainer + dojo.place(child.domNode, this.containerNode, insertIndex); + + if(!child._started){ + child.startup(); + } + + // Then stick the wrapper widget around the child widget + this._setupChild(child); + + // Code below copied from StackContainer + dojo.publish(this.id+"-addChild", [child, insertIndex]); + this.layout(); + if(!this.selectedChildWidget){ + this.selectChild(child); + } + }else{ + // We haven't been started yet so just add in the child widget directly, + // and the wrapper will be created on startup() + this.inherited(arguments); + } + }, + + removeChild: function(child){ + // Overrides _LayoutWidget.removeChild(). + + // Destroy wrapper widget first, before StackContainer.getChildren() call. + // Replace wrapper widget with true child widget (ContentPane etc.). + // This step only happens if the AccordionContainer has been started; otherwise there's no wrapper. + if(child._wrapperWidget){ + dojo.place(child.domNode, child._wrapperWidget.domNode, "after"); + child._wrapperWidget.destroy(); + delete child._wrapperWidget; + } + + dojo.removeClass(child.domNode, "dijitHidden"); + + this.inherited(arguments); + }, + + getChildren: function(){ + // Overrides _Container.getChildren() to return content panes rather than internal AccordionInnerContainer panes + return dojo.map(this.inherited(arguments), function(child){ + return child.declaredClass == "dijit.layout._AccordionInnerContainer" ? child.contentWidget : child; + }, this); + }, + + destroy: function(){ + if(this._animation){ + this._animation.stop(); + } + dojo.forEach(this.getChildren(), function(child){ + // If AccordionContainer has been started, then each child has a wrapper widget which + // also needs to be destroyed. + if(child._wrapperWidget){ + child._wrapperWidget.destroy(); + }else{ + child.destroyRecursive(); + } + }); + this.inherited(arguments); + }, + + _showChild: function(child){ + // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode + child._wrapperWidget.containerNode.style.display="block"; + return this.inherited(arguments); + }, + + _hideChild: function(child){ + // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode + child._wrapperWidget.containerNode.style.display="none"; + this.inherited(arguments); + }, + + _transition: function(/*dijit._Widget?*/ newWidget, /*dijit._Widget?*/ oldWidget, /*Boolean*/ animate){ + // Overrides StackContainer._transition() to provide sliding of title bars etc. + + if(dojo.isIE < 8){ + // workaround animation bugs by not animating; not worth supporting animation for IE6 & 7 + animate = false; + } + + if(this._animation){ + // there's an in-progress animation. speedily end it so we can do the newly requested one + this._animation.stop(true); + delete this._animation; + } + + var self = this; + + if(newWidget){ + newWidget._wrapperWidget.set("selected", true); + + var d = this._showChild(newWidget); // prepare widget to be slid in + + // Size the new widget, in case this is the first time it's being shown, + // or I have been resized since the last time it was shown. + // Note that page must be visible for resizing to work. + if(this.doLayout && newWidget.resize){ + newWidget.resize(this._containerContentBox); + } + } + + if(oldWidget){ + oldWidget._wrapperWidget.set("selected", false); + if(!animate){ + this._hideChild(oldWidget); + } + } + + if(animate){ + var newContents = newWidget._wrapperWidget.containerNode, + oldContents = oldWidget._wrapperWidget.containerNode; + + // During the animation we will be showing two dijitAccordionChildWrapper nodes at once, + // which on claro takes up 4px extra space (compared to stable AccordionContainer). + // Have to compensate for that by immediately shrinking the pane being closed. + var wrapperContainerNode = newWidget._wrapperWidget.containerNode, + wrapperContainerNodeMargin = dojo._getMarginExtents(wrapperContainerNode), + wrapperContainerNodePadBorder = dojo._getPadBorderExtents(wrapperContainerNode), + animationHeightOverhead = wrapperContainerNodeMargin.h + wrapperContainerNodePadBorder.h; + + oldContents.style.height = (self._verticalSpace - animationHeightOverhead) + "px"; + + this._animation = new dojo.Animation({ + node: newContents, + duration: this.duration, + curve: [1, this._verticalSpace - animationHeightOverhead - 1], + onAnimate: function(value){ + value = Math.floor(value); // avoid fractional values + newContents.style.height = value + "px"; + oldContents.style.height = (self._verticalSpace - animationHeightOverhead - value) + "px"; + }, + onEnd: function(){ + delete self._animation; + newContents.style.height = "auto"; + oldWidget._wrapperWidget.containerNode.style.display = "none"; + oldContents.style.height = "auto"; + self._hideChild(oldWidget); + } + }); + this._animation.onStop = this._animation.onEnd; + this._animation.play(); + } + + return d; // If child has an href, promise that fires when the widget has finished loading + }, + + // note: we are treating the container as controller here + _onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){ + // summary: + // Handle keypress events + // description: + // This is called from a handler on AccordionContainer.domNode + // (setup in StackContainer), and is also called directly from + // the click handler for accordion labels + if(this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){ + return; + } + var k = dojo.keys, + c = e.charOrCode; + if((fromTitle && (c == k.LEFT_ARROW || c == k.UP_ARROW)) || + (e.ctrlKey && c == k.PAGE_UP)){ + this._adjacent(false)._buttonWidget._onTitleClick(); + dojo.stopEvent(e); + }else if((fromTitle && (c == k.RIGHT_ARROW || c == k.DOWN_ARROW)) || + (e.ctrlKey && (c == k.PAGE_DOWN || c == k.TAB))){ + this._adjacent(true)._buttonWidget._onTitleClick(); + dojo.stopEvent(e); + } + } + } +); + +dojo.declare("dijit.layout._AccordionInnerContainer", + [dijit._Widget, dijit._CssStateMixin], { + // summary: + // Internal widget placed as direct child of AccordionContainer.containerNode. + // When other widgets are added as children to an AccordionContainer they are wrapped in + // this widget. + +/*===== + // buttonWidget: String + // Name of class to use to instantiate title + // (Wish we didn't have a separate widget for just the title but maintaining it + // for backwards compatibility, is it worth it?) + buttonWidget: null, +=====*/ + +/*===== + // contentWidget: dijit._Widget + // Pointer to the real child widget + contentWidget: null, +=====*/ + + baseClass: "dijitAccordionInnerContainer", + + // tell nested layout widget that we will take care of sizing + isContainer: true, + isLayoutContainer: true, + + buildRendering: function(){ + // Builds a template like: + // <div class=dijitAccordionInnerContainer> + // Button + // <div class=dijitAccordionChildWrapper> + // ContentPane + // </div> + // </div> + + // Create wrapper div, placed where the child is now + this.domNode = dojo.place("<div class='" + this.baseClass + "'>", this.contentWidget.domNode, "after"); + + // wrapper div's first child is the button widget (ie, the title bar) + var child = this.contentWidget, + cls = dojo.getObject(this.buttonWidget); + this.button = child._buttonWidget = (new cls({ + contentWidget: child, + label: child.title, + title: child.tooltip, + dir: child.dir, + lang: child.lang, + iconClass: child.iconClass, + id: child.id + "_button", + parent: this.parent + })).placeAt(this.domNode); + + // and then the actual content widget (changing it from prior-sibling to last-child), + // wrapped by a <div class=dijitAccordionChildWrapper> + this.containerNode = dojo.place("<div class='dijitAccordionChildWrapper' style='display:none'>", this.domNode); + dojo.place(this.contentWidget.domNode, this.containerNode); + }, + + postCreate: function(){ + this.inherited(arguments); + + // Map changes in content widget's title etc. to changes in the button + var button = this.button; + this._contentWidgetWatches = [ + this.contentWidget.watch('title', dojo.hitch(this, function(name, oldValue, newValue){ + button.set("label", newValue); + })), + this.contentWidget.watch('tooltip', dojo.hitch(this, function(name, oldValue, newValue){ + button.set("title", newValue); + })), + this.contentWidget.watch('iconClass', dojo.hitch(this, function(name, oldValue, newValue){ + button.set("iconClass", newValue); + })) + ]; + }, + + _setSelectedAttr: function(/*Boolean*/ isSelected){ + this._set("selected", isSelected); + this.button.set("selected", isSelected); + if(isSelected){ + var cw = this.contentWidget; + if(cw.onSelected){ cw.onSelected(); } + } + }, + + startup: function(){ + // Called by _Container.addChild() + this.contentWidget.startup(); + }, + + destroy: function(){ + this.button.destroyRecursive(); + + dojo.forEach(this._contentWidgetWatches || [], function(w){ w.unwatch(); }); + + delete this.contentWidget._buttonWidget; + delete this.contentWidget._wrapperWidget; + + this.inherited(arguments); + }, + + destroyDescendants: function(){ + // since getChildren isn't working for me, have to code this manually + this.contentWidget.destroyRecursive(); + } }); -this.inherited(arguments); -},_transition:function(_10,_11,_12){ -if(this._inTransition){ -return; -} -var _13=[]; -var _14=this._verticalSpace; -if(_10){ -_10._wrapperWidget.set("selected",true); -this._showChild(_10); -if(this.doLayout&&_10.resize){ -_10.resize(this._containerContentBox); -} -var _15=_10.domNode; -dojo.addClass(_15,"dijitVisible"); -dojo.removeClass(_15,"dijitHidden"); -if(_12){ -var _16=_15.style.overflow; -_15.style.overflow="hidden"; -_13.push(dojo.animateProperty({node:_15,duration:this.duration,properties:{height:{start:1,end:this._getTargetHeight(_15)}},onEnd:function(){ -_15.style.overflow=_16; -if(dojo.isIE){ -setTimeout(function(){ -dojo.removeClass(_15.parentNode,"dijitAccordionInnerContainerFocused"); -setTimeout(function(){ -dojo.addClass(_15.parentNode,"dijitAccordionInnerContainerFocused"); -},0); -},0); -} -}})); -} -} -if(_11){ -_11._wrapperWidget.set("selected",false); -var _17=_11.domNode; -if(_12){ -var _18=_17.style.overflow; -_17.style.overflow="hidden"; -_13.push(dojo.animateProperty({node:_17,duration:this.duration,properties:{height:{start:this._getTargetHeight(_17),end:1}},onEnd:function(){ -dojo.addClass(_17,"dijitHidden"); -dojo.removeClass(_17,"dijitVisible"); -_17.style.overflow=_18; -if(_11.onHide){ -_11.onHide(); -} -}})); -}else{ -dojo.addClass(_17,"dijitHidden"); -dojo.removeClass(_17,"dijitVisible"); -if(_11.onHide){ -_11.onHide(); -} -} -} -if(_12){ -this._inTransition=true; -var _19=dojo.fx.combine(_13); -_19.onEnd=dojo.hitch(this,function(){ -delete this._inTransition; + +dojo.declare("dijit.layout._AccordionButton", + [dijit._Widget, dijit._Templated, dijit._CssStateMixin], + { + // summary: + // The title bar to click to open up an accordion pane. + // Internal widget used by AccordionContainer. + // tags: + // private + + templateString: dojo.cache("dijit.layout", "templates/AccordionButton.html", "<div dojoAttachEvent='onclick:_onTitleClick' class='dijitAccordionTitle'>\n\t<div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='onkeypress:_onTitleKeyPress'\n\t\t\tclass='dijitAccordionTitleFocus' role=\"tab\" aria-expanded=\"false\"\n\t\t><span class='dijitInline dijitAccordionArrow' role=\"presentation\"></span\n\t\t><span class='arrowTextUp' role=\"presentation\">+</span\n\t\t><span class='arrowTextDown' role=\"presentation\">-</span\n\t\t><img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' style=\"vertical-align: middle\" role=\"presentation\"/>\n\t\t<span role=\"presentation\" dojoAttachPoint='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n"), + attributeMap: dojo.mixin(dojo.clone(dijit.layout.ContentPane.prototype.attributeMap), { + label: {node: "titleTextNode", type: "innerHTML" }, + title: {node: "titleTextNode", type: "attribute", attribute: "title"}, + iconClass: { node: "iconNode", type: "class" } + }), + + baseClass: "dijitAccordionTitle", + + getParent: function(){ + // summary: + // Returns the AccordionContainer parent. + // tags: + // private + return this.parent; + }, + + buildRendering: function(){ + this.inherited(arguments); + var titleTextNodeId = this.id.replace(' ','_'); + dojo.attr(this.titleTextNode, "id", titleTextNodeId+"_title"); + dijit.setWaiState(this.focusNode, "labelledby", dojo.attr(this.titleTextNode, "id")); + dojo.setSelectable(this.domNode, false); + }, + + getTitleHeight: function(){ + // summary: + // Returns the height of the title dom node. + return dojo._getMarginSize(this.domNode).h; // Integer + }, + + // TODO: maybe the parent should set these methods directly rather than forcing the code + // into the button widget? + _onTitleClick: function(){ + // summary: + // Callback when someone clicks my title. + var parent = this.getParent(); + parent.selectChild(this.contentWidget, true); + dijit.focus(this.focusNode); + }, + + _onTitleKeyPress: function(/*Event*/ evt){ + return this.getParent()._onKeyPress(evt, this.contentWidget); + }, + + _setSelectedAttr: function(/*Boolean*/ isSelected){ + this._set("selected", isSelected); + dijit.setWaiState(this.focusNode, "expanded", isSelected); + dijit.setWaiState(this.focusNode, "selected", isSelected); + this.focusNode.setAttribute("tabIndex", isSelected ? "0" : "-1"); + } }); -_19.play(); -} -},_onKeyPress:function(e,_1a){ -if(this._inTransition||this.disabled||e.altKey||!(_1a||e.ctrlKey)){ -if(this._inTransition){ -dojo.stopEvent(e); -} -return; -} -var k=dojo.keys,c=e.charOrCode; -if((_1a&&(c==k.LEFT_ARROW||c==k.UP_ARROW))||(e.ctrlKey&&c==k.PAGE_UP)){ -this._adjacent(false)._buttonWidget._onTitleClick(); -dojo.stopEvent(e); -}else{ -if((_1a&&(c==k.RIGHT_ARROW||c==k.DOWN_ARROW))||(e.ctrlKey&&(c==k.PAGE_DOWN||c==k.TAB))){ -this._adjacent(true)._buttonWidget._onTitleClick(); -dojo.stopEvent(e); -} -} -}}); -dojo.declare("dijit.layout._AccordionInnerContainer",[dijit._Widget,dijit._CssStateMixin],{baseClass:"dijitAccordionInnerContainer",isContainer:true,isLayoutContainer:true,buildRendering:function(){ -this.domNode=dojo.place("<div class='"+this.baseClass+"'>",this.contentWidget.domNode,"after"); -var _1b=this.contentWidget,cls=dojo.getObject(this.buttonWidget); -this.button=_1b._buttonWidget=(new cls({contentWidget:_1b,label:_1b.title,title:_1b.tooltip,dir:_1b.dir,lang:_1b.lang,iconClass:_1b.iconClass,id:_1b.id+"_button",parent:this.parent})).placeAt(this.domNode); -dojo.place(this.contentWidget.domNode,this.domNode); -},postCreate:function(){ -this.inherited(arguments); -this.connect(this.contentWidget,"set",function(_1c,_1d){ -var _1e={title:"label",tooltip:"title",iconClass:"iconClass"}[_1c]; -if(_1e){ -this.button.set(_1e,_1d); -} -},this); -},_setSelectedAttr:function(_1f){ -this.selected=_1f; -this.button.set("selected",_1f); -if(_1f){ -var cw=this.contentWidget; -if(cw.onSelected){ -cw.onSelected(); -} -} -},startup:function(){ -this.contentWidget.startup(); -},destroy:function(){ -this.button.destroyRecursive(); -delete this.contentWidget._buttonWidget; -delete this.contentWidget._wrapperWidget; -this.inherited(arguments); -},destroyDescendants:function(){ -this.contentWidget.destroyRecursive(); -}}); -dojo.declare("dijit.layout._AccordionButton",[dijit._Widget,dijit._Templated,dijit._CssStateMixin],{templateString:dojo.cache("dijit.layout","templates/AccordionButton.html","<div dojoAttachEvent='onclick:_onTitleClick' class='dijitAccordionTitle'>\n\t<div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='onkeypress:_onTitleKeyPress'\n\t\t\tclass='dijitAccordionTitleFocus' wairole=\"tab\" waiState=\"expanded-false\"\n\t\t><span class='dijitInline dijitAccordionArrow' waiRole=\"presentation\"></span\n\t\t><span class='arrowTextUp' waiRole=\"presentation\">+</span\n\t\t><span class='arrowTextDown' waiRole=\"presentation\">-</span\n\t\t><img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' style=\"vertical-align: middle\" waiRole=\"presentation\"/>\n\t\t<span waiRole=\"presentation\" dojoAttachPoint='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n"),attributeMap:dojo.mixin(dojo.clone(dijit.layout.ContentPane.prototype.attributeMap),{label:{node:"titleTextNode",type:"innerHTML"},title:{node:"titleTextNode",type:"attribute",attribute:"title"},iconClass:{node:"iconNode",type:"class"}}),baseClass:"dijitAccordionTitle",getParent:function(){ -return this.parent; -},postCreate:function(){ -this.inherited(arguments); -dojo.setSelectable(this.domNode,false); -var _20=dojo.attr(this.domNode,"id").replace(" ","_"); -dojo.attr(this.titleTextNode,"id",_20+"_title"); -dijit.setWaiState(this.focusNode,"labelledby",dojo.attr(this.titleTextNode,"id")); -},getTitleHeight:function(){ -return dojo.marginBox(this.domNode).h; -},_onTitleClick:function(){ -var _21=this.getParent(); -if(!_21._inTransition){ -_21.selectChild(this.contentWidget,true); -dijit.focus(this.focusNode); -} -},_onTitleKeyPress:function(evt){ -return this.getParent()._onKeyPress(evt,this.contentWidget); -},_setSelectedAttr:function(_22){ -this.selected=_22; -dijit.setWaiState(this.focusNode,"expanded",_22); -dijit.setWaiState(this.focusNode,"selected",_22); -this.focusNode.setAttribute("tabIndex",_22?"0":"-1"); -}}); + } diff --git a/lib/dijit/layout/AccordionPane.js b/lib/dijit/layout/AccordionPane.js index 98b92f8e8..394ba346c 100644 --- a/lib/dijit/layout/AccordionPane.js +++ b/lib/dijit/layout/AccordionPane.js @@ -1,16 +1,30 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.AccordionPane"]){ -dojo._hasResource["dijit.layout.AccordionPane"]=true; +if(!dojo._hasResource["dijit.layout.AccordionPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.AccordionPane"] = true; dojo.provide("dijit.layout.AccordionPane"); dojo.require("dijit.layout.ContentPane"); -dojo.declare("dijit.layout.AccordionPane",dijit.layout.ContentPane,{constructor:function(){ -dojo.deprecated("dijit.layout.AccordionPane deprecated, use ContentPane instead","","2.0"); -},onSelected:function(){ -}}); + + +dojo.declare("dijit.layout.AccordionPane", dijit.layout.ContentPane, { + // summary: + // Deprecated widget. Use `dijit.layout.ContentPane` instead. + // tags: + // deprecated + + constructor: function(){ + dojo.deprecated("dijit.layout.AccordionPane deprecated, use ContentPane instead", "", "2.0"); + }, + + onSelected: function(){ + // summary: + // called when this pane is selected + } +}); + } diff --git a/lib/dijit/layout/BorderContainer.js b/lib/dijit/layout/BorderContainer.js index 426a66216..c053256d4 100644 --- a/lib/dijit/layout/BorderContainer.js +++ b/lib/dijit/layout/BorderContainer.js @@ -1,327 +1,527 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.BorderContainer"]){ -dojo._hasResource["dijit.layout.BorderContainer"]=true; +if(!dojo._hasResource["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.BorderContainer"] = true; dojo.provide("dijit.layout.BorderContainer"); dojo.require("dijit.layout._LayoutWidget"); dojo.require("dojo.cookie"); -dojo.declare("dijit.layout.BorderContainer",dijit.layout._LayoutWidget,{design:"headline",gutters:true,liveSplitters:true,persist:false,baseClass:"dijitBorderContainer",_splitterClass:"dijit.layout._Splitter",postMixInProperties:function(){ -if(!this.gutters){ -this.baseClass+="NoGutter"; -} -this.inherited(arguments); -},postCreate:function(){ -this.inherited(arguments); -this._splitters={}; -this._splitterThickness={}; -},startup:function(){ -if(this._started){ -return; -} -dojo.forEach(this.getChildren(),this._setupChild,this); -this.inherited(arguments); -},_setupChild:function(_1){ -var _2=_1.region; -if(_2){ -this.inherited(arguments); -dojo.addClass(_1.domNode,this.baseClass+"Pane"); -var _3=this.isLeftToRight(); -if(_2=="leading"){ -_2=_3?"left":"right"; -} -if(_2=="trailing"){ -_2=_3?"right":"left"; -} -this["_"+_2]=_1.domNode; -this["_"+_2+"Widget"]=_1; -if((_1.splitter||this.gutters)&&!this._splitters[_2]){ -var _4=dojo.getObject(_1.splitter?this._splitterClass:"dijit.layout._Gutter"); -var _5=new _4({id:_1.id+"_splitter",container:this,child:_1,region:_2,live:this.liveSplitters}); -_5.isSplitter=true; -this._splitters[_2]=_5.domNode; -dojo.place(this._splitters[_2],_1.domNode,"after"); -_5.startup(); -} -_1.region=_2; -} -},_computeSplitterThickness:function(_6){ -this._splitterThickness[_6]=this._splitterThickness[_6]||dojo.marginBox(this._splitters[_6])[(/top|bottom/.test(_6)?"h":"w")]; -},layout:function(){ -for(var _7 in this._splitters){ -this._computeSplitterThickness(_7); -} -this._layoutChildren(); -},addChild:function(_8,_9){ -this.inherited(arguments); -if(this._started){ -this.layout(); -} -},removeChild:function(_a){ -var _b=_a.region; -var _c=this._splitters[_b]; -if(_c){ -dijit.byNode(_c).destroy(); -delete this._splitters[_b]; -delete this._splitterThickness[_b]; -} -this.inherited(arguments); -delete this["_"+_b]; -delete this["_"+_b+"Widget"]; -if(this._started){ -this._layoutChildren(); -} -dojo.removeClass(_a.domNode,this.baseClass+"Pane"); -},getChildren:function(){ -return dojo.filter(this.inherited(arguments),function(_d){ -return !_d.isSplitter; +dojo.require("dijit._Templated"); + + +dojo.declare( + "dijit.layout.BorderContainer", + dijit.layout._LayoutWidget, +{ + // summary: + // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides. + // + // description: + // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;", + // that contains a child widget marked region="center" and optionally children widgets marked + // region equal to "top", "bottom", "leading", "trailing", "left" or "right". + // Children along the edges will be laid out according to width or height dimensions and may + // include optional splitters (splitter="true") to make them resizable by the user. The remaining + // space is designated for the center region. + // + // The outer size must be specified on the BorderContainer node. Width must be specified for the sides + // and height for the top and bottom, respectively. No dimensions should be specified on the center; + // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like + // "left" and "right" except that they will be reversed in right-to-left environments. + // + // For complex layouts, multiple children can be specified for a single region. In this case, the + // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority) + // and which child is closer to the center (high layoutPriority). layoutPriority can also be used + // instead of the design attribute to conrol layout precedence of horizontal vs. vertical panes. + // example: + // | <div dojoType="dijit.layout.BorderContainer" design="sidebar" gutters="false" + // | style="width: 400px; height: 300px;"> + // | <div dojoType="dijit.layout.ContentPane" region="top">header text</div> + // | <div dojoType="dijit.layout.ContentPane" region="right" splitter="true" style="width: 200px;">table of contents</div> + // | <div dojoType="dijit.layout.ContentPane" region="center">client area</div> + // | </div> + + // design: String + // Which design is used for the layout: + // - "headline" (default) where the top and bottom extend + // the full width of the container + // - "sidebar" where the left and right sides extend from top to bottom. + design: "headline", + + // gutters: [const] Boolean + // Give each pane a border and margin. + // Margin determined by domNode.paddingLeft. + // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing. + gutters: true, + + // liveSplitters: [const] Boolean + // Specifies whether splitters resize as you drag (true) or only upon mouseup (false) + liveSplitters: true, + + // persist: Boolean + // Save splitter positions in a cookie. + persist: false, + + baseClass: "dijitBorderContainer", + + // _splitterClass: String + // Optional hook to override the default Splitter widget used by BorderContainer + _splitterClass: "dijit.layout._Splitter", + + postMixInProperties: function(){ + // change class name to indicate that BorderContainer is being used purely for + // layout (like LayoutContainer) rather than for pretty formatting. + if(!this.gutters){ + this.baseClass += "NoGutter"; + } + this.inherited(arguments); + }, + + startup: function(){ + if(this._started){ return; } + dojo.forEach(this.getChildren(), this._setupChild, this); + this.inherited(arguments); + }, + + _setupChild: function(/*dijit._Widget*/ child){ + // Override _LayoutWidget._setupChild(). + + var region = child.region; + if(region){ + this.inherited(arguments); + + dojo.addClass(child.domNode, this.baseClass+"Pane"); + + var ltr = this.isLeftToRight(); + if(region == "leading"){ region = ltr ? "left" : "right"; } + if(region == "trailing"){ region = ltr ? "right" : "left"; } + + // Create draggable splitter for resizing pane, + // or alternately if splitter=false but BorderContainer.gutters=true then + // insert dummy div just for spacing + if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){ + var _Splitter = dojo.getObject(child.splitter ? this._splitterClass : "dijit.layout._Gutter"); + var splitter = new _Splitter({ + id: child.id + "_splitter", + container: this, + child: child, + region: region, + live: this.liveSplitters + }); + splitter.isSplitter = true; + child._splitterWidget = splitter; + + dojo.place(splitter.domNode, child.domNode, "after"); + + // Splitters aren't added as Contained children, so we need to call startup explicitly + splitter.startup(); + } + child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc. + } + }, + + layout: function(){ + // Implement _LayoutWidget.layout() virtual method. + this._layoutChildren(); + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // Override _LayoutWidget.addChild(). + this.inherited(arguments); + if(this._started){ + this.layout(); //OPT + } + }, + + removeChild: function(/*dijit._Widget*/ child){ + // Override _LayoutWidget.removeChild(). + + var region = child.region; + var splitter = child._splitterWidget + if(splitter){ + splitter.destroy(); + delete child._splitterWidget; + } + this.inherited(arguments); + + if(this._started){ + this._layoutChildren(); + } + // Clean up whatever style changes we made to the child pane. + // Unclear how height and width should be handled. + dojo.removeClass(child.domNode, this.baseClass+"Pane"); + dojo.style(child.domNode, { + top: "auto", + bottom: "auto", + left: "auto", + right: "auto", + position: "static" + }); + dojo.style(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto"); + }, + + getChildren: function(){ + // Override _LayoutWidget.getChildren() to only return real children, not the splitters. + return dojo.filter(this.inherited(arguments), function(widget){ + return !widget.isSplitter; + }); + }, + + // TODO: remove in 2.0 + getSplitter: function(/*String*/region){ + // summary: + // Returns the widget responsible for rendering the splitter associated with region + // tags: + // deprecated + return dojo.filter(this.getChildren(), function(child){ + return child.region == region; + })[0]._splitterWidget; + }, + + resize: function(newSize, currentSize){ + // Overrides _LayoutWidget.resize(). + + // resetting potential padding to 0px to provide support for 100% width/height + padding + // TODO: this hack doesn't respect the box model and is a temporary fix + if(!this.cs || !this.pe){ + var node = this.domNode; + this.cs = dojo.getComputedStyle(node); + this.pe = dojo._getPadExtents(node, this.cs); + this.pe.r = dojo._toPixelValue(node, this.cs.paddingRight); + this.pe.b = dojo._toPixelValue(node, this.cs.paddingBottom); + + dojo.style(node, "padding", "0px"); + } + + this.inherited(arguments); + }, + + _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){ + // summary: + // This is the main routine for setting size/position of each child. + // description: + // With no arguments, measures the height of top/bottom panes, the width + // of left/right panes, and then sizes all panes accordingly. + // + // With changedRegion specified (as "left", "top", "bottom", or "right"), + // it changes that region's width/height to changedRegionSize and + // then resizes other regions that were affected. + // changedChildId: + // Id of the child which should be resized because splitter was dragged. + // changedChildSize: + // The new width/height (in pixels) to make specified child + + if(!this._borderBox || !this._borderBox.h){ + // We are currently hidden, or we haven't been sized by our parent yet. + // Abort. Someone will resize us later. + return; + } + + // Generate list of wrappers of my children in the order that I want layoutChildren() + // to process them (i.e. from the outside to the inside) + var wrappers = dojo.map(this.getChildren(), function(child, idx){ + return { + pane: child, + weight: [ + child.region == "center" ? Infinity : 0, + child.layoutPriority, + (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1), + idx + ] + }; + }, this); + wrappers.sort(function(a, b){ + var aw = a.weight, bw = b.weight; + for(var i=0; i<aw.length; i++){ + if(aw[i] != bw[i]){ + return aw[i] - bw[i]; + } + } + return 0; + }); + + // Make new list, combining the externally specified children with splitters and gutters + var childrenAndSplitters = []; + dojo.forEach(wrappers, function(wrapper){ + var pane = wrapper.pane; + childrenAndSplitters.push(pane); + if(pane._splitterWidget){ + childrenAndSplitters.push(pane._splitterWidget); + } + }); + + // Compute the box in which to lay out my children + var dim = { + l: this.pe.l, + t: this.pe.t, + w: this._borderBox.w - this.pe.w, + h: this._borderBox.h - this.pe.h + }; + + // Layout the children, possibly changing size due to a splitter drag + dijit.layout.layoutChildren(this.domNode, dim, childrenAndSplitters, + changedChildId, changedChildSize); + }, + + destroyRecursive: function(){ + // Destroy splitters first, while getChildren() still works + dojo.forEach(this.getChildren(), function(child){ + var splitter = child._splitterWidget; + if(splitter){ + splitter.destroy(); + } + delete child._splitterWidget; + }); + + // Then destroy the real children, and myself + this.inherited(arguments); + } }); -},getSplitter:function(_e){ -var _f=this._splitters[_e]; -return _f?dijit.byNode(_f):null; -},resize:function(_10,_11){ -if(!this.cs||!this.pe){ -var _12=this.domNode; -this.cs=dojo.getComputedStyle(_12); -this.pe=dojo._getPadExtents(_12,this.cs); -this.pe.r=dojo._toPixelValue(_12,this.cs.paddingRight); -this.pe.b=dojo._toPixelValue(_12,this.cs.paddingBottom); -dojo.style(_12,"padding","0px"); -} -this.inherited(arguments); -},_layoutChildren:function(_13,_14){ -if(!this._borderBox||!this._borderBox.h){ -return; -} -var _15=(this.design=="sidebar"); -var _16=0,_17=0,_18=0,_19=0; -var _1a={},_1b={},_1c={},_1d={},_1e=(this._center&&this._center.style)||{}; -var _1f=/left|right/.test(_13); -var _20=!_13||(!_1f&&!_15); -var _21=!_13||(_1f&&_15); -if(this._top){ -_1a=(_13=="top"||_21)&&this._top.style; -_16=_13=="top"?_14:dojo.marginBox(this._top).h; -} -if(this._left){ -_1b=(_13=="left"||_20)&&this._left.style; -_18=_13=="left"?_14:dojo.marginBox(this._left).w; -} -if(this._right){ -_1c=(_13=="right"||_20)&&this._right.style; -_19=_13=="right"?_14:dojo.marginBox(this._right).w; -} -if(this._bottom){ -_1d=(_13=="bottom"||_21)&&this._bottom.style; -_17=_13=="bottom"?_14:dojo.marginBox(this._bottom).h; -} -var _22=this._splitters; -var _23=_22.top,_24=_22.bottom,_25=_22.left,_26=_22.right; -var _27=this._splitterThickness; -var _28=_27.top||0,_29=_27.left||0,_2a=_27.right||0,_2b=_27.bottom||0; -if(_29>50||_2a>50){ -setTimeout(dojo.hitch(this,function(){ -this._splitterThickness={}; -for(var _2c in this._splitters){ -this._computeSplitterThickness(_2c); -} -this._layoutChildren(); -}),50); -return false; -} -var pe=this.pe; -var _2d={left:(_15?_18+_29:0)+pe.l+"px",right:(_15?_19+_2a:0)+pe.r+"px"}; -if(_23){ -dojo.mixin(_23.style,_2d); -_23.style.top=_16+pe.t+"px"; -} -if(_24){ -dojo.mixin(_24.style,_2d); -_24.style.bottom=_17+pe.b+"px"; -} -_2d={top:(_15?0:_16+_28)+pe.t+"px",bottom:(_15?0:_17+_2b)+pe.b+"px"}; -if(_25){ -dojo.mixin(_25.style,_2d); -_25.style.left=_18+pe.l+"px"; -} -if(_26){ -dojo.mixin(_26.style,_2d); -_26.style.right=_19+pe.r+"px"; -} -dojo.mixin(_1e,{top:pe.t+_16+_28+"px",left:pe.l+_18+_29+"px",right:pe.r+_19+_2a+"px",bottom:pe.b+_17+_2b+"px"}); -var _2e={top:_15?pe.t+"px":_1e.top,bottom:_15?pe.b+"px":_1e.bottom}; -dojo.mixin(_1b,_2e); -dojo.mixin(_1c,_2e); -_1b.left=pe.l+"px"; -_1c.right=pe.r+"px"; -_1a.top=pe.t+"px"; -_1d.bottom=pe.b+"px"; -if(_15){ -_1a.left=_1d.left=_18+_29+pe.l+"px"; -_1a.right=_1d.right=_19+_2a+pe.r+"px"; -}else{ -_1a.left=_1d.left=pe.l+"px"; -_1a.right=_1d.right=pe.r+"px"; -} -var _2f=this._borderBox.h-pe.t-pe.b,_30=_2f-(_16+_28+_17+_2b),_31=_15?_2f:_30; -var _32=this._borderBox.w-pe.l-pe.r,_33=_32-(_18+_29+_19+_2a),_34=_15?_33:_32; -var dim={top:{w:_34,h:_16},bottom:{w:_34,h:_17},left:{w:_18,h:_31},right:{w:_19,h:_31},center:{h:_30,w:_33}}; -if(_13){ -var _35=this["_"+_13+"Widget"],mb={}; -mb[/top|bottom/.test(_13)?"h":"w"]=_14; -_35.resize?_35.resize(mb,dim[_35.region]):dojo.marginBox(_35.domNode,mb); -} -var _36=dojo.isIE<8||(dojo.isIE&&dojo.isQuirks)||dojo.some(this.getChildren(),function(_37){ -return _37.domNode.tagName=="TEXTAREA"||_37.domNode.tagName=="INPUT"; + +// This argument can be specified for the children of a BorderContainer. +// Since any widget can be specified as a LayoutContainer child, mix it +// into the base widget class. (This is a hack, but it's effective.) +dojo.extend(dijit._Widget, { + // region: [const] String + // Parameter for children of `dijit.layout.BorderContainer`. + // Values: "top", "bottom", "leading", "trailing", "left", "right", "center". + // See the `dijit.layout.BorderContainer` description for details. + region: '', + + // layoutPriority: [const] Number + // Parameter for children of `dijit.layout.BorderContainer`. + // Children with a higher layoutPriority will be placed closer to the BorderContainer center, + // between children with a lower layoutPriority. + layoutPriority: 0, + + // splitter: [const] Boolean + // Parameter for child of `dijit.layout.BorderContainer` where region != "center". + // If true, enables user to resize the widget by putting a draggable splitter between + // this widget and the region=center widget. + splitter: false, + + // minSize: [const] Number + // Parameter for children of `dijit.layout.BorderContainer`. + // Specifies a minimum size (in pixels) for this widget when resized by a splitter. + minSize: 0, + + // maxSize: [const] Number + // Parameter for children of `dijit.layout.BorderContainer`. + // Specifies a maximum size (in pixels) for this widget when resized by a splitter. + maxSize: Infinity }); -if(_36){ -var _38=function(_39,_3a,_3b){ -if(_39){ -(_39.resize?_39.resize(_3a,_3b):dojo.marginBox(_39.domNode,_3a)); -} -}; -if(_25){ -_25.style.height=_31; -} -if(_26){ -_26.style.height=_31; -} -_38(this._leftWidget,{h:_31},dim.left); -_38(this._rightWidget,{h:_31},dim.right); -if(_23){ -_23.style.width=_34; -} -if(_24){ -_24.style.width=_34; -} -_38(this._topWidget,{w:_34},dim.top); -_38(this._bottomWidget,{w:_34},dim.bottom); -_38(this._centerWidget,dim.center); -}else{ -var _3c=!_13||(/top|bottom/.test(_13)&&this.design!="sidebar"),_3d=!_13||(/left|right/.test(_13)&&this.design=="sidebar"),_3e={center:true,left:_3c,right:_3c,top:_3d,bottom:_3d}; -dojo.forEach(this.getChildren(),function(_3f){ -if(_3f.resize&&_3e[_3f.region]){ -_3f.resize(null,dim[_3f.region]); -} -},this); -} -},destroy:function(){ -for(var _40 in this._splitters){ -var _41=this._splitters[_40]; -dijit.byNode(_41).destroy(); -dojo.destroy(_41); -} -delete this._splitters; -delete this._splitterThickness; -this.inherited(arguments); -}}); -dojo.extend(dijit._Widget,{region:"",splitter:false,minSize:0,maxSize:Infinity}); -dojo.require("dijit._Templated"); -dojo.declare("dijit.layout._Splitter",[dijit._Widget,dijit._Templated],{live:true,templateString:"<div class=\"dijitSplitter\" dojoAttachEvent=\"onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse\" tabIndex=\"0\" waiRole=\"separator\"><div class=\"dijitSplitterThumb\"></div></div>",postCreate:function(){ -this.inherited(arguments); -this.horizontal=/top|bottom/.test(this.region); -dojo.addClass(this.domNode,"dijitSplitter"+(this.horizontal?"H":"V")); -this._factor=/top|left/.test(this.region)?1:-1; -this._cookieName=this.container.id+"_"+this.region; -if(this.container.persist){ -var _42=dojo.cookie(this._cookieName); -if(_42){ -this.child.domNode.style[this.horizontal?"height":"width"]=_42; -} -} -},_computeMaxSize:function(){ -var dim=this.horizontal?"h":"w",_43=this.container._splitterThickness[this.region]; -var _44={left:"right",right:"left",top:"bottom",bottom:"top",leading:"trailing",trailing:"leading"},_45=this.container["_"+_44[this.region]]; -var _46=dojo.contentBox(this.container.domNode)[dim]-(_45?dojo.marginBox(_45)[dim]:0)-20-_43*2; -return Math.min(this.child.maxSize,_46); -},_startDrag:function(e){ -if(!this.cover){ -this.cover=dojo.doc.createElement("div"); -dojo.addClass(this.cover,"dijitSplitterCover"); -dojo.place(this.cover,this.child.domNode,"after"); -} -dojo.addClass(this.cover,"dijitSplitterCoverActive"); -if(this.fake){ -dojo.destroy(this.fake); -} -if(!(this._resize=this.live)){ -(this.fake=this.domNode.cloneNode(true)).removeAttribute("id"); -dojo.addClass(this.domNode,"dijitSplitterShadow"); -dojo.place(this.fake,this.domNode,"after"); -} -dojo.addClass(this.domNode,"dijitSplitterActive"); -dojo.addClass(this.domNode,"dijitSplitter"+(this.horizontal?"H":"V")+"Active"); -if(this.fake){ -dojo.removeClass(this.fake,"dijitSplitterHover"); -dojo.removeClass(this.fake,"dijitSplitter"+(this.horizontal?"H":"V")+"Hover"); -} -var _47=this._factor,max=this._computeMaxSize(),min=this.child.minSize||20,_48=this.horizontal,_49=_48?"pageY":"pageX",_4a=e[_49],_4b=this.domNode.style,dim=_48?"h":"w",_4c=dojo.marginBox(this.child.domNode)[dim],_4d=this.region,_4e=parseInt(this.domNode.style[_4d],10),_4f=this._resize,_50=this.child.domNode,_51=dojo.hitch(this.container,this.container._layoutChildren),de=dojo.doc; -this._handlers=(this._handlers||[]).concat([dojo.connect(de,"onmousemove",this._drag=function(e,_52){ -var _53=e[_49]-_4a,_54=_47*_53+_4c,_55=Math.max(Math.min(_54,max),min); -if(_4f||_52){ -_51(_4d,_55); -} -_4b[_4d]=_47*_53+_4e+(_55-_54)+"px"; -}),dojo.connect(de,"ondragstart",dojo.stopEvent),dojo.connect(dojo.body(),"onselectstart",dojo.stopEvent),dojo.connect(de,"onmouseup",this,"_stopDrag")]); -dojo.stopEvent(e); -},_onMouse:function(e){ -var o=(e.type=="mouseover"||e.type=="mouseenter"); -dojo.toggleClass(this.domNode,"dijitSplitterHover",o); -dojo.toggleClass(this.domNode,"dijitSplitter"+(this.horizontal?"H":"V")+"Hover",o); -},_stopDrag:function(e){ -try{ -if(this.cover){ -dojo.removeClass(this.cover,"dijitSplitterCoverActive"); -} -if(this.fake){ -dojo.destroy(this.fake); -} -dojo.removeClass(this.domNode,"dijitSplitterActive"); -dojo.removeClass(this.domNode,"dijitSplitter"+(this.horizontal?"H":"V")+"Active"); -dojo.removeClass(this.domNode,"dijitSplitterShadow"); -this._drag(e); -this._drag(e,true); -} -finally{ -this._cleanupHandlers(); -delete this._drag; -} -if(this.container.persist){ -dojo.cookie(this._cookieName,this.child.domNode.style[this.horizontal?"height":"width"],{expires:365}); -} -},_cleanupHandlers:function(){ -dojo.forEach(this._handlers,dojo.disconnect); -delete this._handlers; -},_onKeyPress:function(e){ -this._resize=true; -var _56=this.horizontal; -var _57=1; -var dk=dojo.keys; -switch(e.charOrCode){ -case _56?dk.UP_ARROW:dk.LEFT_ARROW: -_57*=-1; -case _56?dk.DOWN_ARROW:dk.RIGHT_ARROW: -break; -default: -return; -} -var _58=dojo.marginBox(this.child.domNode)[_56?"h":"w"]+this._factor*_57; -this.container._layoutChildren(this.region,Math.max(Math.min(_58,this._computeMaxSize()),this.child.minSize)); -dojo.stopEvent(e); -},destroy:function(){ -this._cleanupHandlers(); -delete this.child; -delete this.container; -delete this.cover; -delete this.fake; -this.inherited(arguments); -}}); -dojo.declare("dijit.layout._Gutter",[dijit._Widget,dijit._Templated],{templateString:"<div class=\"dijitGutter\" waiRole=\"presentation\"></div>",postCreate:function(){ -this.horizontal=/top|bottom/.test(this.region); -dojo.addClass(this.domNode,"dijitGutter"+(this.horizontal?"H":"V")); -}}); + +dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ], +{ + // summary: + // A draggable spacer between two items in a `dijit.layout.BorderContainer`. + // description: + // This is instantiated by `dijit.layout.BorderContainer`. Users should not + // create it directly. + // tags: + // private + +/*===== + // container: [const] dijit.layout.BorderContainer + // Pointer to the parent BorderContainer + container: null, + + // child: [const] dijit.layout._LayoutWidget + // Pointer to the pane associated with this splitter + child: null, + + // region: [const] String + // Region of pane associated with this splitter. + // "top", "bottom", "left", "right". + region: null, +=====*/ + + // live: [const] Boolean + // If true, the child's size changes and the child widget is redrawn as you drag the splitter; + // otherwise, the size doesn't change until you drop the splitter (by mouse-up) + live: true, + + templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>', + + postMixInProperties: function(){ + this.inherited(arguments); + + this.horizontal = /top|bottom/.test(this.region); + this._factor = /top|left/.test(this.region) ? 1 : -1; + this._cookieName = this.container.id + "_" + this.region; + }, + + buildRendering: function(){ + this.inherited(arguments); + + dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V")); + + if(this.container.persist){ + // restore old size + var persistSize = dojo.cookie(this._cookieName); + if(persistSize){ + this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize; + } + } + }, + + _computeMaxSize: function(){ + // summary: + // Return the maximum size that my corresponding pane can be set to + + var dim = this.horizontal ? 'h' : 'w', + childSize = dojo.marginBox(this.child.domNode)[dim], + center = dojo.filter(this.container.getChildren(), function(child){ return child.region == "center";})[0], + spaceAvailable = dojo.marginBox(center.domNode)[dim]; // can expand until center is crushed to 0 + + return Math.min(this.child.maxSize, childSize + spaceAvailable); + }, + + _startDrag: function(e){ + if(!this.cover){ + this.cover = dojo.doc.createElement('div'); + dojo.addClass(this.cover, "dijitSplitterCover"); + dojo.place(this.cover, this.child.domNode, "after"); + } + dojo.addClass(this.cover, "dijitSplitterCoverActive"); + + // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up. + if(this.fake){ dojo.destroy(this.fake); } + if(!(this._resize = this.live)){ //TODO: disable live for IE6? + // create fake splitter to display at old position while we drag + (this.fake = this.domNode.cloneNode(true)).removeAttribute("id"); + dojo.addClass(this.domNode, "dijitSplitterShadow"); + dojo.place(this.fake, this.domNode, "after"); + } + dojo.addClass(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active"); + if(this.fake){ + dojo.removeClass(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover"); + } + + //Performance: load data info local vars for onmousevent function closure + var factor = this._factor, + isHorizontal = this.horizontal, + axis = isHorizontal ? "pageY" : "pageX", + pageStart = e[axis], + splitterStyle = this.domNode.style, + dim = isHorizontal ? 'h' : 'w', + childStart = dojo.marginBox(this.child.domNode)[dim], + max = this._computeMaxSize(), + min = this.child.minSize || 20, + region = this.region, + splitterAttr = region == "top" || region == "bottom" ? "top" : "left", // style attribute of splitter to adjust + splitterStart = parseInt(splitterStyle[splitterAttr], 10), + resize = this._resize, + layoutFunc = dojo.hitch(this.container, "_layoutChildren", this.child.id), + de = dojo.doc; + + this._handlers = (this._handlers || []).concat([ + dojo.connect(de, "onmousemove", this._drag = function(e, forceResize){ + var delta = e[axis] - pageStart, + childSize = factor * delta + childStart, + boundChildSize = Math.max(Math.min(childSize, max), min); + + if(resize || forceResize){ + layoutFunc(boundChildSize); + } + // TODO: setting style directly (usually) sets content box size, need to set margin box size + splitterStyle[splitterAttr] = delta + splitterStart + factor*(boundChildSize - childSize) + "px"; + }), + dojo.connect(de, "ondragstart", dojo.stopEvent), + dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent), + dojo.connect(de, "onmouseup", this, "_stopDrag") + ]); + dojo.stopEvent(e); + }, + + _onMouse: function(e){ + var o = (e.type == "mouseover" || e.type == "mouseenter"); + dojo.toggleClass(this.domNode, "dijitSplitterHover", o); + dojo.toggleClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o); + }, + + _stopDrag: function(e){ + try{ + if(this.cover){ + dojo.removeClass(this.cover, "dijitSplitterCoverActive"); + } + if(this.fake){ dojo.destroy(this.fake); } + dojo.removeClass(this.domNode, "dijitSplitterActive dijitSplitter" + + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow"); + this._drag(e); //TODO: redundant with onmousemove? + this._drag(e, true); + }finally{ + this._cleanupHandlers(); + delete this._drag; + } + + if(this.container.persist){ + dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365}); + } + }, + + _cleanupHandlers: function(){ + dojo.forEach(this._handlers, dojo.disconnect); + delete this._handlers; + }, + + _onKeyPress: function(/*Event*/ e){ + // should we apply typematic to this? + this._resize = true; + var horizontal = this.horizontal; + var tick = 1; + var dk = dojo.keys; + switch(e.charOrCode){ + case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW: + tick *= -1; +// break; + case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW: + break; + default: +// this.inherited(arguments); + return; + } + var childSize = dojo._getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick; + this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize)); + dojo.stopEvent(e); + }, + + destroy: function(){ + this._cleanupHandlers(); + delete this.child; + delete this.container; + delete this.cover; + delete this.fake; + this.inherited(arguments); + } +}); + +dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated], +{ + // summary: + // Just a spacer div to separate side pane from center pane. + // Basically a trick to lookup the gutter/splitter width from the theme. + // description: + // Instantiated by `dijit.layout.BorderContainer`. Users should not + // create directly. + // tags: + // private + + templateString: '<div class="dijitGutter" role="presentation"></div>', + + postMixInProperties: function(){ + this.inherited(arguments); + this.horizontal = /top|bottom/.test(this.region); + }, + + buildRendering: function(){ + this.inherited(arguments); + dojo.addClass(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V")); + } +}); + } diff --git a/lib/dijit/layout/ContentPane.js b/lib/dijit/layout/ContentPane.js index 56952800a..399ec1bc9 100644 --- a/lib/dijit/layout/ContentPane.js +++ b/lib/dijit/layout/ContentPane.js @@ -1,291 +1,593 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.ContentPane"]){ -dojo._hasResource["dijit.layout.ContentPane"]=true; +if(!dojo._hasResource["dijit.layout.ContentPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.ContentPane"] = true; dojo.provide("dijit.layout.ContentPane"); dojo.require("dijit._Widget"); -dojo.require("dijit._Contained"); -dojo.require("dijit.layout._LayoutWidget"); -dojo.require("dojo.parser"); +dojo.require("dijit.layout._ContentPaneResizeMixin"); dojo.require("dojo.string"); dojo.require("dojo.html"); -dojo.requireLocalization("dijit","loading",null,"ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); -dojo.declare("dijit.layout.ContentPane",dijit._Widget,{href:"",extractContent:false,parseOnLoad:true,preventCache:false,preload:false,refreshOnShow:false,loadingMessage:"<span class='dijitContentPaneLoading'>${loadingState}</span>",errorMessage:"<span class='dijitContentPaneError'>${errorState}</span>",isLoaded:false,baseClass:"dijitContentPane",doLayout:true,ioArgs:{},isContainer:true,isLayoutContainer:true,onLoadDeferred:null,attributeMap:dojo.delegate(dijit._Widget.prototype.attributeMap,{title:[]}),postMixInProperties:function(){ -this.inherited(arguments); -var _1=dojo.i18n.getLocalization("dijit","loading",this.lang); -this.loadingMessage=dojo.string.substitute(this.loadingMessage,_1); -this.errorMessage=dojo.string.substitute(this.errorMessage,_1); -if(!this.href&&this.srcNodeRef&&this.srcNodeRef.innerHTML){ -this.isLoaded=true; -} -},buildRendering:function(){ -this.inherited(arguments); -if(!this.containerNode){ -this.containerNode=this.domNode; -} -},postCreate:function(){ -this.domNode.title=""; -if(!dojo.attr(this.domNode,"role")){ -dijit.setWaiRole(this.domNode,"group"); -} -dojo.addClass(this.domNode,this.baseClass); -},startup:function(){ -if(this._started){ -return; -} -var _2=dijit._Contained.prototype.getParent.call(this); -this._childOfLayoutWidget=_2&&_2.isLayoutContainer; -this._needLayout=!this._childOfLayoutWidget; -if(this.isLoaded){ -dojo.forEach(this.getChildren(),function(_3){ -_3.startup(); -}); -} -if(this._isShown()||this.preload){ -this._onShow(); -} -this.inherited(arguments); -},_checkIfSingleChild:function(){ -var _4=dojo.query("> *",this.containerNode).filter(function(_5){ -return _5.tagName!=="SCRIPT"; -}),_6=_4.filter(function(_7){ -return dojo.hasAttr(_7,"dojoType")||dojo.hasAttr(_7,"widgetId"); -}),_8=dojo.filter(_6.map(dijit.byNode),function(_9){ -return _9&&_9.domNode&&_9.resize; -}); -if(_4.length==_6.length&&_8.length==1){ -this._singleChild=_8[0]; -}else{ -delete this._singleChild; -} -dojo.toggleClass(this.containerNode,this.baseClass+"SingleChild",!!this._singleChild); -},setHref:function(_a){ -dojo.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.","","2.0"); -return this.set("href",_a); -},_setHrefAttr:function(_b){ -this.cancel(); -this.onLoadDeferred=new dojo.Deferred(dojo.hitch(this,"cancel")); -this.href=_b; -if(this._created&&(this.preload||this._isShown())){ -this._load(); -}else{ -this._hrefChanged=true; -} -return this.onLoadDeferred; -},setContent:function(_c){ -dojo.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use set('content', ...) instead.","","2.0"); -this.set("content",_c); -},_setContentAttr:function(_d){ -this.href=""; -this.cancel(); -this.onLoadDeferred=new dojo.Deferred(dojo.hitch(this,"cancel")); -this._setContent(_d||""); -this._isDownloaded=false; -return this.onLoadDeferred; -},_getContentAttr:function(){ -return this.containerNode.innerHTML; -},cancel:function(){ -if(this._xhrDfd&&(this._xhrDfd.fired==-1)){ -this._xhrDfd.cancel(); -} -delete this._xhrDfd; -this.onLoadDeferred=null; -},uninitialize:function(){ -if(this._beingDestroyed){ -this.cancel(); -} -this.inherited(arguments); -},destroyRecursive:function(_e){ -if(this._beingDestroyed){ -return; -} -this.inherited(arguments); -},resize:function(_f,_10){ -if(!this._wasShown){ -this._onShow(); -} -this._resizeCalled=true; -if(_f){ -dojo.marginBox(this.domNode,_f); -} -var cn=this.containerNode; -if(cn===this.domNode){ -var mb=_10||{}; -dojo.mixin(mb,_f||{}); -if(!("h" in mb)||!("w" in mb)){ -mb=dojo.mixin(dojo.marginBox(cn),mb); -} -this._contentBox=dijit.layout.marginBox2contentBox(cn,mb); -}else{ -this._contentBox=dojo.contentBox(cn); -} -this._layoutChildren(); -},_isShown:function(){ -if(this._childOfLayoutWidget){ -if(this._resizeCalled&&"open" in this){ -return this.open; -} -return this._resizeCalled; -}else{ -if("open" in this){ -return this.open; -}else{ -var _11=this.domNode; -return (_11.style.display!="none")&&(_11.style.visibility!="hidden")&&!dojo.hasClass(_11,"dijitHidden"); -} -} -},_onShow:function(){ -if(this.href){ -if(!this._xhrDfd&&(!this.isLoaded||this._hrefChanged||this.refreshOnShow)){ -this.refresh(); -} -}else{ -if(!this._childOfLayoutWidget&&this._needLayout){ -this._layoutChildren(); -} -} -this.inherited(arguments); -this._wasShown=true; -},refresh:function(){ -this.cancel(); -this.onLoadDeferred=new dojo.Deferred(dojo.hitch(this,"cancel")); -this._load(); -return this.onLoadDeferred; -},_load:function(){ -this._setContent(this.onDownloadStart(),true); -var _12=this; -var _13={preventCache:(this.preventCache||this.refreshOnShow),url:this.href,handleAs:"text"}; -if(dojo.isObject(this.ioArgs)){ -dojo.mixin(_13,this.ioArgs); -} -var _14=(this._xhrDfd=(this.ioMethod||dojo.xhrGet)(_13)); -_14.addCallback(function(_15){ -try{ -_12._isDownloaded=true; -_12._setContent(_15,false); -_12.onDownloadEnd(); -} -catch(err){ -_12._onError("Content",err); -} -delete _12._xhrDfd; -return _15; -}); -_14.addErrback(function(err){ -if(!_14.canceled){ -_12._onError("Download",err); -} -delete _12._xhrDfd; -return err; -}); -delete this._hrefChanged; -},_onLoadHandler:function(_16){ -this.isLoaded=true; -try{ -this.onLoadDeferred.callback(_16); -this.onLoad(_16); -} -catch(e){ -console.error("Error "+this.widgetId+" running custom onLoad code: "+e.message); -} -},_onUnloadHandler:function(){ -this.isLoaded=false; -try{ -this.onUnload(); -} -catch(e){ -console.error("Error "+this.widgetId+" running custom onUnload code: "+e.message); -} -},destroyDescendants:function(){ -if(this.isLoaded){ -this._onUnloadHandler(); -} -var _17=this._contentSetter; -dojo.forEach(this.getChildren(),function(_18){ -if(_18.destroyRecursive){ -_18.destroyRecursive(); -} -}); -if(_17){ -dojo.forEach(_17.parseResults,function(_19){ -if(_19.destroyRecursive&&_19.domNode&&_19.domNode.parentNode==dojo.body()){ -_19.destroyRecursive(); -} -}); -delete _17.parseResults; -} -dojo.html._emptyNode(this.containerNode); -delete this._singleChild; -},_setContent:function(_1a,_1b){ -this.destroyDescendants(); -var _1c=this._contentSetter; -if(!(_1c&&_1c instanceof dojo.html._ContentSetter)){ -_1c=this._contentSetter=new dojo.html._ContentSetter({node:this.containerNode,_onError:dojo.hitch(this,this._onError),onContentError:dojo.hitch(this,function(e){ -var _1d=this.onContentError(e); -try{ -this.containerNode.innerHTML=_1d; -} -catch(e){ -console.error("Fatal "+this.id+" could not change content due to "+e.message,e); -} -})}); -} -var _1e=dojo.mixin({cleanContent:this.cleanContent,extractContent:this.extractContent,parseContent:this.parseOnLoad,dir:this.dir,lang:this.lang},this._contentSetterParams||{}); -dojo.mixin(_1c,_1e); -_1c.set((dojo.isObject(_1a)&&_1a.domNode)?_1a.domNode:_1a); -delete this._contentSetterParams; -if(!_1b){ -dojo.forEach(this.getChildren(),function(_1f){ -if(!this.parseOnLoad||_1f.getParent){ -_1f.startup(); -} -},this); -this._scheduleLayout(); -this._onLoadHandler(_1a); -} -},_onError:function(_20,err,_21){ -this.onLoadDeferred.errback(err); -var _22=this["on"+_20+"Error"].call(this,err); -if(_21){ -console.error(_21,err); -}else{ -if(_22){ -this._setContent(_22,true); -} -} -},_scheduleLayout:function(){ -if(this._isShown()){ -this._layoutChildren(); -}else{ -this._needLayout=true; -} -},_layoutChildren:function(){ -if(this.doLayout){ -this._checkIfSingleChild(); -} -if(this._singleChild&&this._singleChild.resize){ -var cb=this._contentBox||dojo.contentBox(this.containerNode); -this._singleChild.resize({w:cb.w,h:cb.h}); -}else{ -dojo.forEach(this.getChildren(),function(_23){ -if(_23.resize){ -_23.resize(); -} +dojo.requireLocalization("dijit", "loading", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); + + +dojo.declare( + "dijit.layout.ContentPane", [dijit._Widget, dijit.layout._ContentPaneResizeMixin], +{ + // summary: + // A widget containing an HTML fragment, specified inline + // or by uri. Fragment may include widgets. + // + // description: + // This widget embeds a document fragment in the page, specified + // either by uri, javascript generated markup or DOM reference. + // Any widgets within this content are instantiated and managed, + // but laid out according to the HTML structure. Unlike IFRAME, + // ContentPane embeds a document fragment as would be found + // inside the BODY tag of a full HTML document. It should not + // contain the HTML, HEAD, or BODY tags. + // For more advanced functionality with scripts and + // stylesheets, see dojox.layout.ContentPane. This widget may be + // used stand alone or as a base class for other widgets. + // ContentPane is useful as a child of other layout containers + // such as BorderContainer or TabContainer, but note that those + // widgets can contain any widget as a child. + // + // example: + // Some quick samples: + // To change the innerHTML: cp.set('content', '<b>new content</b>') + // + // Or you can send it a NodeList: cp.set('content', dojo.query('div [class=selected]', userSelection)) + // + // To do an ajax update: cp.set('href', url) + + // href: String + // The href of the content that displays now. + // Set this at construction if you want to load data externally when the + // pane is shown. (Set preload=true to load it immediately.) + // Changing href after creation doesn't have any effect; Use set('href', ...); + href: "", + +/*===== + // content: String || DomNode || NodeList || dijit._Widget + // The innerHTML of the ContentPane. + // Note that the initialization parameter / argument to set("content", ...) + // can be a String, DomNode, Nodelist, or _Widget. + content: "", +=====*/ + + // extractContent: Boolean + // Extract visible content from inside of <body> .... </body>. + // I.e., strip <html> and <head> (and it's contents) from the href + extractContent: false, + + // parseOnLoad: Boolean + // Parse content and create the widgets, if any. + parseOnLoad: true, + + // parserScope: String + // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo, + // will search for data-dojo-type (or dojoType). For backwards compatibility + // reasons defaults to dojo._scopeName (which is "dojo" except when + // multi-version support is used, when it will be something like dojo16, dojo20, etc.) + parserScope: dojo._scopeName, + + // preventCache: Boolean + // Prevent caching of data from href's by appending a timestamp to the href. + preventCache: false, + + // preload: Boolean + // Force load of data on initialization even if pane is hidden. + preload: false, + + // refreshOnShow: Boolean + // Refresh (re-download) content when pane goes from hidden to shown + refreshOnShow: false, + + // loadingMessage: String + // Message that shows while downloading + loadingMessage: "<span class='dijitContentPaneLoading'>${loadingState}</span>", + + // errorMessage: String + // Message that shows if an error occurs + errorMessage: "<span class='dijitContentPaneError'>${errorState}</span>", + + // isLoaded: [readonly] Boolean + // True if the ContentPane has data in it, either specified + // during initialization (via href or inline content), or set + // via set('content', ...) / set('href', ...) + // + // False if it doesn't have any content, or if ContentPane is + // still in the process of downloading href. + isLoaded: false, + + baseClass: "dijitContentPane", + + // ioArgs: Object + // Parameters to pass to xhrGet() request, for example: + // | <div dojoType="dijit.layout.ContentPane" href="./bar" ioArgs="{timeout: 500}"> + ioArgs: {}, + + // onLoadDeferred: [readonly] dojo.Deferred + // This is the `dojo.Deferred` returned by set('href', ...) and refresh(). + // Calling onLoadDeferred.addCallback() or addErrback() registers your + // callback to be called only once, when the prior set('href', ...) call or + // the initial href parameter to the constructor finishes loading. + // + // This is different than an onLoad() handler which gets called any time any href + // or content is loaded. + onLoadDeferred: null, + + // Override _Widget's attributeMap because we don't want the title attribute (used to specify + // tab labels) to be copied to ContentPane.domNode... otherwise a tooltip shows up over the + // entire pane. + attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { + title: [] + }), + + // Flag to parser that I'll parse my contents, so it shouldn't. + stopParser: true, + + // template: [private] Boolean + // Flag from the parser that this ContentPane is inside a template + // so the contents are pre-parsed. + // (TODO: this declaration can be commented out in 2.0) + template: false, + + create: function(params, srcNodeRef){ + // Convert a srcNodeRef argument into a content parameter, so that the original contents are + // processed in the same way as contents set via set("content", ...), calling the parser etc. + // Avoid modifying original params object since that breaks NodeList instantiation, see #11906. + if((!params || !params.template) && srcNodeRef && !("href" in params) && !("content" in params)){ + var df = dojo.doc.createDocumentFragment(); + srcNodeRef = dojo.byId(srcNodeRef) + while(srcNodeRef.firstChild){ + df.appendChild(srcNodeRef.firstChild); + } + params = dojo.delegate(params, {content: df}); + } + this.inherited(arguments, [params, srcNodeRef]); + }, + + postMixInProperties: function(){ + this.inherited(arguments); + var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang); + this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages); + this.errorMessage = dojo.string.substitute(this.errorMessage, messages); + }, + + buildRendering: function(){ + this.inherited(arguments); + + // Since we have no template we need to set this.containerNode ourselves, to make getChildren() work. + // For subclasses of ContentPane that do have a template, does nothing. + if(!this.containerNode){ + this.containerNode = this.domNode; + } + + // remove the title attribute so it doesn't show up when hovering + // over a node (TODO: remove in 2.0, no longer needed after #11490) + this.domNode.title = ""; + + if(!dojo.attr(this.domNode,"role")){ + dijit.setWaiRole(this.domNode, "group"); + } + }, + + _startChildren: function(){ + // summary: + // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects + + // This starts all the widgets + this.inherited(arguments); + + // And this catches stuff like dojo.dnd.Source + if(this._contentSetter){ + dojo.forEach(this._contentSetter.parseResults, function(obj){ + if(!obj._started && !obj._destroyed && dojo.isFunction(obj.startup)){ + obj.startup(); + obj._started = true; + } + }, this); + } + }, + + setHref: function(/*String|Uri*/ href){ + // summary: + // Deprecated. Use set('href', ...) instead. + dojo.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.", "", "2.0"); + return this.set("href", href); + }, + _setHrefAttr: function(/*String|Uri*/ href){ + // summary: + // Hook so set("href", ...) works. + // description: + // Reset the (external defined) content of this pane and replace with new url + // Note: It delays the download until widget is shown if preload is false. + // href: + // url to the page you want to get, must be within the same domain as your mainpage + + // Cancel any in-flight requests (a set('href', ...) will cancel any in-flight set('href', ...)) + this.cancel(); + + this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); + this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad")); + + this._set("href", href); + + // _setHrefAttr() is called during creation and by the user, after creation. + // Assuming preload == false, only in the second case do we actually load the URL; + // otherwise it's done in startup(), and only if this widget is shown. + if(this.preload || (this._created && this._isShown())){ + this._load(); + }else{ + // Set flag to indicate that href needs to be loaded the next time the + // ContentPane is made visible + this._hrefChanged = true; + } + + return this.onLoadDeferred; // dojo.Deferred + }, + + setContent: function(/*String|DomNode|Nodelist*/data){ + // summary: + // Deprecated. Use set('content', ...) instead. + dojo.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use set('content', ...) instead.", "", "2.0"); + this.set("content", data); + }, + _setContentAttr: function(/*String|DomNode|Nodelist*/data){ + // summary: + // Hook to make set("content", ...) work. + // Replaces old content with data content, include style classes from old content + // data: + // the new Content may be String, DomNode or NodeList + // + // if data is a NodeList (or an array of nodes) nodes are copied + // so you can import nodes from another document implicitly + + // clear href so we can't run refresh and clear content + // refresh should only work if we downloaded the content + this._set("href", ""); + + // Cancel any in-flight requests (a set('content', ...) will cancel any in-flight set('href', ...)) + this.cancel(); + + // Even though user is just setting content directly, still need to define an onLoadDeferred + // because the _onLoadHandler() handler is still getting called from setContent() + this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); + if(this._created){ + // For back-compat reasons, call onLoad() for set('content', ...) + // calls but not for content specified in srcNodeRef (ie: <div dojoType=ContentPane>...</div>) + // or as initialization parameter (ie: new ContentPane({content: ...}) + this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad")); + } + + this._setContent(data || ""); + + this._isDownloaded = false; // mark that content is from a set('content') not a set('href') + + return this.onLoadDeferred; // dojo.Deferred + }, + _getContentAttr: function(){ + // summary: + // Hook to make get("content") work + return this.containerNode.innerHTML; + }, + + cancel: function(){ + // summary: + // Cancels an in-flight download of content + if(this._xhrDfd && (this._xhrDfd.fired == -1)){ + this._xhrDfd.cancel(); + } + delete this._xhrDfd; // garbage collect + + this.onLoadDeferred = null; + }, + + uninitialize: function(){ + if(this._beingDestroyed){ + this.cancel(); + } + this.inherited(arguments); + }, + + destroyRecursive: function(/*Boolean*/ preserveDom){ + // summary: + // Destroy the ContentPane and its contents + + // if we have multiple controllers destroying us, bail after the first + if(this._beingDestroyed){ + return; + } + this.inherited(arguments); + }, + + _onShow: function(){ + // summary: + // Called when the ContentPane is made visible + // description: + // For a plain ContentPane, this is called on initialization, from startup(). + // If the ContentPane is a hidden pane of a TabContainer etc., then it's + // called whenever the pane is made visible. + // + // Does necessary processing, including href download and layout/resize of + // child widget(s) + + this.inherited(arguments); + + if(this.href){ + if(!this._xhrDfd && // if there's an href that isn't already being loaded + (!this.isLoaded || this._hrefChanged || this.refreshOnShow) + ){ + return this.refresh(); // If child has an href, promise that fires when the load is complete + } + } + }, + + refresh: function(){ + // summary: + // [Re]download contents of href and display + // description: + // 1. cancels any currently in-flight requests + // 2. posts "loading..." message + // 3. sends XHR to download new data + + // Cancel possible prior in-flight request + this.cancel(); + + this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); + this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad")); + this._load(); + return this.onLoadDeferred; // If child has an href, promise that fires when refresh is complete + }, + + _load: function(){ + // summary: + // Load/reload the href specified in this.href + + // display loading message + this._setContent(this.onDownloadStart(), true); + + var self = this; + var getArgs = { + preventCache: (this.preventCache || this.refreshOnShow), + url: this.href, + handleAs: "text" + }; + if(dojo.isObject(this.ioArgs)){ + dojo.mixin(getArgs, this.ioArgs); + } + + var hand = (this._xhrDfd = (this.ioMethod || dojo.xhrGet)(getArgs)); + + hand.addCallback(function(html){ + try{ + self._isDownloaded = true; + self._setContent(html, false); + self.onDownloadEnd(); + }catch(err){ + self._onError('Content', err); // onContentError + } + delete self._xhrDfd; + return html; + }); + + hand.addErrback(function(err){ + if(!hand.canceled){ + // show error message in the pane + self._onError('Download', err); // onDownloadError + } + delete self._xhrDfd; + return err; + }); + + // Remove flag saying that a load is needed + delete this._hrefChanged; + }, + + _onLoadHandler: function(data){ + // summary: + // This is called whenever new content is being loaded + this._set("isLoaded", true); + try{ + this.onLoadDeferred.callback(data); + }catch(e){ + console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message); + } + }, + + _onUnloadHandler: function(){ + // summary: + // This is called whenever the content is being unloaded + this._set("isLoaded", false); + try{ + this.onUnload(); + }catch(e){ + console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message); + } + }, + + destroyDescendants: function(){ + // summary: + // Destroy all the widgets inside the ContentPane and empty containerNode + + // Make sure we call onUnload (but only when the ContentPane has real content) + if(this.isLoaded){ + this._onUnloadHandler(); + } + + // Even if this.isLoaded == false there might still be a "Loading..." message + // to erase, so continue... + + // For historical reasons we need to delete all widgets under this.containerNode, + // even ones that the user has created manually. + var setter = this._contentSetter; + dojo.forEach(this.getChildren(), function(widget){ + if(widget.destroyRecursive){ + widget.destroyRecursive(); + } + }); + if(setter){ + // Most of the widgets in setter.parseResults have already been destroyed, but + // things like Menu that have been moved to <body> haven't yet + dojo.forEach(setter.parseResults, function(widget){ + if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == dojo.body()){ + widget.destroyRecursive(); + } + }); + delete setter.parseResults; + } + + // And then clear away all the DOM nodes + dojo.html._emptyNode(this.containerNode); + + // Delete any state information we have about current contents + delete this._singleChild; + }, + + _setContent: function(/*String|DocumentFragment*/ cont, /*Boolean*/ isFakeContent){ + // summary: + // Insert the content into the container node + + // first get rid of child widgets + this.destroyDescendants(); + + // dojo.html.set will take care of the rest of the details + // we provide an override for the error handling to ensure the widget gets the errors + // configure the setter instance with only the relevant widget instance properties + // NOTE: unless we hook into attr, or provide property setters for each property, + // we need to re-configure the ContentSetter with each use + var setter = this._contentSetter; + if(! (setter && setter instanceof dojo.html._ContentSetter)){ + setter = this._contentSetter = new dojo.html._ContentSetter({ + node: this.containerNode, + _onError: dojo.hitch(this, this._onError), + onContentError: dojo.hitch(this, function(e){ + // fires if a domfault occurs when we are appending this.errorMessage + // like for instance if domNode is a UL and we try append a DIV + var errMess = this.onContentError(e); + try{ + this.containerNode.innerHTML = errMess; + }catch(e){ + console.error('Fatal '+this.id+' could not change content due to '+e.message, e); + } + })/*, + _onError */ + }); + }; + + var setterParams = dojo.mixin({ + cleanContent: this.cleanContent, + extractContent: this.extractContent, + parseContent: this.parseOnLoad, + parserScope: this.parserScope, + startup: false, + dir: this.dir, + lang: this.lang + }, this._contentSetterParams || {}); + + setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams ); + + // setter params must be pulled afresh from the ContentPane each time + delete this._contentSetterParams; + + if(this.doLayout){ + this._checkIfSingleChild(); + } + + if(!isFakeContent){ + if(this._started){ + // Startup each top level child widget (and they will start their children, recursively) + this._startChildren(); + + // Call resize() on each of my child layout widgets, + // or resize() on my single child layout widget... + // either now (if I'm currently visible) or when I become visible + this._scheduleLayout(); + } + + this._onLoadHandler(cont); + } + }, + + _onError: function(type, err, consoleText){ + this.onLoadDeferred.errback(err); + + // shows user the string that is returned by on[type]Error + // override on[type]Error and return your own string to customize + var errText = this['on' + type + 'Error'].call(this, err); + if(consoleText){ + console.error(consoleText, err); + }else if(errText){// a empty string won't change current content + this._setContent(errText, true); + } + }, + + // EVENT's, should be overide-able + onLoad: function(data){ + // summary: + // Event hook, is called after everything is loaded and widgetified + // tags: + // callback + }, + + onUnload: function(){ + // summary: + // Event hook, is called before old content is cleared + // tags: + // callback + }, + + onDownloadStart: function(){ + // summary: + // Called before download starts. + // description: + // The string returned by this function will be the html + // that tells the user we are loading something. + // Override with your own function if you want to change text. + // tags: + // extension + return this.loadingMessage; + }, + + onContentError: function(/*Error*/ error){ + // summary: + // Called on DOM faults, require faults etc. in content. + // + // In order to display an error message in the pane, return + // the error message from this method, as an HTML string. + // + // By default (if this method is not overriden), it returns + // nothing, so the error message is just printed to the console. + // tags: + // extension + }, + + onDownloadError: function(/*Error*/ error){ + // summary: + // Called when download error occurs. + // + // In order to display an error message in the pane, return + // the error message from this method, as an HTML string. + // + // Default behavior (if this method is not overriden) is to display + // the error message inside the pane. + // tags: + // extension + return this.errorMessage; + }, + + onDownloadEnd: function(){ + // summary: + // Called when download is finished. + // tags: + // callback + } }); -} -delete this._needLayout; -},onLoad:function(_24){ -},onUnload:function(){ -},onDownloadStart:function(){ -return this.loadingMessage; -},onContentError:function(_25){ -},onDownloadError:function(_26){ -return this.errorMessage; -},onDownloadEnd:function(){ -}}); + } diff --git a/lib/dijit/layout/LayoutContainer.js b/lib/dijit/layout/LayoutContainer.js index 9d9fdc6a4..11204346f 100644 --- a/lib/dijit/layout/LayoutContainer.js +++ b/lib/dijit/layout/LayoutContainer.js @@ -1,28 +1,86 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.LayoutContainer"]){ -dojo._hasResource["dijit.layout.LayoutContainer"]=true; +if(!dojo._hasResource["dijit.layout.LayoutContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.LayoutContainer"] = true; dojo.provide("dijit.layout.LayoutContainer"); dojo.require("dijit.layout._LayoutWidget"); -dojo.declare("dijit.layout.LayoutContainer",dijit.layout._LayoutWidget,{baseClass:"dijitLayoutContainer",constructor:function(){ -dojo.deprecated("dijit.layout.LayoutContainer is deprecated","use BorderContainer instead",2); -},layout:function(){ -dijit.layout.layoutChildren(this.domNode,this._contentBox,this.getChildren()); -},addChild:function(_1,_2){ -this.inherited(arguments); -if(this._started){ -dijit.layout.layoutChildren(this.domNode,this._contentBox,this.getChildren()); -} -},removeChild:function(_3){ -this.inherited(arguments); -if(this._started){ -dijit.layout.layoutChildren(this.domNode,this._contentBox,this.getChildren()); -} -}}); -dojo.extend(dijit._Widget,{layoutAlign:"none"}); + + +dojo.declare("dijit.layout.LayoutContainer", + dijit.layout._LayoutWidget, + { + // summary: + // Deprecated. Use `dijit.layout.BorderContainer` instead. + // + // description: + // Provides Delphi-style panel layout semantics. + // + // A LayoutContainer is a box with a specified size (like style="width: 500px; height: 500px;"), + // that contains children widgets marked with "layoutAlign" of "left", "right", "bottom", "top", and "client". + // It takes it's children marked as left/top/bottom/right, and lays them out along the edges of the box, + // and then it takes the child marked "client" and puts it into the remaining space in the middle. + // + // Left/right positioning is similar to CSS's "float: left" and "float: right", + // and top/bottom positioning would be similar to "float: top" and "float: bottom", if there were such + // CSS. + // + // Note that there can only be one client element, but there can be multiple left, right, top, + // or bottom elements. + // + // example: + // | <style> + // | html, body{ height: 100%; width: 100%; } + // | </style> + // | <div dojoType="dijit.layout.LayoutContainer" style="width: 100%; height: 100%"> + // | <div dojoType="dijit.layout.ContentPane" layoutAlign="top">header text</div> + // | <div dojoType="dijit.layout.ContentPane" layoutAlign="left" style="width: 200px;">table of contents</div> + // | <div dojoType="dijit.layout.ContentPane" layoutAlign="client">client area</div> + // | </div> + // + // Lays out each child in the natural order the children occur in. + // Basically each child is laid out into the "remaining space", where "remaining space" is initially + // the content area of this widget, but is reduced to a smaller rectangle each time a child is added. + // tags: + // deprecated + + baseClass: "dijitLayoutContainer", + + constructor: function(){ + dojo.deprecated("dijit.layout.LayoutContainer is deprecated", "use BorderContainer instead", 2.0); + }, + + layout: function(){ + dijit.layout.layoutChildren(this.domNode, this._contentBox, this.getChildren()); + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + this.inherited(arguments); + if(this._started){ + dijit.layout.layoutChildren(this.domNode, this._contentBox, this.getChildren()); + } + }, + + removeChild: function(/*dijit._Widget*/ widget){ + this.inherited(arguments); + if(this._started){ + dijit.layout.layoutChildren(this.domNode, this._contentBox, this.getChildren()); + } + } +}); + +// This argument can be specified for the children of a LayoutContainer. +// Since any widget can be specified as a LayoutContainer child, mix it +// into the base widget class. (This is a hack, but it's effective.) +dojo.extend(dijit._Widget, { + // layoutAlign: String + // "none", "left", "right", "bottom", "top", and "client". + // See the LayoutContainer description for details on this parameter. + layoutAlign: 'none' +}); + } diff --git a/lib/dijit/layout/LinkPane.js b/lib/dijit/layout/LinkPane.js index 38e6567e7..34e2d92bb 100644 --- a/lib/dijit/layout/LinkPane.js +++ b/lib/dijit/layout/LinkPane.js @@ -1,20 +1,51 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.LinkPane"]){ -dojo._hasResource["dijit.layout.LinkPane"]=true; +if(!dojo._hasResource["dijit.layout.LinkPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.LinkPane"] = true; dojo.provide("dijit.layout.LinkPane"); dojo.require("dijit.layout.ContentPane"); dojo.require("dijit._Templated"); -dojo.declare("dijit.layout.LinkPane",[dijit.layout.ContentPane,dijit._Templated],{templateString:"<div class=\"dijitLinkPane\" dojoAttachPoint=\"containerNode\"></div>",postMixInProperties:function(){ -if(this.srcNodeRef){ -this.title+=this.srcNodeRef.innerHTML; -} -this.inherited(arguments); -},_fillContent:function(_1){ -}}); + + +dojo.declare("dijit.layout.LinkPane", + [dijit.layout.ContentPane, dijit._Templated], + { + // summary: + // A ContentPane with an href where (when declared in markup) + // the title is specified as innerHTML rather than as a title attribute. + // description: + // LinkPane is just a ContentPane that is declared in markup similarly + // to an anchor. The anchor's body (the words between `<a>` and `</a>`) + // become the title of the widget (used for TabContainer, AccordionContainer, etc.) + // example: + // | <a href="foo.html">my title</a> + + // I'm using a template because the user may specify the input as + // <a href="foo.html">title</a>, in which case we need to get rid of the + // <a> because we don't want a link. + templateString: '<div class="dijitLinkPane" dojoAttachPoint="containerNode"></div>', + + postMixInProperties: function(){ + // If user has specified node contents, they become the title + // (the link must be plain text) + if(this.srcNodeRef){ + this.title += this.srcNodeRef.innerHTML; + } + this.inherited(arguments); + }, + + _fillContent: function(/*DomNode*/ source){ + // Overrides _Templated._fillContent(). + + // _Templated._fillContent() relocates srcNodeRef innerHTML to templated container node, + // but in our case the srcNodeRef innerHTML is the title, so shouldn't be + // copied + } +}); + } diff --git a/lib/dijit/layout/ScrollingTabController.js b/lib/dijit/layout/ScrollingTabController.js index 4ee0bd16e..a91c526de 100644 --- a/lib/dijit/layout/ScrollingTabController.js +++ b/lib/dijit/layout/ScrollingTabController.js @@ -1,199 +1,483 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.ScrollingTabController"]){ -dojo._hasResource["dijit.layout.ScrollingTabController"]=true; +if(!dojo._hasResource["dijit.layout.ScrollingTabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.ScrollingTabController"] = true; dojo.provide("dijit.layout.ScrollingTabController"); dojo.require("dijit.layout.TabController"); dojo.require("dijit.Menu"); -dojo.declare("dijit.layout.ScrollingTabController",dijit.layout.TabController,{templateString:dojo.cache("dijit.layout","templates/ScrollingTabController.html","<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\" iconClass=\"dijitTabStripMenuIcon\"\n\t\t\tdojoAttachPoint=\"_menuBtn\" showLabel=false>▼</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\" iconClass=\"dijitTabStripSlideLeftIcon\"\n\t\t\tdojoAttachPoint=\"_leftBtn\" dojoAttachEvent=\"onClick: doSlideLeft\" showLabel=false>◀</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\" iconClass=\"dijitTabStripSlideRightIcon\"\n\t\t\tdojoAttachPoint=\"_rightBtn\" dojoAttachEvent=\"onClick: doSlideRight\" showLabel=false>▶</div>\n\t<div class='dijitTabListWrapper' dojoAttachPoint='tablistWrapper'>\n\t\t<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'\n\t\t\t\tdojoAttachPoint='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>\n"),useMenu:true,useSlider:true,tabStripClass:"",widgetsInTemplate:true,_minScroll:5,attributeMap:dojo.delegate(dijit._Widget.prototype.attributeMap,{"class":"containerNode"}),postCreate:function(){ -this.inherited(arguments); -var n=this.domNode; -this.scrollNode=this.tablistWrapper; -this._initButtons(); -if(!this.tabStripClass){ -this.tabStripClass="dijitTabContainer"+this.tabPosition.charAt(0).toUpperCase()+this.tabPosition.substr(1).replace(/-.*/,"")+"None"; -dojo.addClass(n,"tabStrip-disabled"); -} -dojo.addClass(this.tablistWrapper,this.tabStripClass); -},onStartup:function(){ -this.inherited(arguments); -dojo.style(this.domNode,"visibility","visible"); -this._postStartup=true; -},onAddChild:function(_1,_2){ -this.inherited(arguments); -var _3; -if(this.useMenu){ -var _4=this.containerId; -_3=new dijit.MenuItem({id:_1.id+"_stcMi",label:_1.title,dir:_1.dir,lang:_1.lang,onClick:dojo.hitch(this,function(){ -var _5=dijit.byId(_4); -_5.selectChild(_1); -})}); -this._menuChildren[_1.id]=_3; -this._menu.addChild(_3,_2); -} -this.pane2handles[_1.id].push(this.connect(this.pane2button[_1.id],"set",function(_6,_7){ -if(this._postStartup){ -if(_6=="label"){ -if(_3){ -_3.set(_6,_7); -} -if(this._dim){ -this.resize(this._dim); -} -} -} -})); -dojo.style(this.containerNode,"width",(dojo.style(this.containerNode,"width")+200)+"px"); -},onRemoveChild:function(_8,_9){ -var _a=this.pane2button[_8.id]; -if(this._selectedTab===_a.domNode){ -this._selectedTab=null; -} -if(this.useMenu&&_8&&_8.id&&this._menuChildren[_8.id]){ -this._menu.removeChild(this._menuChildren[_8.id]); -this._menuChildren[_8.id].destroy(); -delete this._menuChildren[_8.id]; -} -this.inherited(arguments); -},_initButtons:function(){ -this._menuChildren={}; -this._btnWidth=0; -this._buttons=dojo.query("> .tabStripButton",this.domNode).filter(function(_b){ -if((this.useMenu&&_b==this._menuBtn.domNode)||(this.useSlider&&(_b==this._rightBtn.domNode||_b==this._leftBtn.domNode))){ -this._btnWidth+=dojo.marginBox(_b).w; -return true; -}else{ -dojo.style(_b,"display","none"); -return false; -} -},this); -if(this.useMenu){ -this._menu=new dijit.Menu({id:this.id+"_menu",dir:this.dir,lang:this.lang,targetNodeIds:[this._menuBtn.domNode],leftClickToOpen:true,refocus:false}); -this._supportingWidgets.push(this._menu); -} -},_getTabsWidth:function(){ -var _c=this.getChildren(); -if(_c.length){ -var _d=_c[this.isLeftToRight()?0:_c.length-1].domNode,_e=_c[this.isLeftToRight()?_c.length-1:0].domNode; -return _e.offsetLeft+dojo.style(_e,"width")-_d.offsetLeft; -}else{ -return 0; -} -},_enableBtn:function(_f){ -var _10=this._getTabsWidth(); -_f=_f||dojo.style(this.scrollNode,"width"); -return _10>0&&_f<_10; -},resize:function(dim){ -if(this.domNode.offsetWidth==0){ -return; -} -this._dim=dim; -this.scrollNode.style.height="auto"; -this._contentBox=dijit.layout.marginBox2contentBox(this.domNode,{h:0,w:dim.w}); -this._contentBox.h=this.scrollNode.offsetHeight; -dojo.contentBox(this.domNode,this._contentBox); -var _11=this._enableBtn(this._contentBox.w); -this._buttons.style("display",_11?"":"none"); -this._leftBtn.layoutAlign="left"; -this._rightBtn.layoutAlign="right"; -this._menuBtn.layoutAlign=this.isLeftToRight()?"right":"left"; -dijit.layout.layoutChildren(this.domNode,this._contentBox,[this._menuBtn,this._leftBtn,this._rightBtn,{domNode:this.scrollNode,layoutAlign:"client"}]); -if(this._selectedTab){ -if(this._anim&&this._anim.status()=="playing"){ -this._anim.stop(); -} -var w=this.scrollNode,sl=this._convertToScrollLeft(this._getScrollForSelectedTab()); -w.scrollLeft=sl; -} -this._setButtonClass(this._getScroll()); -this._postResize=true; -},_getScroll:function(){ -var sl=(this.isLeftToRight()||dojo.isIE<8||(dojo.isIE&&dojo.isQuirks)||dojo.isWebKit)?this.scrollNode.scrollLeft:dojo.style(this.containerNode,"width")-dojo.style(this.scrollNode,"width")+(dojo.isIE==8?-1:1)*this.scrollNode.scrollLeft; -return sl; -},_convertToScrollLeft:function(val){ -if(this.isLeftToRight()||dojo.isIE<8||(dojo.isIE&&dojo.isQuirks)||dojo.isWebKit){ -return val; -}else{ -var _12=dojo.style(this.containerNode,"width")-dojo.style(this.scrollNode,"width"); -return (dojo.isIE==8?-1:1)*(val-_12); -} -},onSelectChild:function(_13){ -var tab=this.pane2button[_13.id]; -if(!tab||!_13){ -return; -} -var _14=tab.domNode; -if(this._postResize&&_14!=this._selectedTab){ -this._selectedTab=_14; -var sl=this._getScroll(); -if(sl>_14.offsetLeft||sl+dojo.style(this.scrollNode,"width")<_14.offsetLeft+dojo.style(_14,"width")){ -this.createSmoothScroll().play(); -} -} -this.inherited(arguments); -},_getScrollBounds:function(){ -var _15=this.getChildren(),_16=dojo.style(this.scrollNode,"width"),_17=dojo.style(this.containerNode,"width"),_18=_17-_16,_19=this._getTabsWidth(); -if(_15.length&&_19>_16){ -return {min:this.isLeftToRight()?0:_15[_15.length-1].domNode.offsetLeft,max:this.isLeftToRight()?(_15[_15.length-1].domNode.offsetLeft+dojo.style(_15[_15.length-1].domNode,"width"))-_16:_18}; -}else{ -var _1a=this.isLeftToRight()?0:_18; -return {min:_1a,max:_1a}; -} -},_getScrollForSelectedTab:function(){ -var w=this.scrollNode,n=this._selectedTab,_1b=dojo.style(this.scrollNode,"width"),_1c=this._getScrollBounds(); -var pos=(n.offsetLeft+dojo.style(n,"width")/2)-_1b/2; -pos=Math.min(Math.max(pos,_1c.min),_1c.max); -return pos; -},createSmoothScroll:function(x){ -if(arguments.length>0){ -var _1d=this._getScrollBounds(); -x=Math.min(Math.max(x,_1d.min),_1d.max); -}else{ -x=this._getScrollForSelectedTab(); -} -if(this._anim&&this._anim.status()=="playing"){ -this._anim.stop(); -} -var _1e=this,w=this.scrollNode,_1f=new dojo._Animation({beforeBegin:function(){ -if(this.curve){ -delete this.curve; -} -var _20=w.scrollLeft,_21=_1e._convertToScrollLeft(x); -_1f.curve=new dojo._Line(_20,_21); -},onAnimate:function(val){ -w.scrollLeft=val; -}}); -this._anim=_1f; -this._setButtonClass(x); -return _1f; -},_getBtnNode:function(e){ -var n=e.target; -while(n&&!dojo.hasClass(n,"tabStripButton")){ -n=n.parentNode; -} -return n; -},doSlideRight:function(e){ -this.doSlide(1,this._getBtnNode(e)); -},doSlideLeft:function(e){ -this.doSlide(-1,this._getBtnNode(e)); -},doSlide:function(_22,_23){ -if(_23&&dojo.hasClass(_23,"dijitTabDisabled")){ -return; -} -var _24=dojo.style(this.scrollNode,"width"); -var d=(_24*0.75)*_22; -var to=this._getScroll()+d; -this._setButtonClass(to); -this.createSmoothScroll(to).play(); -},_setButtonClass:function(_25){ -var _26=this._getScrollBounds(); -this._leftBtn.set("disabled",_25<=_26.min); -this._rightBtn.set("disabled",_25>=_26.max); -}}); -dojo.declare("dijit.layout._ScrollingTabControllerButton",dijit.form.Button,{baseClass:"dijitTab tabStripButton",templateString:dojo.cache("dijit.layout","templates/_ScrollingTabControllerButton.html","<div dojoAttachEvent=\"onclick:_onButtonClick\">\n\t<div waiRole=\"presentation\" class=\"dijitTabInnerDiv\" dojoattachpoint=\"innerDiv,focusNode\">\n\t\t<div waiRole=\"presentation\" class=\"dijitTabContent dijitButtonContents\" dojoattachpoint=\"tabContent\">\n\t\t\t<img waiRole=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t\t<span dojoAttachPoint=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>\n"),tabIndex:"-1"}); +dojo.require("dijit.form.Button"); +dojo.require("dijit._HasDropDown"); + + +dojo.declare("dijit.layout.ScrollingTabController", + dijit.layout.TabController, + { + // summary: + // Set of tabs with left/right arrow keys and a menu to switch between tabs not + // all fitting on a single row. + // Works only for horizontal tabs (either above or below the content, not to the left + // or right). + // tags: + // private + + templateString: dojo.cache("dijit.layout", "templates/ScrollingTabController.html", "<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerMenuButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\" containerId=\"${containerId}\" iconClass=\"dijitTabStripMenuIcon\"\n\t\t\tdropDownPosition=\"below-alt, above-alt\"\n\t\t\tdojoAttachPoint=\"_menuBtn\" showLabel=\"false\">▼</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\" iconClass=\"dijitTabStripSlideLeftIcon\"\n\t\t\tdojoAttachPoint=\"_leftBtn\" dojoAttachEvent=\"onClick: doSlideLeft\" showLabel=\"false\">◀</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\" iconClass=\"dijitTabStripSlideRightIcon\"\n\t\t\tdojoAttachPoint=\"_rightBtn\" dojoAttachEvent=\"onClick: doSlideRight\" showLabel=\"false\">▶</div>\n\t<div class='dijitTabListWrapper' dojoAttachPoint='tablistWrapper'>\n\t\t<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'\n\t\t\t\tdojoAttachPoint='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>\n"), + + // useMenu: [const] Boolean + // True if a menu should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useMenu: true, + + // useSlider: [const] Boolean + // True if a slider should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useSlider: true, + + // tabStripClass: [const] String + // The css class to apply to the tab strip, if it is visible. + tabStripClass: "", + + widgetsInTemplate: true, + + // _minScroll: Number + // The distance in pixels from the edge of the tab strip which, + // if a scroll animation is less than, forces the scroll to + // go all the way to the left/right. + _minScroll: 5, + + attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { + "class": "containerNode" + }), + + buildRendering: function(){ + this.inherited(arguments); + var n = this.domNode; + + this.scrollNode = this.tablistWrapper; + this._initButtons(); + + if(!this.tabStripClass){ + this.tabStripClass = "dijitTabContainer" + + this.tabPosition.charAt(0).toUpperCase() + + this.tabPosition.substr(1).replace(/-.*/, "") + + "None"; + dojo.addClass(n, "tabStrip-disabled") + } + + dojo.addClass(this.tablistWrapper, this.tabStripClass); + }, + + onStartup: function(){ + this.inherited(arguments); + + // Do not show the TabController until the related + // StackController has added it's children. This gives + // a less visually jumpy instantiation. + dojo.style(this.domNode, "visibility", "visible"); + this._postStartup = true; + }, + + onAddChild: function(page, insertIndex){ + this.inherited(arguments); + + // changes to the tab button label or iconClass will have changed the width of the + // buttons, so do a resize + dojo.forEach(["label", "iconClass"], function(attr){ + this.pane2watches[page.id].push( + this.pane2button[page.id].watch(attr, dojo.hitch(this, function(name, oldValue, newValue){ + if(this._postStartup && this._dim){ + this.resize(this._dim); + } + })) + ); + }, this); + + // Increment the width of the wrapper when a tab is added + // This makes sure that the buttons never wrap. + // The value 200 is chosen as it should be bigger than most + // Tab button widths. + dojo.style(this.containerNode, "width", + (dojo.style(this.containerNode, "width") + 200) + "px"); + }, + + onRemoveChild: function(page, insertIndex){ + // null out _selectedTab because we are about to delete that dom node + var button = this.pane2button[page.id]; + if(this._selectedTab === button.domNode){ + this._selectedTab = null; + } + + this.inherited(arguments); + }, + + _initButtons: function(){ + // summary: + // Creates the buttons used to scroll to view tabs that + // may not be visible if the TabContainer is too narrow. + + // Make a list of the buttons to display when the tab labels become + // wider than the TabContainer, and hide the other buttons. + // Also gets the total width of the displayed buttons. + this._btnWidth = 0; + this._buttons = dojo.query("> .tabStripButton", this.domNode).filter(function(btn){ + if((this.useMenu && btn == this._menuBtn.domNode) || + (this.useSlider && (btn == this._rightBtn.domNode || btn == this._leftBtn.domNode))){ + this._btnWidth += dojo._getMarginSize(btn).w; + return true; + }else{ + dojo.style(btn, "display", "none"); + return false; + } + }, this); + }, + + _getTabsWidth: function(){ + var children = this.getChildren(); + if(children.length){ + var leftTab = children[this.isLeftToRight() ? 0 : children.length - 1].domNode, + rightTab = children[this.isLeftToRight() ? children.length - 1 : 0].domNode; + return rightTab.offsetLeft + dojo.style(rightTab, "width") - leftTab.offsetLeft; + }else{ + return 0; + } + }, + + _enableBtn: function(width){ + // summary: + // Determines if the tabs are wider than the width of the TabContainer, and + // thus that we need to display left/right/menu navigation buttons. + var tabsWidth = this._getTabsWidth(); + width = width || dojo.style(this.scrollNode, "width"); + return tabsWidth > 0 && width < tabsWidth; + }, + + resize: function(dim){ + // summary: + // Hides or displays the buttons used to scroll the tab list and launch the menu + // that selects tabs. + + if(this.domNode.offsetWidth == 0){ + return; + } + + // Save the dimensions to be used when a child is renamed. + this._dim = dim; + + // Set my height to be my natural height (tall enough for one row of tab labels), + // and my content-box width based on margin-box width specified in dim parameter. + // But first reset scrollNode.height in case it was set by layoutChildren() call + // in a previous run of this method. + this.scrollNode.style.height = "auto"; + this._contentBox = dijit.layout.marginBox2contentBox(this.domNode, {h: 0, w: dim.w}); + this._contentBox.h = this.scrollNode.offsetHeight; + dojo.contentBox(this.domNode, this._contentBox); + + // Show/hide the left/right/menu navigation buttons depending on whether or not they + // are needed. + var enable = this._enableBtn(this._contentBox.w); + this._buttons.style("display", enable ? "" : "none"); + + // Position and size the navigation buttons and the tablist + this._leftBtn.layoutAlign = "left"; + this._rightBtn.layoutAlign = "right"; + this._menuBtn.layoutAlign = this.isLeftToRight() ? "right" : "left"; + dijit.layout.layoutChildren(this.domNode, this._contentBox, + [this._menuBtn, this._leftBtn, this._rightBtn, {domNode: this.scrollNode, layoutAlign: "client"}]); + + // set proper scroll so that selected tab is visible + if(this._selectedTab){ + if(this._anim && this._anim.status() == "playing"){ + this._anim.stop(); + } + var w = this.scrollNode, + sl = this._convertToScrollLeft(this._getScrollForSelectedTab()); + w.scrollLeft = sl; + } + + // Enable/disabled left right buttons depending on whether or not user can scroll to left or right + this._setButtonClass(this._getScroll()); + + this._postResize = true; + + // Return my size so layoutChildren() can use it. + // Also avoids IE9 layout glitch on browser resize when scroll buttons present + return {h: this._contentBox.h, w: dim.w}; + }, + + _getScroll: function(){ + // summary: + // Returns the current scroll of the tabs where 0 means + // "scrolled all the way to the left" and some positive number, based on # + // of pixels of possible scroll (ex: 1000) means "scrolled all the way to the right" + var sl = (this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit) ? this.scrollNode.scrollLeft : + dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width") + + (dojo.isIE == 8 ? -1 : 1) * this.scrollNode.scrollLeft; + return sl; + }, + + _convertToScrollLeft: function(val){ + // summary: + // Given a scroll value where 0 means "scrolled all the way to the left" + // and some positive number, based on # of pixels of possible scroll (ex: 1000) + // means "scrolled all the way to the right", return value to set this.scrollNode.scrollLeft + // to achieve that scroll. + // + // This method is to adjust for RTL funniness in various browsers and versions. + if(this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit){ + return val; + }else{ + var maxScroll = dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width"); + return (dojo.isIE == 8 ? -1 : 1) * (val - maxScroll); + } + }, + + onSelectChild: function(/*dijit._Widget*/ page){ + // summary: + // Smoothly scrolls to a tab when it is selected. + + var tab = this.pane2button[page.id]; + if(!tab || !page){return;} + + // Scroll to the selected tab, except on startup, when scrolling is handled in resize() + var node = tab.domNode; + if(this._postResize && node != this._selectedTab){ + this._selectedTab = node; + + var sl = this._getScroll(); + + if(sl > node.offsetLeft || + sl + dojo.style(this.scrollNode, "width") < + node.offsetLeft + dojo.style(node, "width")){ + this.createSmoothScroll().play(); + } + } + + this.inherited(arguments); + }, + + _getScrollBounds: function(){ + // summary: + // Returns the minimum and maximum scroll setting to show the leftmost and rightmost + // tabs (respectively) + var children = this.getChildren(), + scrollNodeWidth = dojo.style(this.scrollNode, "width"), // about 500px + containerWidth = dojo.style(this.containerNode, "width"), // 50,000px + maxPossibleScroll = containerWidth - scrollNodeWidth, // scrolling until right edge of containerNode visible + tabsWidth = this._getTabsWidth(); + + if(children.length && tabsWidth > scrollNodeWidth){ + // Scrolling should happen + return { + min: this.isLeftToRight() ? 0 : children[children.length-1].domNode.offsetLeft, + max: this.isLeftToRight() ? + (children[children.length-1].domNode.offsetLeft + dojo.style(children[children.length-1].domNode, "width")) - scrollNodeWidth : + maxPossibleScroll + }; + }else{ + // No scrolling needed, all tabs visible, we stay either scrolled to far left or far right (depending on dir) + var onlyScrollPosition = this.isLeftToRight() ? 0 : maxPossibleScroll; + return { + min: onlyScrollPosition, + max: onlyScrollPosition + }; + } + }, + + _getScrollForSelectedTab: function(){ + // summary: + // Returns the scroll value setting so that the selected tab + // will appear in the center + var w = this.scrollNode, + n = this._selectedTab, + scrollNodeWidth = dojo.style(this.scrollNode, "width"), + scrollBounds = this._getScrollBounds(); + + // TODO: scroll minimal amount (to either right or left) so that + // selected tab is fully visible, and just return if it's already visible? + var pos = (n.offsetLeft + dojo.style(n, "width")/2) - scrollNodeWidth/2; + pos = Math.min(Math.max(pos, scrollBounds.min), scrollBounds.max); + + // TODO: + // If scrolling close to the left side or right side, scroll + // all the way to the left or right. See this._minScroll. + // (But need to make sure that doesn't scroll the tab out of view...) + return pos; + }, + + createSmoothScroll: function(x){ + // summary: + // Creates a dojo._Animation object that smoothly scrolls the tab list + // either to a fixed horizontal pixel value, or to the selected tab. + // description: + // If an number argument is passed to the function, that horizontal + // pixel position is scrolled to. Otherwise the currently selected + // tab is scrolled to. + // x: Integer? + // An optional pixel value to scroll to, indicating distance from left. + + // Calculate position to scroll to + if(arguments.length > 0){ + // position specified by caller, just make sure it's within bounds + var scrollBounds = this._getScrollBounds(); + x = Math.min(Math.max(x, scrollBounds.min), scrollBounds.max); + }else{ + // scroll to center the current tab + x = this._getScrollForSelectedTab(); + } + + if(this._anim && this._anim.status() == "playing"){ + this._anim.stop(); + } + + var self = this, + w = this.scrollNode, + anim = new dojo._Animation({ + beforeBegin: function(){ + if(this.curve){ delete this.curve; } + var oldS = w.scrollLeft, + newS = self._convertToScrollLeft(x); + anim.curve = new dojo._Line(oldS, newS); + }, + onAnimate: function(val){ + w.scrollLeft = val; + } + }); + this._anim = anim; + + // Disable/enable left/right buttons according to new scroll position + this._setButtonClass(x); + + return anim; // dojo._Animation + }, + + _getBtnNode: function(/*Event*/ e){ + // summary: + // Gets a button DOM node from a mouse click event. + // e: + // The mouse click event. + var n = e.target; + while(n && !dojo.hasClass(n, "tabStripButton")){ + n = n.parentNode; + } + return n; + }, + + doSlideRight: function(/*Event*/ e){ + // summary: + // Scrolls the menu to the right. + // e: + // The mouse click event. + this.doSlide(1, this._getBtnNode(e)); + }, + + doSlideLeft: function(/*Event*/ e){ + // summary: + // Scrolls the menu to the left. + // e: + // The mouse click event. + this.doSlide(-1,this._getBtnNode(e)); + }, + + doSlide: function(/*Number*/ direction, /*DomNode*/ node){ + // summary: + // Scrolls the tab list to the left or right by 75% of the widget width. + // direction: + // If the direction is 1, the widget scrolls to the right, if it is + // -1, it scrolls to the left. + + if(node && dojo.hasClass(node, "dijitTabDisabled")){return;} + + var sWidth = dojo.style(this.scrollNode, "width"); + var d = (sWidth * 0.75) * direction; + + var to = this._getScroll() + d; + + this._setButtonClass(to); + + this.createSmoothScroll(to).play(); + }, + + _setButtonClass: function(/*Number*/ scroll){ + // summary: + // Disables the left scroll button if the tabs are scrolled all the way to the left, + // or the right scroll button in the opposite case. + // scroll: Integer + // amount of horizontal scroll + + var scrollBounds = this._getScrollBounds(); + this._leftBtn.set("disabled", scroll <= scrollBounds.min); + this._rightBtn.set("disabled", scroll >= scrollBounds.max); + } +}); + + +dojo.declare("dijit.layout._ScrollingTabControllerButtonMixin", null, { + baseClass: "dijitTab tabStripButton", + + templateString: dojo.cache("dijit.layout", "templates/_ScrollingTabControllerButton.html", "<div dojoAttachEvent=\"onclick:_onButtonClick\">\n\t<div role=\"presentation\" class=\"dijitTabInnerDiv\" dojoattachpoint=\"innerDiv,focusNode\">\n\t\t<div role=\"presentation\" class=\"dijitTabContent dijitButtonContents\" dojoattachpoint=\"tabContent\">\n\t\t\t<img role=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t\t<span dojoAttachPoint=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>\n"), + + // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be + // able to tab to the left/right/menu buttons + tabIndex: "", + + // Similarly, override FormWidget.isFocusable() because clicking a button shouldn't focus it + // either (this override avoids focus() call in FormWidget.js) + isFocusable: function(){ return false; } +}); + +dojo.declare("dijit.layout._ScrollingTabControllerButton", + [dijit.form.Button, dijit.layout._ScrollingTabControllerButtonMixin]); + +dojo.declare( + "dijit.layout._ScrollingTabControllerMenuButton", + [dijit.form.Button, dijit._HasDropDown, dijit.layout._ScrollingTabControllerButtonMixin], +{ + // id of the TabContainer itself + containerId: "", + + // -1 so user can't tab into the button, but so that button can still be focused programatically. + // Because need to move focus to the button (or somewhere) before the menu is hidden or IE6 will crash. + tabIndex: "-1", + + isLoaded: function(){ + // recreate menu every time, in case the TabContainer's list of children (or their icons/labels) have changed + return false; + }, + + loadDropDown: function(callback){ + this.dropDown = new dijit.Menu({ + id: this.containerId + "_menu", + dir: this.dir, + lang: this.lang + }); + var container = dijit.byId(this.containerId); + dojo.forEach(container.getChildren(), function(page){ + var menuItem = new dijit.MenuItem({ + id: page.id + "_stcMi", + label: page.title, + iconClass: page.iconClass, + dir: page.dir, + lang: page.lang, + onClick: function(){ + container.selectChild(page); + } + }); + this.dropDown.addChild(menuItem); + }, this); + callback(); + }, + + closeDropDown: function(/*Boolean*/ focus){ + this.inherited(arguments); + if(this.dropDown){ + this.dropDown.destroyRecursive(); + delete this.dropDown; + } + } +}); + } diff --git a/lib/dijit/layout/SplitContainer.js b/lib/dijit/layout/SplitContainer.js index b0db3e465..cb18273e4 100644 --- a/lib/dijit/layout/SplitContainer.js +++ b/lib/dijit/layout/SplitContainer.js @@ -1,348 +1,589 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.SplitContainer"]){ -dojo._hasResource["dijit.layout.SplitContainer"]=true; +if(!dojo._hasResource["dijit.layout.SplitContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.SplitContainer"] = true; dojo.provide("dijit.layout.SplitContainer"); dojo.require("dojo.cookie"); dojo.require("dijit.layout._LayoutWidget"); -dojo.declare("dijit.layout.SplitContainer",dijit.layout._LayoutWidget,{constructor:function(){ -dojo.deprecated("dijit.layout.SplitContainer is deprecated","use BorderContainer with splitter instead",2); -},activeSizing:false,sizerWidth:7,orientation:"horizontal",persist:true,baseClass:"dijitSplitContainer",postMixInProperties:function(){ -this.inherited("postMixInProperties",arguments); -this.isHorizontal=(this.orientation=="horizontal"); -},postCreate:function(){ -this.inherited(arguments); -this.sizers=[]; -if(dojo.isMozilla){ -this.domNode.style.overflow="-moz-scrollbars-none"; -} -if(typeof this.sizerWidth=="object"){ -try{ -this.sizerWidth=parseInt(this.sizerWidth.toString()); -} -catch(e){ -this.sizerWidth=7; -} -} -var _1=dojo.doc.createElement("div"); -this.virtualSizer=_1; -_1.style.position="relative"; -_1.style.zIndex=10; -_1.className=this.isHorizontal?"dijitSplitContainerVirtualSizerH":"dijitSplitContainerVirtualSizerV"; -this.domNode.appendChild(_1); -dojo.setSelectable(_1,false); -},destroy:function(){ -delete this.virtualSizer; -dojo.forEach(this._ownconnects,dojo.disconnect); -this.inherited(arguments); -},startup:function(){ -if(this._started){ -return; -} -dojo.forEach(this.getChildren(),function(_2,i,_3){ -this._setupChild(_2); -if(i<_3.length-1){ -this._addSizer(); -} -},this); -if(this.persist){ -this._restoreState(); -} -this.inherited(arguments); -},_setupChild:function(_4){ -this.inherited(arguments); -_4.domNode.style.position="absolute"; -dojo.addClass(_4.domNode,"dijitSplitPane"); -},_onSizerMouseDown:function(e){ -if(e.target.id){ -for(var i=0;i<this.sizers.length;i++){ -if(this.sizers[i].id==e.target.id){ -break; -} -} -if(i<this.sizers.length){ -this.beginSizing(e,i); -} -} -},_addSizer:function(_5){ -_5=_5===undefined?this.sizers.length:_5; -var _6=dojo.doc.createElement("div"); -_6.id=dijit.getUniqueId("dijit_layout_SplitterContainer_Splitter"); -this.sizers.splice(_5,0,_6); -this.domNode.appendChild(_6); -_6.className=this.isHorizontal?"dijitSplitContainerSizerH":"dijitSplitContainerSizerV"; -var _7=dojo.doc.createElement("div"); -_7.className="thumb"; -_6.appendChild(_7); -this.connect(_6,"onmousedown","_onSizerMouseDown"); -dojo.setSelectable(_6,false); -},removeChild:function(_8){ -if(this.sizers.length){ -var i=dojo.indexOf(this.getChildren(),_8); -if(i!=-1){ -if(i==this.sizers.length){ -i--; -} -dojo.destroy(this.sizers[i]); -this.sizers.splice(i,1); -} -} -this.inherited(arguments); -if(this._started){ -this.layout(); -} -},addChild:function(_9,_a){ -this.inherited(arguments); -if(this._started){ -var _b=this.getChildren(); -if(_b.length>1){ -this._addSizer(_a); -} -this.layout(); -} -},layout:function(){ -this.paneWidth=this._contentBox.w; -this.paneHeight=this._contentBox.h; -var _c=this.getChildren(); -if(!_c.length){ -return; -} -var _d=this.isHorizontal?this.paneWidth:this.paneHeight; -if(_c.length>1){ -_d-=this.sizerWidth*(_c.length-1); -} -var _e=0; -dojo.forEach(_c,function(_f){ -_e+=_f.sizeShare; -}); -var _10=_d/_e; -var _11=0; -dojo.forEach(_c.slice(0,_c.length-1),function(_12){ -var _13=Math.round(_10*_12.sizeShare); -_12.sizeActual=_13; -_11+=_13; -}); -_c[_c.length-1].sizeActual=_d-_11; -this._checkSizes(); -var pos=0; -var _14=_c[0].sizeActual; -this._movePanel(_c[0],pos,_14); -_c[0].position=pos; -pos+=_14; -if(!this.sizers){ -return; -} -dojo.some(_c.slice(1),function(_15,i){ -if(!this.sizers[i]){ -return true; -} -this._moveSlider(this.sizers[i],pos,this.sizerWidth); -this.sizers[i].position=pos; -pos+=this.sizerWidth; -_14=_15.sizeActual; -this._movePanel(_15,pos,_14); -_15.position=pos; -pos+=_14; -},this); -},_movePanel:function(_16,pos,_17){ -if(this.isHorizontal){ -_16.domNode.style.left=pos+"px"; -_16.domNode.style.top=0; -var box={w:_17,h:this.paneHeight}; -if(_16.resize){ -_16.resize(box); -}else{ -dojo.marginBox(_16.domNode,box); -} -}else{ -_16.domNode.style.left=0; -_16.domNode.style.top=pos+"px"; -var box={w:this.paneWidth,h:_17}; -if(_16.resize){ -_16.resize(box); -}else{ -dojo.marginBox(_16.domNode,box); -} -} -},_moveSlider:function(_18,pos,_19){ -if(this.isHorizontal){ -_18.style.left=pos+"px"; -_18.style.top=0; -dojo.marginBox(_18,{w:_19,h:this.paneHeight}); -}else{ -_18.style.left=0; -_18.style.top=pos+"px"; -dojo.marginBox(_18,{w:this.paneWidth,h:_19}); -} -},_growPane:function(_1a,_1b){ -if(_1a>0){ -if(_1b.sizeActual>_1b.sizeMin){ -if((_1b.sizeActual-_1b.sizeMin)>_1a){ -_1b.sizeActual=_1b.sizeActual-_1a; -_1a=0; -}else{ -_1a-=_1b.sizeActual-_1b.sizeMin; -_1b.sizeActual=_1b.sizeMin; -} -} -} -return _1a; -},_checkSizes:function(){ -var _1c=0; -var _1d=0; -var _1e=this.getChildren(); -dojo.forEach(_1e,function(_1f){ -_1d+=_1f.sizeActual; -_1c+=_1f.sizeMin; -}); -if(_1c<=_1d){ -var _20=0; -dojo.forEach(_1e,function(_21){ -if(_21.sizeActual<_21.sizeMin){ -_20+=_21.sizeMin-_21.sizeActual; -_21.sizeActual=_21.sizeMin; -} -}); -if(_20>0){ -var _22=this.isDraggingLeft?_1e.reverse():_1e; -dojo.forEach(_22,function(_23){ -_20=this._growPane(_20,_23); -},this); -} -}else{ -dojo.forEach(_1e,function(_24){ -_24.sizeActual=Math.round(_1d*(_24.sizeMin/_1c)); + + +// +// FIXME: make it prettier +// FIXME: active dragging upwards doesn't always shift other bars (direction calculation is wrong in this case) +// + + +dojo.declare("dijit.layout.SplitContainer", + dijit.layout._LayoutWidget, + { + // summary: + // Deprecated. Use `dijit.layout.BorderContainer` instead. + // description: + // A Container widget with sizing handles in-between each child. + // Contains multiple children widgets, all of which are displayed side by side + // (either horizontally or vertically); there's a bar between each of the children, + // and you can adjust the relative size of each child by dragging the bars. + // + // You must specify a size (width and height) for the SplitContainer. + // tags: + // deprecated + + constructor: function(){ + dojo.deprecated("dijit.layout.SplitContainer is deprecated", "use BorderContainer with splitter instead", 2.0); + }, + + // activeSizing: Boolean + // If true, the children's size changes as you drag the bar; + // otherwise, the sizes don't change until you drop the bar (by mouse-up) + activeSizing: false, + + // sizerWidth: Integer + // Size in pixels of the bar between each child + sizerWidth: 7, // FIXME: this should be a CSS attribute (at 7 because css wants it to be 7 until we fix to css) + + // orientation: String + // either 'horizontal' or vertical; indicates whether the children are + // arranged side-by-side or up/down. + orientation: 'horizontal', + + // persist: Boolean + // Save splitter positions in a cookie + persist: true, + + baseClass: "dijitSplitContainer", + + postMixInProperties: function(){ + this.inherited("postMixInProperties",arguments); + this.isHorizontal = (this.orientation == 'horizontal'); + }, + + postCreate: function(){ + this.inherited(arguments); + this.sizers = []; + + // overflow has to be explicitly hidden for splitContainers using gekko (trac #1435) + // to keep other combined css classes from inadvertantly making the overflow visible + if(dojo.isMozilla){ + this.domNode.style.overflow = '-moz-scrollbars-none'; // hidden doesn't work + } + + // create the fake dragger + if(typeof this.sizerWidth == "object"){ + try{ //FIXME: do this without a try/catch + this.sizerWidth = parseInt(this.sizerWidth.toString()); + }catch(e){ this.sizerWidth = 7; } + } + var sizer = dojo.doc.createElement('div'); + this.virtualSizer = sizer; + sizer.style.position = 'relative'; + + // #1681: work around the dreaded 'quirky percentages in IE' layout bug + // If the splitcontainer's dimensions are specified in percentages, it + // will be resized when the virtualsizer is displayed in _showSizingLine + // (typically expanding its bounds unnecessarily). This happens because + // we use position: relative for .dijitSplitContainer. + // The workaround: instead of changing the display style attribute, + // switch to changing the zIndex (bring to front/move to back) + + sizer.style.zIndex = 10; + sizer.className = this.isHorizontal ? 'dijitSplitContainerVirtualSizerH' : 'dijitSplitContainerVirtualSizerV'; + this.domNode.appendChild(sizer); + dojo.setSelectable(sizer, false); + }, + + destroy: function(){ + delete this.virtualSizer; + dojo.forEach(this._ownconnects, dojo.disconnect); + this.inherited(arguments); + }, + startup: function(){ + if(this._started){ return; } + + dojo.forEach(this.getChildren(), function(child, i, children){ + // attach the children and create the draggers + this._setupChild(child); + + if(i < children.length-1){ + this._addSizer(); + } + }, this); + + if(this.persist){ + this._restoreState(); + } + + this.inherited(arguments); + }, + + _setupChild: function(/*dijit._Widget*/ child){ + this.inherited(arguments); + child.domNode.style.position = "absolute"; + dojo.addClass(child.domNode, "dijitSplitPane"); + }, + + _onSizerMouseDown: function(e){ + if(e.target.id){ + for(var i=0;i<this.sizers.length;i++){ + if(this.sizers[i].id == e.target.id){ + break; + } + } + if(i<this.sizers.length){ + this.beginSizing(e,i); + } + } + }, + _addSizer: function(index){ + index = index === undefined ? this.sizers.length : index; + + // TODO: use a template for this!!! + var sizer = dojo.doc.createElement('div'); + sizer.id=dijit.getUniqueId('dijit_layout_SplitterContainer_Splitter'); + this.sizers.splice(index,0,sizer); + this.domNode.appendChild(sizer); + + sizer.className = this.isHorizontal ? 'dijitSplitContainerSizerH' : 'dijitSplitContainerSizerV'; + + // add the thumb div + var thumb = dojo.doc.createElement('div'); + thumb.className = 'thumb'; + sizer.appendChild(thumb); + + // FIXME: are you serious? why aren't we using mover start/stop combo? + this.connect(sizer, "onmousedown", '_onSizerMouseDown'); + + dojo.setSelectable(sizer, false); + }, + + removeChild: function(widget){ + // summary: + // Remove sizer, but only if widget is really our child and + // we have at least one sizer to throw away + if(this.sizers.length){ + var i=dojo.indexOf(this.getChildren(), widget) + if(i != -1){ + if(i == this.sizers.length){ + i--; + } + dojo.destroy(this.sizers[i]); + this.sizers.splice(i,1); + } + } + + // Remove widget and repaint + this.inherited(arguments); + if(this._started){ + this.layout(); + } + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // summary: + // Add a child widget to the container + // child: + // a widget to add + // insertIndex: + // postion in the "stack" to add the child widget + + this.inherited(arguments); + + if(this._started){ + // Do the stuff that startup() does for each widget + var children = this.getChildren(); + if(children.length > 1){ + this._addSizer(insertIndex); + } + + // and then reposition (ie, shrink) every pane to make room for the new guy + this.layout(); + } + }, + + layout: function(){ + // summary: + // Do layout of panels + + // base class defines this._contentBox on initial creation and also + // on resize + this.paneWidth = this._contentBox.w; + this.paneHeight = this._contentBox.h; + + var children = this.getChildren(); + if(!children.length){ return; } + + // + // calculate space + // + + var space = this.isHorizontal ? this.paneWidth : this.paneHeight; + if(children.length > 1){ + space -= this.sizerWidth * (children.length - 1); + } + + // + // calculate total of SizeShare values + // + var outOf = 0; + dojo.forEach(children, function(child){ + outOf += child.sizeShare; + }); + + // + // work out actual pixels per sizeshare unit + // + var pixPerUnit = space / outOf; + + // + // set the SizeActual member of each pane + // + var totalSize = 0; + dojo.forEach(children.slice(0, children.length - 1), function(child){ + var size = Math.round(pixPerUnit * child.sizeShare); + child.sizeActual = size; + totalSize += size; + }); + + children[children.length-1].sizeActual = space - totalSize; + + // + // make sure the sizes are ok + // + this._checkSizes(); + + // + // now loop, positioning each pane and letting children resize themselves + // + + var pos = 0; + var size = children[0].sizeActual; + this._movePanel(children[0], pos, size); + children[0].position = pos; + pos += size; + + // if we don't have any sizers, our layout method hasn't been called yet + // so bail until we are called..TODO: REVISIT: need to change the startup + // algorithm to guaranteed the ordering of calls to layout method + if(!this.sizers){ + return; + } + + dojo.some(children.slice(1), function(child, i){ + // error-checking + if(!this.sizers[i]){ + return true; + } + // first we position the sizing handle before this pane + this._moveSlider(this.sizers[i], pos, this.sizerWidth); + this.sizers[i].position = pos; + pos += this.sizerWidth; + + size = child.sizeActual; + this._movePanel(child, pos, size); + child.position = pos; + pos += size; + }, this); + }, + + _movePanel: function(panel, pos, size){ + if(this.isHorizontal){ + panel.domNode.style.left = pos + 'px'; // TODO: resize() takes l and t parameters too, don't need to set manually + panel.domNode.style.top = 0; + var box = {w: size, h: this.paneHeight}; + if(panel.resize){ + panel.resize(box); + }else{ + dojo.marginBox(panel.domNode, box); + } + }else{ + panel.domNode.style.left = 0; // TODO: resize() takes l and t parameters too, don't need to set manually + panel.domNode.style.top = pos + 'px'; + var box = {w: this.paneWidth, h: size}; + if(panel.resize){ + panel.resize(box); + }else{ + dojo.marginBox(panel.domNode, box); + } + } + }, + + _moveSlider: function(slider, pos, size){ + if(this.isHorizontal){ + slider.style.left = pos + 'px'; + slider.style.top = 0; + dojo.marginBox(slider, { w: size, h: this.paneHeight }); + }else{ + slider.style.left = 0; + slider.style.top = pos + 'px'; + dojo.marginBox(slider, { w: this.paneWidth, h: size }); + } + }, + + _growPane: function(growth, pane){ + if(growth > 0){ + if(pane.sizeActual > pane.sizeMin){ + if((pane.sizeActual - pane.sizeMin) > growth){ + + // stick all the growth in this pane + pane.sizeActual = pane.sizeActual - growth; + growth = 0; + }else{ + // put as much growth in here as we can + growth -= pane.sizeActual - pane.sizeMin; + pane.sizeActual = pane.sizeMin; + } + } + } + return growth; + }, + + _checkSizes: function(){ + + var totalMinSize = 0; + var totalSize = 0; + var children = this.getChildren(); + + dojo.forEach(children, function(child){ + totalSize += child.sizeActual; + totalMinSize += child.sizeMin; + }); + + // only make adjustments if we have enough space for all the minimums + + if(totalMinSize <= totalSize){ + + var growth = 0; + + dojo.forEach(children, function(child){ + if(child.sizeActual < child.sizeMin){ + growth += child.sizeMin - child.sizeActual; + child.sizeActual = child.sizeMin; + } + }); + + if(growth > 0){ + var list = this.isDraggingLeft ? children.reverse() : children; + dojo.forEach(list, function(child){ + growth = this._growPane(growth, child); + }, this); + } + }else{ + dojo.forEach(children, function(child){ + child.sizeActual = Math.round(totalSize * (child.sizeMin / totalMinSize)); + }); + } + }, + + beginSizing: function(e, i){ + var children = this.getChildren(); + this.paneBefore = children[i]; + this.paneAfter = children[i+1]; + + this.isSizing = true; + this.sizingSplitter = this.sizers[i]; + + if(!this.cover){ + this.cover = dojo.create('div', { + style: { + position:'absolute', + zIndex:5, + top: 0, + left: 0, + width: "100%", + height: "100%" + } + }, this.domNode); + }else{ + this.cover.style.zIndex = 5; + } + this.sizingSplitter.style.zIndex = 6; + + // TODO: REVISIT - we want MARGIN_BOX and core hasn't exposed that yet (but can't we use it anyway if we pay attention? we do elsewhere.) + this.originPos = dojo.position(children[0].domNode, true); + if(this.isHorizontal){ + var client = e.layerX || e.offsetX || 0; + var screen = e.pageX; + this.originPos = this.originPos.x; + }else{ + var client = e.layerY || e.offsetY || 0; + var screen = e.pageY; + this.originPos = this.originPos.y; + } + this.startPoint = this.lastPoint = screen; + this.screenToClientOffset = screen - client; + this.dragOffset = this.lastPoint - this.paneBefore.sizeActual - this.originPos - this.paneBefore.position; + + if(!this.activeSizing){ + this._showSizingLine(); + } + + // + // attach mouse events + // + this._ownconnects = []; + this._ownconnects.push(dojo.connect(dojo.doc.documentElement, "onmousemove", this, "changeSizing")); + this._ownconnects.push(dojo.connect(dojo.doc.documentElement, "onmouseup", this, "endSizing")); + + dojo.stopEvent(e); + }, + + changeSizing: function(e){ + if(!this.isSizing){ return; } + this.lastPoint = this.isHorizontal ? e.pageX : e.pageY; + this.movePoint(); + if(this.activeSizing){ + this._updateSize(); + }else{ + this._moveSizingLine(); + } + dojo.stopEvent(e); + }, + + endSizing: function(e){ + if(!this.isSizing){ return; } + if(this.cover){ + this.cover.style.zIndex = -1; + } + if(!this.activeSizing){ + this._hideSizingLine(); + } + + this._updateSize(); + + this.isSizing = false; + + if(this.persist){ + this._saveState(this); + } + + dojo.forEach(this._ownconnects, dojo.disconnect); + }, + + movePoint: function(){ + + // make sure lastPoint is a legal point to drag to + var p = this.lastPoint - this.screenToClientOffset; + + var a = p - this.dragOffset; + a = this.legaliseSplitPoint(a); + p = a + this.dragOffset; + + this.lastPoint = p + this.screenToClientOffset; + }, + + legaliseSplitPoint: function(a){ + + a += this.sizingSplitter.position; + + this.isDraggingLeft = !!(a > 0); + + if(!this.activeSizing){ + var min = this.paneBefore.position + this.paneBefore.sizeMin; + if(a < min){ + a = min; + } + + var max = this.paneAfter.position + (this.paneAfter.sizeActual - (this.sizerWidth + this.paneAfter.sizeMin)); + if(a > max){ + a = max; + } + } + + a -= this.sizingSplitter.position; + + this._checkSizes(); + + return a; + }, + + _updateSize: function(){ + //FIXME: sometimes this.lastPoint is NaN + var pos = this.lastPoint - this.dragOffset - this.originPos; + + var start_region = this.paneBefore.position; + var end_region = this.paneAfter.position + this.paneAfter.sizeActual; + + this.paneBefore.sizeActual = pos - start_region; + this.paneAfter.position = pos + this.sizerWidth; + this.paneAfter.sizeActual = end_region - this.paneAfter.position; + + dojo.forEach(this.getChildren(), function(child){ + child.sizeShare = child.sizeActual; + }); + + if(this._started){ + this.layout(); + } + }, + + _showSizingLine: function(){ + + this._moveSizingLine(); + + dojo.marginBox(this.virtualSizer, + this.isHorizontal ? { w: this.sizerWidth, h: this.paneHeight } : { w: this.paneWidth, h: this.sizerWidth }); + + this.virtualSizer.style.display = 'block'; + }, + + _hideSizingLine: function(){ + this.virtualSizer.style.display = 'none'; + }, + + _moveSizingLine: function(){ + var pos = (this.lastPoint - this.startPoint) + this.sizingSplitter.position; + dojo.style(this.virtualSizer,(this.isHorizontal ? "left" : "top"),pos+"px"); + // this.virtualSizer.style[ this.isHorizontal ? "left" : "top" ] = pos + 'px'; // FIXME: remove this line if the previous is better + }, + + _getCookieName: function(i){ + return this.id + "_" + i; + }, + + _restoreState: function(){ + dojo.forEach(this.getChildren(), function(child, i){ + var cookieName = this._getCookieName(i); + var cookieValue = dojo.cookie(cookieName); + if(cookieValue){ + var pos = parseInt(cookieValue); + if(typeof pos == "number"){ + child.sizeShare = pos; + } + } + }, this); + }, + + _saveState: function(){ + if(!this.persist){ + return; + } + dojo.forEach(this.getChildren(), function(child, i){ + dojo.cookie(this._getCookieName(i), child.sizeShare, {expires:365}); + }, this); + } }); -} -},beginSizing:function(e,i){ -var _25=this.getChildren(); -this.paneBefore=_25[i]; -this.paneAfter=_25[i+1]; -this.isSizing=true; -this.sizingSplitter=this.sizers[i]; -if(!this.cover){ -this.cover=dojo.create("div",{style:{position:"absolute",zIndex:5,top:0,left:0,width:"100%",height:"100%"}},this.domNode); -}else{ -this.cover.style.zIndex=5; -} -this.sizingSplitter.style.zIndex=6; -this.originPos=dojo.position(_25[0].domNode,true); -if(this.isHorizontal){ -var _26=e.layerX||e.offsetX||0; -var _27=e.pageX; -this.originPos=this.originPos.x; -}else{ -var _26=e.layerY||e.offsetY||0; -var _27=e.pageY; -this.originPos=this.originPos.y; -} -this.startPoint=this.lastPoint=_27; -this.screenToClientOffset=_27-_26; -this.dragOffset=this.lastPoint-this.paneBefore.sizeActual-this.originPos-this.paneBefore.position; -if(!this.activeSizing){ -this._showSizingLine(); -} -this._ownconnects=[]; -this._ownconnects.push(dojo.connect(dojo.doc.documentElement,"onmousemove",this,"changeSizing")); -this._ownconnects.push(dojo.connect(dojo.doc.documentElement,"onmouseup",this,"endSizing")); -dojo.stopEvent(e); -},changeSizing:function(e){ -if(!this.isSizing){ -return; -} -this.lastPoint=this.isHorizontal?e.pageX:e.pageY; -this.movePoint(); -if(this.activeSizing){ -this._updateSize(); -}else{ -this._moveSizingLine(); -} -dojo.stopEvent(e); -},endSizing:function(e){ -if(!this.isSizing){ -return; -} -if(this.cover){ -this.cover.style.zIndex=-1; -} -if(!this.activeSizing){ -this._hideSizingLine(); -} -this._updateSize(); -this.isSizing=false; -if(this.persist){ -this._saveState(this); -} -dojo.forEach(this._ownconnects,dojo.disconnect); -},movePoint:function(){ -var p=this.lastPoint-this.screenToClientOffset; -var a=p-this.dragOffset; -a=this.legaliseSplitPoint(a); -p=a+this.dragOffset; -this.lastPoint=p+this.screenToClientOffset; -},legaliseSplitPoint:function(a){ -a+=this.sizingSplitter.position; -this.isDraggingLeft=!!(a>0); -if(!this.activeSizing){ -var min=this.paneBefore.position+this.paneBefore.sizeMin; -if(a<min){ -a=min; -} -var max=this.paneAfter.position+(this.paneAfter.sizeActual-(this.sizerWidth+this.paneAfter.sizeMin)); -if(a>max){ -a=max; -} -} -a-=this.sizingSplitter.position; -this._checkSizes(); -return a; -},_updateSize:function(){ -var pos=this.lastPoint-this.dragOffset-this.originPos; -var _28=this.paneBefore.position; -var _29=this.paneAfter.position+this.paneAfter.sizeActual; -this.paneBefore.sizeActual=pos-_28; -this.paneAfter.position=pos+this.sizerWidth; -this.paneAfter.sizeActual=_29-this.paneAfter.position; -dojo.forEach(this.getChildren(),function(_2a){ -_2a.sizeShare=_2a.sizeActual; + +// These arguments can be specified for the children of a SplitContainer. +// Since any widget can be specified as a SplitContainer child, mix them +// into the base widget class. (This is a hack, but it's effective.) +dojo.extend(dijit._Widget, { + // sizeMin: [deprecated] Integer + // Deprecated. Parameter for children of `dijit.layout.SplitContainer`. + // Minimum size (width or height) of a child of a SplitContainer. + // The value is relative to other children's sizeShare properties. + sizeMin: 10, + + // sizeShare: [deprecated] Integer + // Deprecated. Parameter for children of `dijit.layout.SplitContainer`. + // Size (width or height) of a child of a SplitContainer. + // The value is relative to other children's sizeShare properties. + // For example, if there are two children and each has sizeShare=10, then + // each takes up 50% of the available space. + sizeShare: 10 }); -if(this._started){ -this.layout(); -} -},_showSizingLine:function(){ -this._moveSizingLine(); -dojo.marginBox(this.virtualSizer,this.isHorizontal?{w:this.sizerWidth,h:this.paneHeight}:{w:this.paneWidth,h:this.sizerWidth}); -this.virtualSizer.style.display="block"; -},_hideSizingLine:function(){ -this.virtualSizer.style.display="none"; -},_moveSizingLine:function(){ -var pos=(this.lastPoint-this.startPoint)+this.sizingSplitter.position; -dojo.style(this.virtualSizer,(this.isHorizontal?"left":"top"),pos+"px"); -},_getCookieName:function(i){ -return this.id+"_"+i; -},_restoreState:function(){ -dojo.forEach(this.getChildren(),function(_2b,i){ -var _2c=this._getCookieName(i); -var _2d=dojo.cookie(_2c); -if(_2d){ -var pos=parseInt(_2d); -if(typeof pos=="number"){ -_2b.sizeShare=pos; -} -} -},this); -},_saveState:function(){ -if(!this.persist){ -return; -} -dojo.forEach(this.getChildren(),function(_2e,i){ -dojo.cookie(this._getCookieName(i),_2e.sizeShare,{expires:365}); -},this); -}}); -dojo.extend(dijit._Widget,{sizeMin:10,sizeShare:10}); + } diff --git a/lib/dijit/layout/StackContainer.js b/lib/dijit/layout/StackContainer.js index 78a49021d..98af4c942 100644 --- a/lib/dijit/layout/StackContainer.js +++ b/lib/dijit/layout/StackContainer.js @@ -1,148 +1,336 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.StackContainer"]){ -dojo._hasResource["dijit.layout.StackContainer"]=true; +if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.StackContainer"] = true; dojo.provide("dijit.layout.StackContainer"); dojo.require("dijit._Templated"); dojo.require("dijit.layout._LayoutWidget"); -dojo.requireLocalization("dijit","common",null,"ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); +dojo.requireLocalization("dijit", "common", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); dojo.require("dojo.cookie"); -dojo.declare("dijit.layout.StackContainer",dijit.layout._LayoutWidget,{doLayout:true,persist:false,baseClass:"dijitStackContainer",postCreate:function(){ -this.inherited(arguments); -dojo.addClass(this.domNode,"dijitLayoutContainer"); -dijit.setWaiRole(this.containerNode,"tabpanel"); -this.connect(this.domNode,"onkeypress",this._onKeyPress); -},startup:function(){ -if(this._started){ -return; -} -var _1=this.getChildren(); -dojo.forEach(_1,this._setupChild,this); -if(this.persist){ -this.selectedChildWidget=dijit.byId(dojo.cookie(this.id+"_selectedChild")); -}else{ -dojo.some(_1,function(_2){ -if(_2.selected){ -this.selectedChildWidget=_2; -} -return _2.selected; -},this); -} -var _3=this.selectedChildWidget; -if(!_3&&_1[0]){ -_3=this.selectedChildWidget=_1[0]; -_3.selected=true; -} -dojo.publish(this.id+"-startup",[{children:_1,selected:_3}]); -this.inherited(arguments); -},resize:function(){ -var _4=this.selectedChildWidget; -if(_4&&!this._hasBeenShown){ -this._hasBeenShown=true; -this._showChild(_4); -} -this.inherited(arguments); -},_setupChild:function(_5){ -this.inherited(arguments); -dojo.removeClass(_5.domNode,"dijitVisible"); -dojo.addClass(_5.domNode,"dijitHidden"); -_5.domNode.title=""; -},addChild:function(_6,_7){ -this.inherited(arguments); -if(this._started){ -dojo.publish(this.id+"-addChild",[_6,_7]); -this.layout(); -if(!this.selectedChildWidget){ -this.selectChild(_6); -} -} -},removeChild:function(_8){ -this.inherited(arguments); -if(this._started){ -dojo.publish(this.id+"-removeChild",[_8]); -} -if(this._beingDestroyed){ -return; -} -if(this.selectedChildWidget===_8){ -this.selectedChildWidget=undefined; -if(this._started){ -var _9=this.getChildren(); -if(_9.length){ -this.selectChild(_9[0]); -} -} -} -if(this._started){ -this.layout(); -} -},selectChild:function(_a,_b){ -_a=dijit.byId(_a); -if(this.selectedChildWidget!=_a){ -this._transition(_a,this.selectedChildWidget,_b); -this.selectedChildWidget=_a; -dojo.publish(this.id+"-selectChild",[_a]); -if(this.persist){ -dojo.cookie(this.id+"_selectedChild",this.selectedChildWidget.id); -} -} -},_transition:function(_c,_d){ -if(_d){ -this._hideChild(_d); -} -this._showChild(_c); -if(_c.resize){ -if(this.doLayout){ -_c.resize(this._containerContentBox||this._contentBox); -}else{ -_c.resize(); -} -} -},_adjacent:function(_e){ -var _f=this.getChildren(); -var _10=dojo.indexOf(_f,this.selectedChildWidget); -_10+=_e?1:_f.length-1; -return _f[_10%_f.length]; -},forward:function(){ -this.selectChild(this._adjacent(true),true); -},back:function(){ -this.selectChild(this._adjacent(false),true); -},_onKeyPress:function(e){ -dojo.publish(this.id+"-containerKeyPress",[{e:e,page:this}]); -},layout:function(){ -if(this.doLayout&&this.selectedChildWidget&&this.selectedChildWidget.resize){ -this.selectedChildWidget.resize(this._containerContentBox||this._contentBox); -} -},_showChild:function(_11){ -var _12=this.getChildren(); -_11.isFirstChild=(_11==_12[0]); -_11.isLastChild=(_11==_12[_12.length-1]); -_11.selected=true; -dojo.removeClass(_11.domNode,"dijitHidden"); -dojo.addClass(_11.domNode,"dijitVisible"); -_11._onShow(); -},_hideChild:function(_13){ -_13.selected=false; -dojo.removeClass(_13.domNode,"dijitVisible"); -dojo.addClass(_13.domNode,"dijitHidden"); -_13.onHide(); -},closeChild:function(_14){ -var _15=_14.onClose(this,_14); -if(_15){ -this.removeChild(_14); -_14.destroyRecursive(); -} -},destroyDescendants:function(_16){ -dojo.forEach(this.getChildren(),function(_17){ -this.removeChild(_17); -_17.destroyRecursive(_16); -},this); -}}); dojo.require("dijit.layout.StackController"); -dojo.extend(dijit._Widget,{selected:false,closable:false,iconClass:"",showTitle:true}); + + +dojo.declare( + "dijit.layout.StackContainer", + dijit.layout._LayoutWidget, + { + // summary: + // A container that has multiple children, but shows only + // one child at a time + // + // description: + // A container for widgets (ContentPanes, for example) That displays + // only one Widget at a time. + // + // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild + // + // Can be base class for container, Wizard, Show, etc. + + // doLayout: Boolean + // If true, change the size of my currently displayed child to match my size + doLayout: true, + + // persist: Boolean + // Remembers the selected child across sessions + persist: false, + + baseClass: "dijitStackContainer", + +/*===== + // selectedChildWidget: [readonly] dijit._Widget + // References the currently selected child widget, if any. + // Adjust selected child with selectChild() method. + selectedChildWidget: null, +=====*/ + + buildRendering: function(){ + this.inherited(arguments); + dojo.addClass(this.domNode, "dijitLayoutContainer"); + dijit.setWaiRole(this.containerNode, "tabpanel"); + }, + + postCreate: function(){ + this.inherited(arguments); + this.connect(this.domNode, "onkeypress", this._onKeyPress); + }, + + startup: function(){ + if(this._started){ return; } + + var children = this.getChildren(); + + // Setup each page panel to be initially hidden + dojo.forEach(children, this._setupChild, this); + + // Figure out which child to initially display, defaulting to first one + if(this.persist){ + this.selectedChildWidget = dijit.byId(dojo.cookie(this.id + "_selectedChild")); + }else{ + dojo.some(children, function(child){ + if(child.selected){ + this.selectedChildWidget = child; + } + return child.selected; + }, this); + } + var selected = this.selectedChildWidget; + if(!selected && children[0]){ + selected = this.selectedChildWidget = children[0]; + selected.selected = true; + } + + // Publish information about myself so any StackControllers can initialize. + // This needs to happen before this.inherited(arguments) so that for + // TabContainer, this._contentBox doesn't include the space for the tab labels. + dojo.publish(this.id+"-startup", [{children: children, selected: selected}]); + + // Startup each child widget, and do initial layout like setting this._contentBox, + // then calls this.resize() which does the initial sizing on the selected child. + this.inherited(arguments); + }, + + resize: function(){ + // Resize is called when we are first made visible (it's called from startup() + // if we are initially visible). If this is the first time we've been made + // visible then show our first child. + var selected = this.selectedChildWidget; + if(selected && !this._hasBeenShown){ + this._hasBeenShown = true; + this._showChild(selected); + } + this.inherited(arguments); + }, + + _setupChild: function(/*dijit._Widget*/ child){ + // Overrides _LayoutWidget._setupChild() + + this.inherited(arguments); + + dojo.replaceClass(child.domNode, "dijitHidden", "dijitVisible"); + + // remove the title attribute so it doesn't show up when i hover + // over a node + child.domNode.title = ""; + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // Overrides _Container.addChild() to do layout and publish events + + this.inherited(arguments); + + if(this._started){ + dojo.publish(this.id+"-addChild", [child, insertIndex]); + + // in case the tab titles have overflowed from one line to two lines + // (or, if this if first child, from zero lines to one line) + // TODO: w/ScrollingTabController this is no longer necessary, although + // ScrollTabController.resize() does need to get called to show/hide + // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild() + this.layout(); + + // if this is the first child, then select it + if(!this.selectedChildWidget){ + this.selectChild(child); + } + } + }, + + removeChild: function(/*dijit._Widget*/ page){ + // Overrides _Container.removeChild() to do layout and publish events + + this.inherited(arguments); + + if(this._started){ + // this will notify any tablists to remove a button; do this first because it may affect sizing + dojo.publish(this.id + "-removeChild", [page]); + } + + // If we are being destroyed than don't run the code below (to select another page), because we are deleting + // every page one by one + if(this._beingDestroyed){ return; } + + // Select new page to display, also updating TabController to show the respective tab. + // Do this before layout call because it can affect the height of the TabController. + if(this.selectedChildWidget === page){ + this.selectedChildWidget = undefined; + if(this._started){ + var children = this.getChildren(); + if(children.length){ + this.selectChild(children[0]); + } + } + } + + if(this._started){ + // In case the tab titles now take up one line instead of two lines + // (note though that ScrollingTabController never overflows to multiple lines), + // or the height has changed slightly because of addition/removal of tab which close icon + this.layout(); + } + }, + + selectChild: function(/*dijit._Widget|String*/ page, /*Boolean*/ animate){ + // summary: + // Show the given widget (which must be one of my children) + // page: + // Reference to child widget or id of child widget + + page = dijit.byId(page); + + if(this.selectedChildWidget != page){ + // Deselect old page and select new one + var d = this._transition(page, this.selectedChildWidget, animate); + this._set("selectedChildWidget", page); + dojo.publish(this.id+"-selectChild", [page]); + + if(this.persist){ + dojo.cookie(this.id + "_selectedChild", this.selectedChildWidget.id); + } + } + + return d; // If child has an href, promise that fires when the child's href finishes loading + }, + + _transition: function(/*dijit._Widget*/ newWidget, /*dijit._Widget*/ oldWidget, /*Boolean*/ animate){ + // summary: + // Hide the old widget and display the new widget. + // Subclasses should override this. + // tags: + // protected extension + if(oldWidget){ + this._hideChild(oldWidget); + } + var d = this._showChild(newWidget); + + // Size the new widget, in case this is the first time it's being shown, + // or I have been resized since the last time it was shown. + // Note that page must be visible for resizing to work. + if(newWidget.resize){ + if(this.doLayout){ + newWidget.resize(this._containerContentBox || this._contentBox); + }else{ + // the child should pick it's own size but we still need to call resize() + // (with no arguments) to let the widget lay itself out + newWidget.resize(); + } + } + + return d; // If child has an href, promise that fires when the child's href finishes loading + }, + + _adjacent: function(/*Boolean*/ forward){ + // summary: + // Gets the next/previous child widget in this container from the current selection. + var children = this.getChildren(); + var index = dojo.indexOf(children, this.selectedChildWidget); + index += forward ? 1 : children.length - 1; + return children[ index % children.length ]; // dijit._Widget + }, + + forward: function(){ + // summary: + // Advance to next page. + return this.selectChild(this._adjacent(true), true); + }, + + back: function(){ + // summary: + // Go back to previous page. + return this.selectChild(this._adjacent(false), true); + }, + + _onKeyPress: function(e){ + dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]); + }, + + layout: function(){ + // Implement _LayoutWidget.layout() virtual method. + if(this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize){ + this.selectedChildWidget.resize(this._containerContentBox || this._contentBox); + } + }, + + _showChild: function(/*dijit._Widget*/ page){ + // summary: + // Show the specified child by changing it's CSS, and call _onShow()/onShow() so + // it can do any updates it needs regarding loading href's etc. + // returns: + // Promise that fires when page has finished showing, or true if there's no href + var children = this.getChildren(); + page.isFirstChild = (page == children[0]); + page.isLastChild = (page == children[children.length-1]); + page._set("selected", true); + + dojo.replaceClass(page.domNode, "dijitVisible", "dijitHidden"); + + return page._onShow() || true; + }, + + _hideChild: function(/*dijit._Widget*/ page){ + // summary: + // Hide the specified child by changing it's CSS, and call _onHide() so + // it's notified. + page._set("selected", false); + dojo.replaceClass(page.domNode, "dijitHidden", "dijitVisible"); + + page.onHide(); + }, + + closeChild: function(/*dijit._Widget*/ page){ + // summary: + // Callback when user clicks the [X] to remove a page. + // If onClose() returns true then remove and destroy the child. + // tags: + // private + var remove = page.onClose(this, page); + if(remove){ + this.removeChild(page); + // makes sure we can clean up executeScripts in ContentPane onUnLoad + page.destroyRecursive(); + } + }, + + destroyDescendants: function(/*Boolean*/ preserveDom){ + dojo.forEach(this.getChildren(), function(child){ + this.removeChild(child); + child.destroyRecursive(preserveDom); + }, this); + } +}); + +// For back-compat, remove for 2.0 + + +// These arguments can be specified for the children of a StackContainer. +// Since any widget can be specified as a StackContainer child, mix them +// into the base widget class. (This is a hack, but it's effective.) +dojo.extend(dijit._Widget, { + // selected: Boolean + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // Specifies that this widget should be the initially displayed pane. + // Note: to change the selected child use `dijit.layout.StackContainer.selectChild` + selected: false, + + // closable: Boolean + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // True if user can close (destroy) this child, such as (for example) clicking the X on the tab. + closable: false, + + // iconClass: String + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // CSS Class specifying icon to use in label associated with this pane. + iconClass: "", + + // showTitle: Boolean + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // When true, display title of this widget as tab label etc., rather than just using + // icon specified in iconClass + showTitle: true +}); + } diff --git a/lib/dijit/layout/StackController.js b/lib/dijit/layout/StackController.js index 033dadf6f..e0d2075bb 100644 --- a/lib/dijit/layout/StackController.js +++ b/lib/dijit/layout/StackController.js @@ -1,174 +1,334 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.StackController"]){ -dojo._hasResource["dijit.layout.StackController"]=true; +if(!dojo._hasResource["dijit.layout.StackController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.StackController"] = true; dojo.provide("dijit.layout.StackController"); dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); dojo.require("dijit._Container"); dojo.require("dijit.form.ToggleButton"); -dojo.requireLocalization("dijit","common",null,"ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); -dojo.declare("dijit.layout.StackController",[dijit._Widget,dijit._Templated,dijit._Container],{templateString:"<span wairole='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>",containerId:"",buttonWidget:"dijit.layout._StackButton",postCreate:function(){ -dijit.setWaiRole(this.domNode,"tablist"); -this.pane2button={}; -this.pane2handles={}; -this.subscribe(this.containerId+"-startup","onStartup"); -this.subscribe(this.containerId+"-addChild","onAddChild"); -this.subscribe(this.containerId+"-removeChild","onRemoveChild"); -this.subscribe(this.containerId+"-selectChild","onSelectChild"); -this.subscribe(this.containerId+"-containerKeyPress","onContainerKeyPress"); -},onStartup:function(_1){ -dojo.forEach(_1.children,this.onAddChild,this); -if(_1.selected){ -this.onSelectChild(_1.selected); -} -},destroy:function(){ -for(var _2 in this.pane2button){ -this.onRemoveChild(dijit.byId(_2)); -} -this.inherited(arguments); -},onAddChild:function(_3,_4){ -var _5=dojo.getObject(this.buttonWidget); -var _6=new _5({id:this.id+"_"+_3.id,label:_3.title,dir:_3.dir,lang:_3.lang,showLabel:_3.showTitle,iconClass:_3.iconClass,closeButton:_3.closable,title:_3.tooltip}); -dijit.setWaiState(_6.focusNode,"selected","false"); -this.pane2handles[_3.id]=[this.connect(_3,"set",function(_7,_8){ -var _9={title:"label",showTitle:"showLabel",iconClass:"iconClass",closable:"closeButton",tooltip:"title"}[_7]; -if(_9){ -_6.set(_9,_8); -} -}),this.connect(_6,"onClick",dojo.hitch(this,"onButtonClick",_3)),this.connect(_6,"onClickCloseButton",dojo.hitch(this,"onCloseButtonClick",_3))]; -this.addChild(_6,_4); -this.pane2button[_3.id]=_6; -_3.controlButton=_6; -if(!this._currentChild){ -_6.focusNode.setAttribute("tabIndex","0"); -dijit.setWaiState(_6.focusNode,"selected","true"); -this._currentChild=_3; -} -if(!this.isLeftToRight()&&dojo.isIE&&this._rectifyRtlTabList){ -this._rectifyRtlTabList(); -} -},onRemoveChild:function(_a){ -if(this._currentChild===_a){ -this._currentChild=null; -} -dojo.forEach(this.pane2handles[_a.id],this.disconnect,this); -delete this.pane2handles[_a.id]; -var _b=this.pane2button[_a.id]; -if(_b){ -this.removeChild(_b); -delete this.pane2button[_a.id]; -_b.destroy(); -} -delete _a.controlButton; -},onSelectChild:function(_c){ -if(!_c){ -return; -} -if(this._currentChild){ -var _d=this.pane2button[this._currentChild.id]; -_d.set("checked",false); -dijit.setWaiState(_d.focusNode,"selected","false"); -_d.focusNode.setAttribute("tabIndex","-1"); -} -var _e=this.pane2button[_c.id]; -_e.set("checked",true); -dijit.setWaiState(_e.focusNode,"selected","true"); -this._currentChild=_c; -_e.focusNode.setAttribute("tabIndex","0"); -var _f=dijit.byId(this.containerId); -dijit.setWaiState(_f.containerNode,"labelledby",_e.id); -},onButtonClick:function(_10){ -var _11=dijit.byId(this.containerId); -_11.selectChild(_10); -},onCloseButtonClick:function(_12){ -var _13=dijit.byId(this.containerId); -_13.closeChild(_12); -if(this._currentChild){ -var b=this.pane2button[this._currentChild.id]; -if(b){ -dijit.focus(b.focusNode||b.domNode); -} -} -},adjacent:function(_14){ -if(!this.isLeftToRight()&&(!this.tabPosition||/top|bottom/.test(this.tabPosition))){ -_14=!_14; -} -var _15=this.getChildren(); -var _16=dojo.indexOf(_15,this.pane2button[this._currentChild.id]); -var _17=_14?1:_15.length-1; -return _15[(_16+_17)%_15.length]; -},onkeypress:function(e){ -if(this.disabled||e.altKey){ -return; -} -var _18=null; -if(e.ctrlKey||!e._djpage){ -var k=dojo.keys; -switch(e.charOrCode){ -case k.LEFT_ARROW: -case k.UP_ARROW: -if(!e._djpage){ -_18=false; -} -break; -case k.PAGE_UP: -if(e.ctrlKey){ -_18=false; -} -break; -case k.RIGHT_ARROW: -case k.DOWN_ARROW: -if(!e._djpage){ -_18=true; -} -break; -case k.PAGE_DOWN: -if(e.ctrlKey){ -_18=true; -} -break; -case k.DELETE: -if(this._currentChild.closable){ -this.onCloseButtonClick(this._currentChild); -} -dojo.stopEvent(e); -break; -default: -if(e.ctrlKey){ -if(e.charOrCode===k.TAB){ -this.adjacent(!e.shiftKey).onClick(); -dojo.stopEvent(e); -}else{ -if(e.charOrCode=="w"){ -if(this._currentChild.closable){ -this.onCloseButtonClick(this._currentChild); -} -dojo.stopEvent(e); -} -} -} -} -if(_18!==null){ -this.adjacent(_18).onClick(); -dojo.stopEvent(e); -} -} -},onContainerKeyPress:function(_19){ -_19.e._djpage=_19.page; -this.onkeypress(_19.e); -}}); -dojo.declare("dijit.layout._StackButton",dijit.form.ToggleButton,{tabIndex:"-1",postCreate:function(evt){ -dijit.setWaiRole((this.focusNode||this.domNode),"tab"); -this.inherited(arguments); -},onClick:function(evt){ -dijit.focus(this.focusNode); -},onClickCloseButton:function(evt){ -evt.stopPropagation(); -}}); +dojo.requireLocalization("dijit", "common", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); + + +dojo.declare( + "dijit.layout.StackController", + [dijit._Widget, dijit._Templated, dijit._Container], + { + // summary: + // Set of buttons to select a page in a page list. + // description: + // Monitors the specified StackContainer, and whenever a page is + // added, deleted, or selected, updates itself accordingly. + + templateString: "<span role='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>", + + // containerId: [const] String + // The id of the page container that I point to + containerId: "", + + // buttonWidget: [const] String + // The name of the button widget to create to correspond to each page + buttonWidget: "dijit.layout._StackButton", + + constructor: function(){ + this.pane2button = {}; // mapping from pane id to buttons + this.pane2connects = {}; // mapping from pane id to this.connect() handles + this.pane2watches = {}; // mapping from pane id to watch() handles + }, + + buildRendering: function(){ + this.inherited(arguments); + dijit.setWaiRole(this.domNode, "tablist"); // TODO: unneeded? it's in template above. + }, + + postCreate: function(){ + this.inherited(arguments); + + // Listen to notifications from StackContainer + this.subscribe(this.containerId+"-startup", "onStartup"); + this.subscribe(this.containerId+"-addChild", "onAddChild"); + this.subscribe(this.containerId+"-removeChild", "onRemoveChild"); + this.subscribe(this.containerId+"-selectChild", "onSelectChild"); + this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress"); + }, + + onStartup: function(/*Object*/ info){ + // summary: + // Called after StackContainer has finished initializing + // tags: + // private + dojo.forEach(info.children, this.onAddChild, this); + if(info.selected){ + // Show button corresponding to selected pane (unless selected + // is null because there are no panes) + this.onSelectChild(info.selected); + } + }, + + destroy: function(){ + for(var pane in this.pane2button){ + this.onRemoveChild(dijit.byId(pane)); + } + this.inherited(arguments); + }, + + onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){ + // summary: + // Called whenever a page is added to the container. + // Create button corresponding to the page. + // tags: + // private + + // create an instance of the button widget + var cls = dojo.getObject(this.buttonWidget); + var button = new cls({ + id: this.id + "_" + page.id, + label: page.title, + dir: page.dir, + lang: page.lang, + showLabel: page.showTitle, + iconClass: page.iconClass, + closeButton: page.closable, + title: page.tooltip + }); + dijit.setWaiState(button.focusNode,"selected", "false"); + + + // map from page attribute to corresponding tab button attribute + var pageAttrList = ["title", "showTitle", "iconClass", "closable", "tooltip"], + buttonAttrList = ["label", "showLabel", "iconClass", "closeButton", "title"]; + + // watch() so events like page title changes are reflected in tab button + this.pane2watches[page.id] = dojo.map(pageAttrList, function(pageAttr, idx){ + return page.watch(pageAttr, function(name, oldVal, newVal){ + button.set(buttonAttrList[idx], newVal); + }); + }); + + // connections so that clicking a tab button selects the corresponding page + this.pane2connects[page.id] = [ + this.connect(button, 'onClick', dojo.hitch(this,"onButtonClick", page)), + this.connect(button, 'onClickCloseButton', dojo.hitch(this,"onCloseButtonClick", page)) + ]; + + this.addChild(button, insertIndex); + this.pane2button[page.id] = button; + page.controlButton = button; // this value might be overwritten if two tabs point to same container + if(!this._currentChild){ // put the first child into the tab order + button.focusNode.setAttribute("tabIndex", "0"); + dijit.setWaiState(button.focusNode, "selected", "true"); + this._currentChild = page; + } + // make sure all tabs have the same length + if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){ + this._rectifyRtlTabList(); + } + }, + + onRemoveChild: function(/*dijit._Widget*/ page){ + // summary: + // Called whenever a page is removed from the container. + // Remove the button corresponding to the page. + // tags: + // private + + if(this._currentChild === page){ this._currentChild = null; } + + // disconnect/unwatch connections/watches related to page being removed + dojo.forEach(this.pane2connects[page.id], dojo.hitch(this, "disconnect")); + delete this.pane2connects[page.id]; + dojo.forEach(this.pane2watches[page.id], function(w){ w.unwatch(); }); + delete this.pane2watches[page.id]; + + var button = this.pane2button[page.id]; + if(button){ + this.removeChild(button); + delete this.pane2button[page.id]; + button.destroy(); + } + delete page.controlButton; + }, + + onSelectChild: function(/*dijit._Widget*/ page){ + // summary: + // Called when a page has been selected in the StackContainer, either by me or by another StackController + // tags: + // private + + if(!page){ return; } + + if(this._currentChild){ + var oldButton=this.pane2button[this._currentChild.id]; + oldButton.set('checked', false); + dijit.setWaiState(oldButton.focusNode, "selected", "false"); + oldButton.focusNode.setAttribute("tabIndex", "-1"); + } + + var newButton=this.pane2button[page.id]; + newButton.set('checked', true); + dijit.setWaiState(newButton.focusNode, "selected", "true"); + this._currentChild = page; + newButton.focusNode.setAttribute("tabIndex", "0"); + var container = dijit.byId(this.containerId); + dijit.setWaiState(container.containerNode, "labelledby", newButton.id); + }, + + onButtonClick: function(/*dijit._Widget*/ page){ + // summary: + // Called whenever one of my child buttons is pressed in an attempt to select a page + // tags: + // private + + var container = dijit.byId(this.containerId); + container.selectChild(page); + }, + + onCloseButtonClick: function(/*dijit._Widget*/ page){ + // summary: + // Called whenever one of my child buttons [X] is pressed in an attempt to close a page + // tags: + // private + + var container = dijit.byId(this.containerId); + container.closeChild(page); + if(this._currentChild){ + var b = this.pane2button[this._currentChild.id]; + if(b){ + dijit.focus(b.focusNode || b.domNode); + } + } + }, + + // TODO: this is a bit redundant with forward, back api in StackContainer + adjacent: function(/*Boolean*/ forward){ + // summary: + // Helper for onkeypress to find next/previous button + // tags: + // private + + if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; } + // find currently focused button in children array + var children = this.getChildren(); + var current = dojo.indexOf(children, this.pane2button[this._currentChild.id]); + // pick next button to focus on + var offset = forward ? 1 : children.length - 1; + return children[ (current + offset) % children.length ]; // dijit._Widget + }, + + onkeypress: function(/*Event*/ e){ + // summary: + // Handle keystrokes on the page list, for advancing to next/previous button + // and closing the current page if the page is closable. + // tags: + // private + + if(this.disabled || e.altKey ){ return; } + var forward = null; + if(e.ctrlKey || !e._djpage){ + var k = dojo.keys; + switch(e.charOrCode){ + case k.LEFT_ARROW: + case k.UP_ARROW: + if(!e._djpage){ forward = false; } + break; + case k.PAGE_UP: + if(e.ctrlKey){ forward = false; } + break; + case k.RIGHT_ARROW: + case k.DOWN_ARROW: + if(!e._djpage){ forward = true; } + break; + case k.PAGE_DOWN: + if(e.ctrlKey){ forward = true; } + break; + case k.HOME: + case k.END: + var children = this.getChildren(); + if(children && children.length){ + children[e.charOrCode == k.HOME ? 0 : children.length-1].onClick(); + } + dojo.stopEvent(e); + break; + case k.DELETE: + if(this._currentChild.closable){ + this.onCloseButtonClick(this._currentChild); + } + dojo.stopEvent(e); + break; + default: + if(e.ctrlKey){ + if(e.charOrCode === k.TAB){ + this.adjacent(!e.shiftKey).onClick(); + dojo.stopEvent(e); + }else if(e.charOrCode == "w"){ + if(this._currentChild.closable){ + this.onCloseButtonClick(this._currentChild); + } + dojo.stopEvent(e); // avoid browser tab closing. + } + } + } + // handle next/previous page navigation (left/right arrow, etc.) + if(forward !== null){ + this.adjacent(forward).onClick(); + dojo.stopEvent(e); + } + } + }, + + onContainerKeyPress: function(/*Object*/ info){ + // summary: + // Called when there was a keypress on the container + // tags: + // private + info.e._djpage = info.page; + this.onkeypress(info.e); + } + }); + + +dojo.declare("dijit.layout._StackButton", + dijit.form.ToggleButton, + { + // summary: + // Internal widget used by StackContainer. + // description: + // The button-like or tab-like object you click to select or delete a page + // tags: + // private + + // Override _FormWidget.tabIndex. + // StackContainer buttons are not in the tab order by default. + // Probably we should be calling this.startupKeyNavChildren() instead. + tabIndex: "-1", + + buildRendering: function(/*Event*/ evt){ + this.inherited(arguments); + dijit.setWaiRole((this.focusNode || this.domNode), "tab"); + }, + + onClick: function(/*Event*/ evt){ + // summary: + // This is for TabContainer where the tabs are <span> rather than button, + // so need to set focus explicitly (on some browsers) + // Note that you shouldn't override this method, but you can connect to it. + dijit.focus(this.focusNode); + + // ... now let StackController catch the event and tell me what to do + }, + + onClickCloseButton: function(/*Event*/ evt){ + // summary: + // StackContainer connects to this function; if your widget contains a close button + // then clicking it should call this function. + // Note that you shouldn't override this method, but you can connect to it. + evt.stopPropagation(); + } + }); + } diff --git a/lib/dijit/layout/TabContainer.js b/lib/dijit/layout/TabContainer.js index d765cf7e4..009422464 100644 --- a/lib/dijit/layout/TabContainer.js +++ b/lib/dijit/layout/TabContainer.js @@ -1,23 +1,76 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.TabContainer"]){ -dojo._hasResource["dijit.layout.TabContainer"]=true; +if(!dojo._hasResource["dijit.layout.TabContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.TabContainer"] = true; dojo.provide("dijit.layout.TabContainer"); dojo.require("dijit.layout._TabContainerBase"); dojo.require("dijit.layout.TabController"); dojo.require("dijit.layout.ScrollingTabController"); -dojo.declare("dijit.layout.TabContainer",dijit.layout._TabContainerBase,{useMenu:true,useSlider:true,controllerWidget:"",_makeController:function(_1){ -var _2=this.baseClass+"-tabs"+(this.doLayout?"":" dijitTabNoLayout"),_3=dojo.getObject(this.controllerWidget); -return new _3({id:this.id+"_tablist",dir:this.dir,lang:this.lang,tabPosition:this.tabPosition,doLayout:this.doLayout,containerId:this.id,"class":_2,nested:this.nested,useMenu:this.useMenu,useSlider:this.useSlider,tabStripClass:this.tabStrip?this.baseClass+(this.tabStrip?"":"No")+"Strip":null},_1); -},postMixInProperties:function(){ -this.inherited(arguments); -if(!this.controllerWidget){ -this.controllerWidget=(this.tabPosition=="top"||this.tabPosition=="bottom")&&!this.nested?"dijit.layout.ScrollingTabController":"dijit.layout.TabController"; -} -}}); + + +dojo.declare("dijit.layout.TabContainer", + dijit.layout._TabContainerBase, + { + // summary: + // A Container with tabs to select each child (only one of which is displayed at a time). + // description: + // A TabContainer is a container that has multiple panes, but shows only + // one pane at a time. There are a set of tabs corresponding to each pane, + // where each tab has the name (aka title) of the pane, and optionally a close button. + + // useMenu: [const] Boolean + // True if a menu should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useMenu: true, + + // useSlider: [const] Boolean + // True if a slider should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useSlider: true, + + // controllerWidget: String + // An optional parameter to override the widget used to display the tab labels + controllerWidget: "", + + _makeController: function(/*DomNode*/ srcNode){ + // summary: + // Instantiate tablist controller widget and return reference to it. + // Callback from _TabContainerBase.postCreate(). + // tags: + // protected extension + + var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"), + TabController = dojo.getObject(this.controllerWidget); + + return new TabController({ + id: this.id + "_tablist", + dir: this.dir, + lang: this.lang, + tabPosition: this.tabPosition, + doLayout: this.doLayout, + containerId: this.id, + "class": cls, + nested: this.nested, + useMenu: this.useMenu, + useSlider: this.useSlider, + tabStripClass: this.tabStrip ? this.baseClass + (this.tabStrip ? "":"No") + "Strip": null + }, srcNode); + }, + + postMixInProperties: function(){ + this.inherited(arguments); + + // Scrolling controller only works for horizontal non-nested tabs + if(!this.controllerWidget){ + this.controllerWidget = (this.tabPosition == "top" || this.tabPosition == "bottom") && !this.nested ? + "dijit.layout.ScrollingTabController" : "dijit.layout.TabController"; + } + } +}); + } diff --git a/lib/dijit/layout/TabController.js b/lib/dijit/layout/TabController.js index edb3930c5..0a2276aca 100644 --- a/lib/dijit/layout/TabController.js +++ b/lib/dijit/layout/TabController.js @@ -1,77 +1,161 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout.TabController"]){ -dojo._hasResource["dijit.layout.TabController"]=true; +if(!dojo._hasResource["dijit.layout.TabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.TabController"] = true; dojo.provide("dijit.layout.TabController"); dojo.require("dijit.layout.StackController"); dojo.require("dijit.Menu"); dojo.require("dijit.MenuItem"); -dojo.requireLocalization("dijit","common",null,"ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); -dojo.declare("dijit.layout.TabController",dijit.layout.StackController,{templateString:"<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>",tabPosition:"top",buttonWidget:"dijit.layout._TabButton",_rectifyRtlTabList:function(){ -if(0>=this.tabPosition.indexOf("-h")){ -return; -} -if(!this.pane2button){ -return; -} -var _1=0; -for(var _2 in this.pane2button){ -var ow=this.pane2button[_2].innerDiv.scrollWidth; -_1=Math.max(_1,ow); -} -for(_2 in this.pane2button){ -this.pane2button[_2].innerDiv.style.width=_1+"px"; -} -}}); -dojo.declare("dijit.layout._TabButton",dijit.layout._StackButton,{baseClass:"dijitTab",cssStateNodes:{closeNode:"dijitTabCloseButton"},templateString:dojo.cache("dijit.layout","templates/_TabButton.html","<div waiRole=\"presentation\" dojoAttachPoint=\"titleNode\" dojoAttachEvent='onclick:onClick'>\n <div waiRole=\"presentation\" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'>\n <div waiRole=\"presentation\" class='dijitTabContent' dojoAttachPoint='tabContent'>\n \t<div waiRole=\"presentation\" dojoAttachPoint='focusNode'>\n\t\t <img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' />\n\t\t <span dojoAttachPoint='containerNode' class='tabLabel'></span>\n\t\t <span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" dojoAttachPoint='closeNode'\n\t\t \t\tdojoAttachEvent='onclick: onClickCloseButton' waiRole=\"presentation\">\n\t\t <span dojoAttachPoint='closeText' class='dijitTabCloseText'>x</span\n\t\t ></span>\n\t\t\t</div>\n </div>\n </div>\n</div>\n"),scrollOnFocus:false,postMixInProperties:function(){ -if(!this.iconClass){ -this.iconClass="dijitTabButtonIcon"; -} -},postCreate:function(){ -this.inherited(arguments); -dojo.setSelectable(this.containerNode,false); -if(this.iconNode.className=="dijitTabButtonIcon"){ -dojo.style(this.iconNode,"width","1px"); -} -},startup:function(){ -this.inherited(arguments); -var n=this.domNode; -setTimeout(function(){ -n.className=n.className; -},1); -},_setCloseButtonAttr:function(_3){ -this.closeButton=_3; -dojo.toggleClass(this.innerDiv,"dijitClosable",_3); -this.closeNode.style.display=_3?"":"none"; -if(_3){ -var _4=dojo.i18n.getLocalization("dijit","common"); -if(this.closeNode){ -dojo.attr(this.closeNode,"title",_4.itemClose); -} -var _4=dojo.i18n.getLocalization("dijit","common"); -this._closeMenu=new dijit.Menu({id:this.id+"_Menu",dir:this.dir,lang:this.lang,targetNodeIds:[this.domNode]}); -this._closeMenu.addChild(new dijit.MenuItem({label:_4.itemClose,dir:this.dir,lang:this.lang,onClick:dojo.hitch(this,"onClickCloseButton")})); -}else{ -if(this._closeMenu){ -this._closeMenu.destroyRecursive(); -delete this._closeMenu; -} -} -},_setLabelAttr:function(_5){ -this.inherited(arguments); -if(this.showLabel==false&&!this.params.title){ -this.iconNode.alt=dojo.trim(this.containerNode.innerText||this.containerNode.textContent||""); -} -},destroy:function(){ -if(this._closeMenu){ -this._closeMenu.destroyRecursive(); -delete this._closeMenu; -} -this.inherited(arguments); -}}); +dojo.requireLocalization("dijit", "common", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); + + +// Menu is used for an accessible close button, would be nice to have a lighter-weight solution + + +dojo.declare("dijit.layout.TabController", + dijit.layout.StackController, +{ + // summary: + // Set of tabs (the things with titles and a close button, that you click to show a tab panel). + // Used internally by `dijit.layout.TabContainer`. + // description: + // Lets the user select the currently shown pane in a TabContainer or StackContainer. + // TabController also monitors the TabContainer, and whenever a pane is + // added or deleted updates itself accordingly. + // tags: + // private + + templateString: "<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>", + + // tabPosition: String + // Defines where tabs go relative to the content. + // "top", "bottom", "left-h", "right-h" + tabPosition: "top", + + // buttonWidget: String + // The name of the tab widget to create to correspond to each page + buttonWidget: "dijit.layout._TabButton", + + _rectifyRtlTabList: function(){ + // summary: + // For left/right TabContainer when page is RTL mode, rectify the width of all tabs to be equal, otherwise the tab widths are different in IE + + if(0 >= this.tabPosition.indexOf('-h')){ return; } + if(!this.pane2button){ return; } + + var maxWidth = 0; + for(var pane in this.pane2button){ + var ow = this.pane2button[pane].innerDiv.scrollWidth; + maxWidth = Math.max(maxWidth, ow); + } + //unify the length of all the tabs + for(pane in this.pane2button){ + this.pane2button[pane].innerDiv.style.width = maxWidth + 'px'; + } + } +}); + +dojo.declare("dijit.layout._TabButton", + dijit.layout._StackButton, + { + // summary: + // A tab (the thing you click to select a pane). + // description: + // Contains the title of the pane, and optionally a close-button to destroy the pane. + // This is an internal widget and should not be instantiated directly. + // tags: + // private + + // baseClass: String + // The CSS class applied to the domNode. + baseClass: "dijitTab", + + // Apply dijitTabCloseButtonHover when close button is hovered + cssStateNodes: { + closeNode: "dijitTabCloseButton" + }, + + templateString: dojo.cache("dijit.layout", "templates/_TabButton.html", "<div role=\"presentation\" dojoAttachPoint=\"titleNode\" dojoAttachEvent='onclick:onClick'>\n <div role=\"presentation\" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'>\n <div role=\"presentation\" class='dijitTabContent' dojoAttachPoint='tabContent'>\n \t<div role=\"presentation\" dojoAttachPoint='focusNode'>\n\t\t <img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitTabButtonIcon\" dojoAttachPoint='iconNode' />\n\t\t <span dojoAttachPoint='containerNode' class='tabLabel'></span>\n\t\t <span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" dojoAttachPoint='closeNode'\n\t\t \t\tdojoAttachEvent='onclick: onClickCloseButton' role=\"presentation\">\n\t\t <span dojoAttachPoint='closeText' class='dijitTabCloseText'>[x]</span\n\t\t ></span>\n\t\t\t</div>\n </div>\n </div>\n</div>\n"), + + // Override _FormWidget.scrollOnFocus. + // Don't scroll the whole tab container into view when the button is focused. + scrollOnFocus: false, + + buildRendering: function(){ + this.inherited(arguments); + + dojo.setSelectable(this.containerNode, false); + }, + + startup: function(){ + this.inherited(arguments); + var n = this.domNode; + + // Required to give IE6 a kick, as it initially hides the + // tabs until they are focused on. + setTimeout(function(){ + n.className = n.className; + }, 1); + }, + + _setCloseButtonAttr: function(/*Boolean*/ disp){ + // summary: + // Hide/show close button + this._set("closeButton", disp); + dojo.toggleClass(this.innerDiv, "dijitClosable", disp); + this.closeNode.style.display = disp ? "" : "none"; + if(disp){ + var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); + if(this.closeNode){ + dojo.attr(this.closeNode,"title", _nlsResources.itemClose); + } + // add context menu onto title button + var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); + this._closeMenu = new dijit.Menu({ + id: this.id+"_Menu", + dir: this.dir, + lang: this.lang, + targetNodeIds: [this.domNode] + }); + + this._closeMenu.addChild(new dijit.MenuItem({ + label: _nlsResources.itemClose, + dir: this.dir, + lang: this.lang, + onClick: dojo.hitch(this, "onClickCloseButton") + })); + }else{ + if(this._closeMenu){ + this._closeMenu.destroyRecursive(); + delete this._closeMenu; + } + } + }, + _setLabelAttr: function(/*String*/ content){ + // summary: + // Hook for set('label', ...) to work. + // description: + // takes an HTML string. + // Inherited ToggleButton implementation will Set the label (text) of the button; + // Need to set the alt attribute of icon on tab buttons if no label displayed + this.inherited(arguments); + if(this.showLabel == false && !this.params.title){ + this.iconNode.alt = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || ''); + } + }, + + destroy: function(){ + if(this._closeMenu){ + this._closeMenu.destroyRecursive(); + delete this._closeMenu; + } + this.inherited(arguments); + } +}); + } diff --git a/lib/dijit/layout/_ContentPaneResizeMixin.js b/lib/dijit/layout/_ContentPaneResizeMixin.js new file mode 100644 index 000000000..fe361bee5 --- /dev/null +++ b/lib/dijit/layout/_ContentPaneResizeMixin.js @@ -0,0 +1,260 @@ +/* + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Available via Academic Free License >= 2.1 OR the modified BSD license. + see: http://dojotoolkit.org/license for details +*/ + + +if(!dojo._hasResource["dijit.layout._ContentPaneResizeMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout._ContentPaneResizeMixin"] = true; +dojo.provide("dijit.layout._ContentPaneResizeMixin"); +dojo.require("dijit._Contained"); +dojo.require("dijit.layout._LayoutWidget"); + + +dojo.declare("dijit.layout._ContentPaneResizeMixin", null, { + // summary: + // Resize() functionality of ContentPane. If there's a single layout widget + // child then it will call resize() with the same dimensions as the ContentPane. + // Otherwise just calls resize on each child. + // + // Also implements basic startup() functionality, where starting the parent + // will start the children + + // doLayout: Boolean + // - false - don't adjust size of children + // - true - if there is a single visible child widget, set it's size to + // however big the ContentPane is + doLayout: true, + + // isContainer: [protected] Boolean + // Indicates that this widget acts as a "parent" to the descendant widgets. + // When the parent is started it will call startup() on the child widgets. + // See also `isLayoutContainer`. + isContainer: true, + + // isLayoutContainer: [protected] Boolean + // Indicates that this widget will call resize() on it's child widgets + // when they become visible. + isLayoutContainer: true, + + _startChildren: function(){ + // summary: + // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects + + // This starts all the widgets + dojo.forEach(this.getChildren(), function(child){ + child.startup(); + child._started = true; + }); + }, + + startup: function(){ + // summary: + // See `dijit.layout._LayoutWidget.startup` for description. + // Although ContentPane doesn't extend _LayoutWidget, it does implement + // the same API. + + if(this._started){ return; } + + var parent = dijit._Contained.prototype.getParent.call(this); + this._childOfLayoutWidget = parent && parent.isLayoutContainer; + + // I need to call resize() on my child/children (when I become visible), unless + // I'm the child of a layout widget in which case my parent will call resize() on me and I'll do it then. + this._needLayout = !this._childOfLayoutWidget; + + this.inherited(arguments); + + this._startChildren(); + + if(this._isShown()){ + this._onShow(); + } + + if(!this._childOfLayoutWidget){ + // If my parent isn't a layout container, since my style *may be* width=height=100% + // or something similar (either set directly or via a CSS class), + // monitor when my size changes so that I can re-layout. + // For browsers where I can't directly monitor when my size changes, + // monitor when the viewport changes size, which *may* indicate a size change for me. + this.connect(dojo.isIE ? this.domNode : dojo.global, 'onresize', function(){ + // Using function(){} closure to ensure no arguments to resize. + this._needLayout = !this._childOfLayoutWidget; + this.resize(); + }); + } + }, + + _checkIfSingleChild: function(){ + // summary: + // Test if we have exactly one visible widget as a child, + // and if so assume that we are a container for that widget, + // and should propagate startup() and resize() calls to it. + // Skips over things like data stores since they aren't visible. + + var childNodes = dojo.query("> *", this.containerNode).filter(function(node){ + return node.tagName !== "SCRIPT"; // or a regexp for hidden elements like script|area|map|etc.. + }), + childWidgetNodes = childNodes.filter(function(node){ + return dojo.hasAttr(node, "data-dojo-type") || dojo.hasAttr(node, "dojoType") || dojo.hasAttr(node, "widgetId"); + }), + candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){ + return widget && widget.domNode && widget.resize; + }); + + if( + // all child nodes are widgets + childNodes.length == childWidgetNodes.length && + + // all but one are invisible (like dojo.data) + candidateWidgets.length == 1 + ){ + this._singleChild = candidateWidgets[0]; + }else{ + delete this._singleChild; + } + + // So we can set overflow: hidden to avoid a safari bug w/scrollbars showing up (#9449) + dojo.toggleClass(this.containerNode, this.baseClass + "SingleChild", !!this._singleChild); + }, + + resize: function(changeSize, resultSize){ + // summary: + // See `dijit.layout._LayoutWidget.resize` for description. + // Although ContentPane doesn't extend _LayoutWidget, it does implement + // the same API. + + // For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is + // never called, so resize() is our trigger to do the initial href download (see [20099]). + // However, don't load href for closed TitlePanes. + if(!this._wasShown && this.open !== false){ + this._onShow(); + } + + this._resizeCalled = true; + + this._scheduleLayout(changeSize, resultSize); + }, + + _scheduleLayout: function(changeSize, resultSize){ + // summary: + // Resize myself, and call resize() on each of my child layout widgets, either now + // (if I'm currently visible) or when I become visible + if(this._isShown()){ + this._layout(changeSize, resultSize); + }else{ + this._needLayout = true; + this._changeSize = changeSize; + this._resultSize = resultSize; + } + }, + + _layout: function(changeSize, resultSize){ + // summary: + // Resize myself according to optional changeSize/resultSize parameters, like a layout widget. + // Also, since I am a Container widget, each of my children expects me to + // call resize() or layout() on them. + // + // Should be called on initialization and also whenever we get new content + // (from an href, or from set('content', ...))... but deferred until + // the ContentPane is visible + + // Set margin box size, unless it wasn't specified, in which case use current size. + if(changeSize){ + dojo.marginBox(this.domNode, changeSize); + } + + // Compute content box size of containerNode in case we [later] need to size our single child. + var cn = this.containerNode; + if(cn === this.domNode){ + // If changeSize or resultSize was passed to this method and this.containerNode == + // this.domNode then we can compute the content-box size without querying the node, + // which is more reliable (similar to LayoutWidget.resize) (see for example #9449). + var mb = resultSize || {}; + dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize + if(!("h" in mb) || !("w" in mb)){ + mb = dojo.mixin(dojo.marginBox(cn), mb); // just use dojo.marginBox() to fill in missing values + } + this._contentBox = dijit.layout.marginBox2contentBox(cn, mb); + }else{ + this._contentBox = dojo.contentBox(cn); + } + + this._layoutChildren(); + + delete this._needLayout; + }, + + _layoutChildren: function(){ + // Call _checkIfSingleChild() again in case app has manually mucked w/the content + // of the ContentPane (rather than changing it through the set("content", ...) API. + if(this.doLayout){ + this._checkIfSingleChild(); + } + + if(this._singleChild && this._singleChild.resize){ + var cb = this._contentBox || dojo.contentBox(this.containerNode); + + // note: if widget has padding this._contentBox will have l and t set, + // but don't pass them to resize() or it will doubly-offset the child + this._singleChild.resize({w: cb.w, h: cb.h}); + }else{ + // All my child widgets are independently sized (rather than matching my size), + // but I still need to call resize() on each child to make it layout. + dojo.forEach(this.getChildren(), function(widget){ + if(widget.resize){ + widget.resize(); + } + }); + } + }, + + _isShown: function(){ + // summary: + // Returns true if the content is currently shown. + // description: + // If I am a child of a layout widget then it actually returns true if I've ever been visible, + // not whether I'm currently visible, since that's much faster than tracing up the DOM/widget + // tree every call, and at least solves the performance problem on page load by deferring loading + // hidden ContentPanes until they are first shown + + if(this._childOfLayoutWidget){ + // If we are TitlePane, etc - we return that only *IF* we've been resized + if(this._resizeCalled && "open" in this){ + return this.open; + } + return this._resizeCalled; + }else if("open" in this){ + return this.open; // for TitlePane, etc. + }else{ + var node = this.domNode, parent = this.domNode.parentNode; + return (node.style.display != 'none') && (node.style.visibility != 'hidden') && !dojo.hasClass(node, "dijitHidden") && + parent && parent.style && (parent.style.display != 'none'); + } + }, + + _onShow: function(){ + // summary: + // Called when the ContentPane is made visible + // description: + // For a plain ContentPane, this is called on initialization, from startup(). + // If the ContentPane is a hidden pane of a TabContainer etc., then it's + // called whenever the pane is made visible. + // + // Does layout/resize of child widget(s) + + if(this._needLayout){ + // If a layout has been scheduled for when we become visible, do it now + this._layout(this._changeSize, this._resultSize); + } + + this.inherited(arguments); + + // Need to keep track of whether ContentPane has been shown (which is different than + // whether or not it's currently visible). + this._wasShown = true; + } +}); + +} diff --git a/lib/dijit/layout/_LayoutWidget.js b/lib/dijit/layout/_LayoutWidget.js index ec7be37b7..cbbf71009 100644 --- a/lib/dijit/layout/_LayoutWidget.js +++ b/lib/dijit/layout/_LayoutWidget.js @@ -1,126 +1,313 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout._LayoutWidget"]){ -dojo._hasResource["dijit.layout._LayoutWidget"]=true; +if(!dojo._hasResource["dijit.layout._LayoutWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout._LayoutWidget"] = true; dojo.provide("dijit.layout._LayoutWidget"); dojo.require("dijit._Widget"); dojo.require("dijit._Container"); dojo.require("dijit._Contained"); -dojo.declare("dijit.layout._LayoutWidget",[dijit._Widget,dijit._Container,dijit._Contained],{baseClass:"dijitLayoutContainer",isLayoutContainer:true,postCreate:function(){ -dojo.addClass(this.domNode,"dijitContainer"); -this.inherited(arguments); -},startup:function(){ -if(this._started){ -return; -} -this.inherited(arguments); -var _1=this.getParent&&this.getParent(); -if(!(_1&&_1.isLayoutContainer)){ -this.resize(); -this.connect(dojo.isIE?this.domNode:dojo.global,"onresize",function(){ -this.resize(); -}); -} -},resize:function(_2,_3){ -var _4=this.domNode; -if(_2){ -dojo.marginBox(_4,_2); -if(_2.t){ -_4.style.top=_2.t+"px"; -} -if(_2.l){ -_4.style.left=_2.l+"px"; -} -} -var mb=_3||{}; -dojo.mixin(mb,_2||{}); -if(!("h" in mb)||!("w" in mb)){ -mb=dojo.mixin(dojo.marginBox(_4),mb); -} -var cs=dojo.getComputedStyle(_4); -var me=dojo._getMarginExtents(_4,cs); -var be=dojo._getBorderExtents(_4,cs); -var bb=(this._borderBox={w:mb.w-(me.w+be.w),h:mb.h-(me.h+be.h)}); -var pe=dojo._getPadExtents(_4,cs); -this._contentBox={l:dojo._toPixelValue(_4,cs.paddingLeft),t:dojo._toPixelValue(_4,cs.paddingTop),w:bb.w-pe.w,h:bb.h-pe.h}; -this.layout(); -},layout:function(){ -},_setupChild:function(_5){ -dojo.addClass(_5.domNode,this.baseClass+"-child"); -if(_5.baseClass){ -dojo.addClass(_5.domNode,this.baseClass+"-"+_5.baseClass); -} -},addChild:function(_6,_7){ -this.inherited(arguments); -if(this._started){ -this._setupChild(_6); -} -},removeChild:function(_8){ -dojo.removeClass(_8.domNode,this.baseClass+"-child"); -if(_8.baseClass){ -dojo.removeClass(_8.domNode,this.baseClass+"-"+_8.baseClass); -} -this.inherited(arguments); -}}); -dijit.layout.marginBox2contentBox=function(_9,mb){ -var cs=dojo.getComputedStyle(_9); -var me=dojo._getMarginExtents(_9,cs); -var pb=dojo._getPadBorderExtents(_9,cs); -return {l:dojo._toPixelValue(_9,cs.paddingLeft),t:dojo._toPixelValue(_9,cs.paddingTop),w:mb.w-(me.w+pb.w),h:mb.h-(me.h+pb.h)}; + + +dojo.declare("dijit.layout._LayoutWidget", + [dijit._Widget, dijit._Container, dijit._Contained], + { + // summary: + // Base class for a _Container widget which is responsible for laying out its children. + // Widgets which mixin this code must define layout() to manage placement and sizing of the children. + + // baseClass: [protected extension] String + // This class name is applied to the widget's domNode + // and also may be used to generate names for sub nodes, + // for example dijitTabContainer-content. + baseClass: "dijitLayoutContainer", + + // isLayoutContainer: [protected] Boolean + // Indicates that this widget is going to call resize() on its + // children widgets, setting their size, when they become visible. + isLayoutContainer: true, + + buildRendering: function(){ + this.inherited(arguments); + dojo.addClass(this.domNode, "dijitContainer"); + }, + + startup: function(){ + // summary: + // Called after all the widgets have been instantiated and their + // dom nodes have been inserted somewhere under dojo.doc.body. + // + // Widgets should override this method to do any initialization + // dependent on other widgets existing, and then call + // this superclass method to finish things off. + // + // startup() in subclasses shouldn't do anything + // size related because the size of the widget hasn't been set yet. + + if(this._started){ return; } + + // Need to call inherited first - so that child widgets get started + // up correctly + this.inherited(arguments); + + // If I am a not being controlled by a parent layout widget... + var parent = this.getParent && this.getParent() + if(!(parent && parent.isLayoutContainer)){ + // Do recursive sizing and layout of all my descendants + // (passing in no argument to resize means that it has to glean the size itself) + this.resize(); + + // Since my parent isn't a layout container, and my style *may be* width=height=100% + // or something similar (either set directly or via a CSS class), + // monitor when my size changes so that I can re-layout. + // For browsers where I can't directly monitor when my size changes, + // monitor when the viewport changes size, which *may* indicate a size change for me. + this.connect(dojo.isIE ? this.domNode : dojo.global, 'onresize', function(){ + // Using function(){} closure to ensure no arguments to resize. + this.resize(); + }); + } + }, + + resize: function(changeSize, resultSize){ + // summary: + // Call this to resize a widget, or after its size has changed. + // description: + // Change size mode: + // When changeSize is specified, changes the marginBox of this widget + // and forces it to relayout its contents accordingly. + // changeSize may specify height, width, or both. + // + // If resultSize is specified it indicates the size the widget will + // become after changeSize has been applied. + // + // Notification mode: + // When changeSize is null, indicates that the caller has already changed + // the size of the widget, or perhaps it changed because the browser + // window was resized. Tells widget to relayout its contents accordingly. + // + // If resultSize is also specified it indicates the size the widget has + // become. + // + // In either mode, this method also: + // 1. Sets this._borderBox and this._contentBox to the new size of + // the widget. Queries the current domNode size if necessary. + // 2. Calls layout() to resize contents (and maybe adjust child widgets). + // + // changeSize: Object? + // Sets the widget to this margin-box size and position. + // May include any/all of the following properties: + // | {w: int, h: int, l: int, t: int} + // + // resultSize: Object? + // The margin-box size of this widget after applying changeSize (if + // changeSize is specified). If caller knows this size and + // passes it in, we don't need to query the browser to get the size. + // | {w: int, h: int} + + var node = this.domNode; + + // set margin box size, unless it wasn't specified, in which case use current size + if(changeSize){ + dojo.marginBox(node, changeSize); + + // set offset of the node + if(changeSize.t){ node.style.top = changeSize.t + "px"; } + if(changeSize.l){ node.style.left = changeSize.l + "px"; } + } + + // If either height or width wasn't specified by the user, then query node for it. + // But note that setting the margin box and then immediately querying dimensions may return + // inaccurate results, so try not to depend on it. + var mb = resultSize || {}; + dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize + if( !("h" in mb) || !("w" in mb) ){ + mb = dojo.mixin(dojo.marginBox(node), mb); // just use dojo.marginBox() to fill in missing values + } + + // Compute and save the size of my border box and content box + // (w/out calling dojo.contentBox() since that may fail if size was recently set) + var cs = dojo.getComputedStyle(node); + var me = dojo._getMarginExtents(node, cs); + var be = dojo._getBorderExtents(node, cs); + var bb = (this._borderBox = { + w: mb.w - (me.w + be.w), + h: mb.h - (me.h + be.h) + }); + var pe = dojo._getPadExtents(node, cs); + this._contentBox = { + l: dojo._toPixelValue(node, cs.paddingLeft), + t: dojo._toPixelValue(node, cs.paddingTop), + w: bb.w - pe.w, + h: bb.h - pe.h + }; + + // Callback for widget to adjust size of its children + this.layout(); + }, + + layout: function(){ + // summary: + // Widgets override this method to size and position their contents/children. + // When this is called this._contentBox is guaranteed to be set (see resize()). + // + // This is called after startup(), and also when the widget's size has been + // changed. + // tags: + // protected extension + }, + + _setupChild: function(/*dijit._Widget*/child){ + // summary: + // Common setup for initial children and children which are added after startup + // tags: + // protected extension + + var cls = this.baseClass + "-child " + + (child.baseClass ? this.baseClass + "-" + child.baseClass : ""); + dojo.addClass(child.domNode, cls); + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // Overrides _Container.addChild() to call _setupChild() + this.inherited(arguments); + if(this._started){ + this._setupChild(child); + } + }, + + removeChild: function(/*dijit._Widget*/ child){ + // Overrides _Container.removeChild() to remove class added by _setupChild() + var cls = this.baseClass + "-child" + + (child.baseClass ? + " " + this.baseClass + "-" + child.baseClass : ""); + dojo.removeClass(child.domNode, cls); + + this.inherited(arguments); + } + } +); + +dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){ + // summary: + // Given the margin-box size of a node, return its content box size. + // Functions like dojo.contentBox() but is more reliable since it doesn't have + // to wait for the browser to compute sizes. + var cs = dojo.getComputedStyle(node); + var me = dojo._getMarginExtents(node, cs); + var pb = dojo._getPadBorderExtents(node, cs); + return { + l: dojo._toPixelValue(node, cs.paddingLeft), + t: dojo._toPixelValue(node, cs.paddingTop), + w: mb.w - (me.w + pb.w), + h: mb.h - (me.h + pb.h) + }; }; + (function(){ -var _a=function(_b){ -return _b.substring(0,1).toUpperCase()+_b.substring(1); -}; -var _c=function(_d,_e){ -_d.resize?_d.resize(_e):dojo.marginBox(_d.domNode,_e); -dojo.mixin(_d,dojo.marginBox(_d.domNode)); -dojo.mixin(_d,_e); -}; -dijit.layout.layoutChildren=function(_f,dim,_10){ -dim=dojo.mixin({},dim); -dojo.addClass(_f,"dijitLayoutContainer"); -_10=dojo.filter(_10,function(_11){ -return _11.layoutAlign!="client"; -}).concat(dojo.filter(_10,function(_12){ -return _12.layoutAlign=="client"; -})); -dojo.forEach(_10,function(_13){ -var elm=_13.domNode,pos=_13.layoutAlign; -var _14=elm.style; -_14.left=dim.l+"px"; -_14.top=dim.t+"px"; -_14.bottom=_14.right="auto"; -dojo.addClass(elm,"dijitAlign"+_a(pos)); -if(pos=="top"||pos=="bottom"){ -_c(_13,{w:dim.w}); -dim.h-=_13.h; -if(pos=="top"){ -dim.t+=_13.h; -}else{ -_14.top=dim.t+dim.h+"px"; -} -}else{ -if(pos=="left"||pos=="right"){ -_c(_13,{h:dim.h}); -dim.w-=_13.w; -if(pos=="left"){ -dim.l+=_13.w; -}else{ -_14.left=dim.l+dim.w+"px"; -} -}else{ -if(pos=="client"){ -_c(_13,dim); -} -} -} -}); -}; + var capitalize = function(word){ + return word.substring(0,1).toUpperCase() + word.substring(1); + }; + + var size = function(widget, dim){ + // size the child + var newSize = widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim); + + // record child's size + if(newSize){ + // if the child returned it's new size then use that + dojo.mixin(widget, newSize); + }else{ + // otherwise, call marginBox(), but favor our own numbers when we have them. + // the browser lies sometimes + dojo.mixin(widget, dojo.marginBox(widget.domNode)); + dojo.mixin(widget, dim); + } + }; + + dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Widget[]*/ children, + /*String?*/ changedRegionId, /*Number?*/ changedRegionSize){ + // summary + // Layout a bunch of child dom nodes within a parent dom node + // container: + // parent node + // dim: + // {l, t, w, h} object specifying dimensions of container into which to place children + // children: + // an array of Widgets or at least objects containing: + // * domNode: pointer to DOM node to position + // * region or layoutAlign: position to place DOM node + // * resize(): (optional) method to set size of node + // * id: (optional) Id of widgets, referenced from resize object, below. + // changedRegionId: + // If specified, the slider for the region with the specified id has been dragged, and thus + // the region's height or width should be adjusted according to changedRegionSize + // changedRegionSize: + // See changedRegionId. + + // copy dim because we are going to modify it + dim = dojo.mixin({}, dim); + + dojo.addClass(container, "dijitLayoutContainer"); + + // Move "client" elements to the end of the array for layout. a11y dictates that the author + // needs to be able to put them in the document in tab-order, but this algorithm requires that + // client be last. TODO: move these lines to LayoutContainer? Unneeded other places I think. + children = dojo.filter(children, function(item){ return item.region != "center" && item.layoutAlign != "client"; }) + .concat(dojo.filter(children, function(item){ return item.region == "center" || item.layoutAlign == "client"; })); + + // set positions/sizes + dojo.forEach(children, function(child){ + var elm = child.domNode, + pos = (child.region || child.layoutAlign); + + // set elem to upper left corner of unused space; may move it later + var elmStyle = elm.style; + elmStyle.left = dim.l+"px"; + elmStyle.top = dim.t+"px"; + elmStyle.position = "absolute"; + + dojo.addClass(elm, "dijitAlign" + capitalize(pos)); + + // Size adjustments to make to this child widget + var sizeSetting = {}; + + // Check for optional size adjustment due to splitter drag (height adjustment for top/bottom align + // panes and width adjustment for left/right align panes. + if(changedRegionId && changedRegionId == child.id){ + sizeSetting[child.region == "top" || child.region == "bottom" ? "h" : "w"] = changedRegionSize; + } + + // set size && adjust record of remaining space. + // note that setting the width of a <div> may affect its height. + if(pos == "top" || pos == "bottom"){ + sizeSetting.w = dim.w; + size(child, sizeSetting); + dim.h -= child.h; + if(pos == "top"){ + dim.t += child.h; + }else{ + elmStyle.top = dim.t + dim.h + "px"; + } + }else if(pos == "left" || pos == "right"){ + sizeSetting.h = dim.h; + size(child, sizeSetting); + dim.w -= child.w; + if(pos == "left"){ + dim.l += child.w; + }else{ + elmStyle.left = dim.l + dim.w + "px"; + } + }else if(pos == "client" || pos == "center"){ + size(child, dim); + } + }); + }; + })(); + } diff --git a/lib/dijit/layout/_TabContainerBase.js b/lib/dijit/layout/_TabContainerBase.js index 0ba42ec35..5e07cb653 100644 --- a/lib/dijit/layout/_TabContainerBase.js +++ b/lib/dijit/layout/_TabContainerBase.js @@ -1,68 +1,145 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.layout._TabContainerBase"]){ -dojo._hasResource["dijit.layout._TabContainerBase"]=true; +if(!dojo._hasResource["dijit.layout._TabContainerBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout._TabContainerBase"] = true; dojo.provide("dijit.layout._TabContainerBase"); dojo.require("dijit.layout.StackContainer"); dojo.require("dijit._Templated"); -dojo.declare("dijit.layout._TabContainerBase",[dijit.layout.StackContainer,dijit._Templated],{tabPosition:"top",baseClass:"dijitTabContainer",tabStrip:false,nested:false,templateString:dojo.cache("dijit.layout","templates/TabContainer.html","<div class=\"dijitTabContainer\">\n\t<div class=\"dijitTabListWrapper\" dojoAttachPoint=\"tablistNode\"></div>\n\t<div dojoAttachPoint=\"tablistSpacer\" class=\"dijitTabSpacer ${baseClass}-spacer\"></div>\n\t<div class=\"dijitTabPaneWrapper ${baseClass}-container\" dojoAttachPoint=\"containerNode\"></div>\n</div>\n"),postMixInProperties:function(){ -this.baseClass+=this.tabPosition.charAt(0).toUpperCase()+this.tabPosition.substr(1).replace(/-.*/,""); -this.srcNodeRef&&dojo.style(this.srcNodeRef,"visibility","hidden"); -this.inherited(arguments); -},postCreate:function(){ -this.inherited(arguments); -this.tablist=this._makeController(this.tablistNode); -if(!this.doLayout){ -dojo.addClass(this.domNode,"dijitTabContainerNoLayout"); -} -if(this.nested){ -dojo.addClass(this.domNode,"dijitTabContainerNested"); -dojo.addClass(this.tablist.containerNode,"dijitTabContainerTabListNested"); -dojo.addClass(this.tablistSpacer,"dijitTabContainerSpacerNested"); -dojo.addClass(this.containerNode,"dijitTabPaneWrapperNested"); -}else{ -dojo.addClass(this.domNode,"tabStrip-"+(this.tabStrip?"enabled":"disabled")); -} -},_setupChild:function(_1){ -dojo.addClass(_1.domNode,"dijitTabPane"); -this.inherited(arguments); -},startup:function(){ -if(this._started){ -return; -} -this.tablist.startup(); -this.inherited(arguments); -},layout:function(){ -if(!this._contentBox||typeof (this._contentBox.l)=="undefined"){ -return; -} -var sc=this.selectedChildWidget; -if(this.doLayout){ -var _2=this.tabPosition.replace(/-h/,""); -this.tablist.layoutAlign=_2; -var _3=[this.tablist,{domNode:this.tablistSpacer,layoutAlign:_2},{domNode:this.containerNode,layoutAlign:"client"}]; -dijit.layout.layoutChildren(this.domNode,this._contentBox,_3); -this._containerContentBox=dijit.layout.marginBox2contentBox(this.containerNode,_3[2]); -if(sc&&sc.resize){ -sc.resize(this._containerContentBox); -} -}else{ -if(this.tablist.resize){ -this.tablist.resize({w:dojo.contentBox(this.domNode).w}); -} -if(sc&&sc.resize){ -sc.resize(); -} -} -},destroy:function(){ -if(this.tablist){ -this.tablist.destroy(); -} -this.inherited(arguments); -}}); + + +dojo.declare("dijit.layout._TabContainerBase", + [dijit.layout.StackContainer, dijit._Templated], + { + // summary: + // Abstract base class for TabContainer. Must define _makeController() to instantiate + // and return the widget that displays the tab labels + // description: + // A TabContainer is a container that has multiple panes, but shows only + // one pane at a time. There are a set of tabs corresponding to each pane, + // where each tab has the name (aka title) of the pane, and optionally a close button. + + // tabPosition: String + // Defines where tabs go relative to tab content. + // "top", "bottom", "left-h", "right-h" + tabPosition: "top", + + baseClass: "dijitTabContainer", + + // tabStrip: [const] Boolean + // Defines whether the tablist gets an extra class for layouting, putting a border/shading + // around the set of tabs. Not supported by claro theme. + tabStrip: false, + + // nested: [const] Boolean + // If true, use styling for a TabContainer nested inside another TabContainer. + // For tundra etc., makes tabs look like links, and hides the outer + // border since the outer TabContainer already has a border. + nested: false, + + templateString: dojo.cache("dijit.layout", "templates/TabContainer.html", "<div class=\"dijitTabContainer\">\n\t<div class=\"dijitTabListWrapper\" dojoAttachPoint=\"tablistNode\"></div>\n\t<div dojoAttachPoint=\"tablistSpacer\" class=\"dijitTabSpacer ${baseClass}-spacer\"></div>\n\t<div class=\"dijitTabPaneWrapper ${baseClass}-container\" dojoAttachPoint=\"containerNode\"></div>\n</div>\n"), + + postMixInProperties: function(){ + // set class name according to tab position, ex: dijitTabContainerTop + this.baseClass += this.tabPosition.charAt(0).toUpperCase() + this.tabPosition.substr(1).replace(/-.*/, ""); + + this.srcNodeRef && dojo.style(this.srcNodeRef, "visibility", "hidden"); + + this.inherited(arguments); + }, + + buildRendering: function(){ + this.inherited(arguments); + + // Create the tab list that will have a tab (a.k.a. tab button) for each tab panel + this.tablist = this._makeController(this.tablistNode); + + if(!this.doLayout){ dojo.addClass(this.domNode, "dijitTabContainerNoLayout"); } + + if(this.nested){ + /* workaround IE's lack of support for "a > b" selectors by + * tagging each node in the template. + */ + dojo.addClass(this.domNode, "dijitTabContainerNested"); + dojo.addClass(this.tablist.containerNode, "dijitTabContainerTabListNested"); + dojo.addClass(this.tablistSpacer, "dijitTabContainerSpacerNested"); + dojo.addClass(this.containerNode, "dijitTabPaneWrapperNested"); + }else{ + dojo.addClass(this.domNode, "tabStrip-" + (this.tabStrip ? "enabled" : "disabled")); + } + }, + + _setupChild: function(/*dijit._Widget*/ tab){ + // Overrides StackContainer._setupChild(). + dojo.addClass(tab.domNode, "dijitTabPane"); + this.inherited(arguments); + }, + + startup: function(){ + if(this._started){ return; } + + // wire up the tablist and its tabs + this.tablist.startup(); + + this.inherited(arguments); + }, + + layout: function(){ + // Overrides StackContainer.layout(). + // Configure the content pane to take up all the space except for where the tabs are + + if(!this._contentBox || typeof(this._contentBox.l) == "undefined"){return;} + + var sc = this.selectedChildWidget; + + if(this.doLayout){ + // position and size the titles and the container node + var titleAlign = this.tabPosition.replace(/-h/, ""); + this.tablist.layoutAlign = titleAlign; + var children = [this.tablist, { + domNode: this.tablistSpacer, + layoutAlign: titleAlign + }, { + domNode: this.containerNode, + layoutAlign: "client" + }]; + dijit.layout.layoutChildren(this.domNode, this._contentBox, children); + + // Compute size to make each of my children. + // children[2] is the margin-box size of this.containerNode, set by layoutChildren() call above + this._containerContentBox = dijit.layout.marginBox2contentBox(this.containerNode, children[2]); + + if(sc && sc.resize){ + sc.resize(this._containerContentBox); + } + }else{ + // just layout the tab controller, so it can position left/right buttons etc. + if(this.tablist.resize){ + //make the tabs zero width so that they don't interfere with width calc, then reset + var s = this.tablist.domNode.style; + s.width="0"; + var width = dojo.contentBox(this.domNode).w; + s.width=""; + this.tablist.resize({w: width}); + } + + // and call resize() on the selected pane just to tell it that it's been made visible + if(sc && sc.resize){ + sc.resize(); + } + } + }, + + destroy: function(){ + if(this.tablist){ + this.tablist.destroy(); + } + this.inherited(arguments); + } +}); + } |