1 define("dijit/layout/StackContainer", [
2 "dojo/_base/array", // array.forEach array.indexOf array.some
3 "dojo/cookie", // cookie
4 "dojo/_base/declare", // declare
5 "dojo/dom-class", // domClass.add domClass.replace
6 "dojo/has", // has("dijit-legacy-requires")
7 "dojo/_base/lang", // lang.extend
9 "dojo/topic", // publish
10 "../registry", // registry.byId
13 "dojo/i18n!../nls/common"
14 ], function(array
, cookie
, declare
, domClass
, has
, lang
, ready
, topic
,
15 registry
, _WidgetBase
, _LayoutWidget
){
18 // dijit/layout/StackContainer
20 // Back compat w/1.6, remove for 2.0
21 if(has("dijit-legacy-requires")){
23 var requires
= ["dijit/layout/StackController"];
24 require(requires
); // use indirection so modules not rolled into a build
28 var StackContainer
= declare("dijit.layout.StackContainer", _LayoutWidget
, {
30 // A container that has multiple children, but shows only
31 // one child at a time
34 // A container for widgets (ContentPanes, for example) That displays
35 // only one Widget at a time.
37 // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild
39 // Can be base class for container, Wizard, Show, etc.
41 // See `StackContainer.ChildWidgetProperties` for details on the properties that can be set on
42 // children of a `StackContainer`.
45 // If true, change the size of my currently displayed child to match my size
49 // Remembers the selected child across sessions
52 baseClass
: "dijitStackContainer",
55 // selectedChildWidget: [readonly] dijit._Widget
56 // References the currently selected child widget, if any.
57 // Adjust selected child with selectChild() method.
58 selectedChildWidget: null,
61 buildRendering: function(){
62 this.inherited(arguments
);
63 domClass
.add(this.domNode
, "dijitLayoutContainer");
64 this.containerNode
.setAttribute("role", "tabpanel");
67 postCreate: function(){
68 this.inherited(arguments
);
69 this.connect(this.domNode
, "onkeypress", this._onKeyPress
);
73 if(this._started
){ return; }
75 var children
= this.getChildren();
77 // Setup each page panel to be initially hidden
78 array
.forEach(children
, this._setupChild
, this);
80 // Figure out which child to initially display, defaulting to first one
82 this.selectedChildWidget
= registry
.byId(cookie(this.id
+ "_selectedChild"));
84 array
.some(children
, function(child
){
86 this.selectedChildWidget
= child
;
88 return child
.selected
;
91 var selected
= this.selectedChildWidget
;
92 if(!selected
&& children
[0]){
93 selected
= this.selectedChildWidget
= children
[0];
94 selected
.selected
= true;
97 // Publish information about myself so any StackControllers can initialize.
98 // This needs to happen before this.inherited(arguments) so that for
99 // TabContainer, this._contentBox doesn't include the space for the tab labels.
100 topic
.publish(this.id
+"-startup", {children
: children
, selected
: selected
});
102 // Startup each child widget, and do initial layout like setting this._contentBox,
103 // then calls this.resize() which does the initial sizing on the selected child.
104 this.inherited(arguments
);
108 // Overrides _LayoutWidget.resize()
109 // Resize is called when we are first made visible (it's called from startup()
110 // if we are initially visible). If this is the first time we've been made
111 // visible then show our first child.
112 if(!this._hasBeenShown
){
113 this._hasBeenShown
= true;
114 var selected
= this.selectedChildWidget
;
116 this._showChild(selected
);
119 this.inherited(arguments
);
122 _setupChild: function(/*dijit/_WidgetBase*/ child){
123 // Overrides _LayoutWidget._setupChild()
125 this.inherited(arguments);
127 domClass.replace(child.domNode, "dijitHidden", "dijitVisible");
129 // remove the title attribute so it doesn't show up when i hover
131 child.domNode.title = "";
134 addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex
){
135 // Overrides _Container.addChild() to do layout and publish events
137 this.inherited(arguments
);
140 topic
.publish(this.id
+"-addChild", child
, insertIndex
); // publish
142 // in case the tab titles have overflowed from one line to two lines
143 // (or, if this if first child, from zero lines to one line)
144 // TODO: w/ScrollingTabController this is no longer necessary, although
145 // ScrollTabController.resize() does need to get called to show/hide
146 // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild().
147 // If this is updated to not layout [except for initial child added / last child removed], update
148 // "childless startup" test in StackContainer.html to check for no resize event after second addChild()
151 // if this is the first child, then select it
152 if(!this.selectedChildWidget
){
153 this.selectChild(child
);
158 removeChild: function(/*dijit/_WidgetBase*/ page){
159 // Overrides _Container.removeChild() to do layout and publish events
161 this.inherited(arguments);
164 // this will notify any tablists to remove a button; do this first because it may affect sizing
165 topic.publish(this.id + "-removeChild", page); // publish
168 // If all our children are being destroyed than don't run the code below (to select another page),
169 // because we are deleting every page one by one
170 if(this._descendantsBeingDestroyed){ return; }
172 // Select new page to display, also updating TabController to show the respective tab.
173 // Do this before layout call because it can affect the height of the TabController.
174 if(this.selectedChildWidget === page){
175 this.selectedChildWidget = undefined;
177 var children = this.getChildren();
179 this.selectChild(children[0]);
185 // In case the tab titles now take up one line instead of two lines
186 // (note though that ScrollingTabController never overflows to multiple lines),
187 // or the height has changed slightly because of addition/removal of tab which close icon
192 selectChild: function(/*dijit/_WidgetBase|String*/ page, /*Boolean*/ animate
){
194 // Show the given widget (which must be one of my children)
196 // Reference to child widget or id of child widget
198 page
= registry
.byId(page
);
200 if(this.selectedChildWidget
!= page
){
201 // Deselect old page and select new one
202 var d
= this._transition(page
, this.selectedChildWidget
, animate
);
203 this._set("selectedChildWidget", page
);
204 topic
.publish(this.id
+"-selectChild", page
); // publish
207 cookie(this.id
+ "_selectedChild", this.selectedChildWidget
.id
);
211 return d
; // If child has an href, promise that fires when the child's href finishes loading
214 _transition: function(newWidget
, oldWidget
/*===== , animate =====*/){
216 // Hide the old widget and display the new widget.
217 // Subclasses should override this.
218 // newWidget: dijit/_WidgetBase
219 // The newly selected widget.
220 // oldWidget: dijit/_WidgetBase
221 // The previously selected widget.
223 // Used by AccordionContainer to turn on/off slide effect.
225 // protected extension
227 this._hideChild(oldWidget
);
229 var d
= this._showChild(newWidget
);
231 // Size the new widget, in case this is the first time it's being shown,
232 // or I have been resized since the last time it was shown.
233 // Note that page must be visible for resizing to work.
234 if(newWidget
.resize
){
236 newWidget
.resize(this._containerContentBox
|| this._contentBox
);
238 // the child should pick it's own size but we still need to call resize()
239 // (with no arguments) to let the widget lay itself out
244 return d
; // If child has an href, promise that fires when the child's href finishes loading
247 _adjacent: function(/*Boolean*/ forward
){
249 // Gets the next/previous child widget in this container from the current selection.
251 // TODO: remove for 2.0 if this isn't being used. Otherwise, fix to skip disabled tabs.
253 var children
= this.getChildren();
254 var index
= array
.indexOf(children
, this.selectedChildWidget
);
255 index
+= forward
? 1 : children
.length
- 1;
256 return children
[ index
% children
.length
]; // dijit/_WidgetBase
261 // Advance to next page.
262 return this.selectChild(this._adjacent(true), true);
267 // Go back to previous page.
268 return this.selectChild(this._adjacent(false), true);
271 _onKeyPress: function(e
){
272 topic
.publish(this.id
+"-containerKeyPress", { e
: e
, page
: this}); // publish
276 // Implement _LayoutWidget.layout() virtual method.
277 var child
= this.selectedChildWidget
;
278 if(child
&& child
.resize
){
280 child
.resize(this._containerContentBox
|| this._contentBox
);
287 _showChild: function(/*dijit/_WidgetBase*/ page){
289 // Show the specified child by changing it's CSS, and call _onShow()/onShow() so
290 // it can do any updates it needs regarding loading href's etc.
292 // Promise that fires when page has finished showing, or true if there's no href
293 var children = this.getChildren();
294 page.isFirstChild = (page == children[0]);
295 page.isLastChild = (page == children[children.length-1]);
296 page._set("selected", true);
298 domClass.replace(page.domNode, "dijitVisible", "dijitHidden");
300 return (page._onShow && page._onShow()) || true;
303 _hideChild: function(/*dijit/_WidgetBase*/ page){
305 // Hide the specified child by changing it's CSS, and call _onHide() so
307 page._set("selected", false);
308 domClass.replace(page.domNode, "dijitHidden", "dijitVisible");
310 page.onHide && page.onHide();
313 closeChild: function(/*dijit/_WidgetBase*/ page){
315 // Callback when user clicks the [X] to remove a page.
316 // If onClose() returns true then remove and destroy the child.
319 var remove = page.onClose(this, page);
321 this.removeChild(page);
322 // makes sure we can clean up executeScripts in ContentPane onUnLoad
323 page.destroyRecursive();
327 destroyDescendants: function(/*Boolean*/ preserveDom
){
328 this._descendantsBeingDestroyed
= true;
329 this.selectedChildWidget
= undefined;
330 array
.forEach(this.getChildren(), function(child
){
332 this.removeChild(child
);
334 child
.destroyRecursive(preserveDom
);
336 this._descendantsBeingDestroyed
= false;
340 StackContainer
.ChildWidgetProperties
= {
342 // These properties can be specified for the children of a StackContainer.
345 // Specifies that this widget should be the initially displayed pane.
346 // Note: to change the selected child use `dijit/layout/StackContainer.selectChild`
350 // Specifies that the button to select this pane should be disabled.
351 // Doesn't affect programmatic selection of the pane, nor does it deselect the pane if it is currently selected.
355 // True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
359 // CSS Class specifying icon to use in label associated with this pane.
360 iconClass
: "dijitNoIcon",
362 // showTitle: Boolean
363 // When true, display title of this widget as tab label etc., rather than just using
364 // icon specified in iconClass
368 // Since any widget can be specified as a StackContainer child, mix them
369 // into the base widget class. (This is a hack, but it's effective.)
370 // This is for the benefit of the parser. Remove for 2.0. Also, hide from doc viewer.
371 lang
.extend(_WidgetBase
, /*===== {} || =====*/ StackContainer
.ChildWidgetProperties
);
373 return StackContainer
;