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/_base/kernel", // kernel.isAsync
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
, kernel
, lang
, ready
, topic
,
15 registry
, _WidgetBase
, _LayoutWidget
){
18 var _WidgetBase = dijit._WidgetBase;
19 var _LayoutWidget = dijit.layout._LayoutWidget;
20 var StackController = dijit.layout.StackController;
24 // dijit/layout/StackContainer
26 // A container that has multiple children, but shows only one child at a time.
28 // Back compat w/1.6, remove for 2.0
31 var requires
= ["dijit/layout/StackController"];
32 require(requires
); // use indirection so modules not rolled into a build
36 // These arguments can be specified for the children of a StackContainer.
37 // Since any widget can be specified as a StackContainer child, mix them
38 // into the base widget class. (This is a hack, but it's effective.)
39 lang
.extend(_WidgetBase
, {
41 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
42 // Specifies that this widget should be the initially displayed pane.
43 // Note: to change the selected child use `dijit.layout.StackContainer.selectChild`
47 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
48 // True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
52 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
53 // CSS Class specifying icon to use in label associated with this pane.
54 iconClass
: "dijitNoIcon",
57 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
58 // When true, display title of this widget as tab label etc., rather than just using
59 // icon specified in iconClass
63 return declare("dijit.layout.StackContainer", _LayoutWidget
, {
65 // A container that has multiple children, but shows only
66 // one child at a time
69 // A container for widgets (ContentPanes, for example) That displays
70 // only one Widget at a time.
72 // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild
74 // Can be base class for container, Wizard, Show, etc.
77 // If true, change the size of my currently displayed child to match my size
81 // Remembers the selected child across sessions
84 baseClass
: "dijitStackContainer",
87 // selectedChildWidget: [readonly] dijit._Widget
88 // References the currently selected child widget, if any.
89 // Adjust selected child with selectChild() method.
90 selectedChildWidget: null,
93 buildRendering: function(){
94 this.inherited(arguments
);
95 domClass
.add(this.domNode
, "dijitLayoutContainer");
96 this.containerNode
.setAttribute("role", "tabpanel");
99 postCreate: function(){
100 this.inherited(arguments
);
101 this.connect(this.domNode
, "onkeypress", this._onKeyPress
);
105 if(this._started
){ return; }
107 var children
= this.getChildren();
109 // Setup each page panel to be initially hidden
110 array
.forEach(children
, this._setupChild
, this);
112 // Figure out which child to initially display, defaulting to first one
114 this.selectedChildWidget
= registry
.byId(cookie(this.id
+ "_selectedChild"));
116 array
.some(children
, function(child
){
118 this.selectedChildWidget
= child
;
120 return child
.selected
;
123 var selected
= this.selectedChildWidget
;
124 if(!selected
&& children
[0]){
125 selected
= this.selectedChildWidget
= children
[0];
126 selected
.selected
= true;
129 // Publish information about myself so any StackControllers can initialize.
130 // This needs to happen before this.inherited(arguments) so that for
131 // TabContainer, this._contentBox doesn't include the space for the tab labels.
132 topic
.publish(this.id
+"-startup", {children
: children
, selected
: selected
});
134 // Startup each child widget, and do initial layout like setting this._contentBox,
135 // then calls this.resize() which does the initial sizing on the selected child.
136 this.inherited(arguments
);
140 // Resize is called when we are first made visible (it's called from startup()
141 // if we are initially visible). If this is the first time we've been made
142 // visible then show our first child.
143 if(!this._hasBeenShown
){
144 this._hasBeenShown
= true;
145 var selected
= this.selectedChildWidget
;
147 this._showChild(selected
);
150 this.inherited(arguments
);
153 _setupChild: function(/*dijit._Widget*/ child
){
154 // Overrides _LayoutWidget._setupChild()
156 this.inherited(arguments
);
158 domClass
.replace(child
.domNode
, "dijitHidden", "dijitVisible");
160 // remove the title attribute so it doesn't show up when i hover
162 child
.domNode
.title
= "";
165 addChild: function(/*dijit._Widget*/ child
, /*Integer?*/ insertIndex
){
166 // Overrides _Container.addChild() to do layout and publish events
168 this.inherited(arguments
);
171 topic
.publish(this.id
+"-addChild", child
, insertIndex
); // publish
173 // in case the tab titles have overflowed from one line to two lines
174 // (or, if this if first child, from zero lines to one line)
175 // TODO: w/ScrollingTabController this is no longer necessary, although
176 // ScrollTabController.resize() does need to get called to show/hide
177 // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild().
178 // If this is updated to not layout [except for initial child added / last child removed], update
179 // "childless startup" test in StackContainer.html to check for no resize event after second addChild()
182 // if this is the first child, then select it
183 if(!this.selectedChildWidget
){
184 this.selectChild(child
);
189 removeChild: function(/*dijit._Widget*/ page
){
190 // Overrides _Container.removeChild() to do layout and publish events
192 this.inherited(arguments
);
195 // this will notify any tablists to remove a button; do this first because it may affect sizing
196 topic
.publish(this.id
+ "-removeChild", page
); // publish
199 // If all our children are being destroyed than don't run the code below (to select another page),
200 // because we are deleting every page one by one
201 if(this._descendantsBeingDestroyed
){ return; }
203 // Select new page to display, also updating TabController to show the respective tab.
204 // Do this before layout call because it can affect the height of the TabController.
205 if(this.selectedChildWidget
=== page
){
206 this.selectedChildWidget
= undefined;
208 var children
= this.getChildren();
210 this.selectChild(children
[0]);
216 // In case the tab titles now take up one line instead of two lines
217 // (note though that ScrollingTabController never overflows to multiple lines),
218 // or the height has changed slightly because of addition/removal of tab which close icon
223 selectChild: function(/*dijit._Widget|String*/ page
, /*Boolean*/ animate
){
225 // Show the given widget (which must be one of my children)
227 // Reference to child widget or id of child widget
229 page
= registry
.byId(page
);
231 if(this.selectedChildWidget
!= page
){
232 // Deselect old page and select new one
233 var d
= this._transition(page
, this.selectedChildWidget
, animate
);
234 this._set("selectedChildWidget", page
);
235 topic
.publish(this.id
+"-selectChild", page
); // publish
238 cookie(this.id
+ "_selectedChild", this.selectedChildWidget
.id
);
242 return d
; // If child has an href, promise that fires when the child's href finishes loading
245 _transition: function(newWidget
, oldWidget
/*===== , animate =====*/){
247 // Hide the old widget and display the new widget.
248 // Subclasses should override this.
249 // newWidget: dijit._Widget
250 // The newly selected widget.
251 // oldWidget: dijit._Widget
252 // The previously selected widget.
254 // Used by AccordionContainer to turn on/off slide effect.
256 // protected extension
258 this._hideChild(oldWidget
);
260 var d
= this._showChild(newWidget
);
262 // Size the new widget, in case this is the first time it's being shown,
263 // or I have been resized since the last time it was shown.
264 // Note that page must be visible for resizing to work.
265 if(newWidget
.resize
){
267 newWidget
.resize(this._containerContentBox
|| this._contentBox
);
269 // the child should pick it's own size but we still need to call resize()
270 // (with no arguments) to let the widget lay itself out
275 return d
; // If child has an href, promise that fires when the child's href finishes loading
278 _adjacent: function(/*Boolean*/ forward
){
280 // Gets the next/previous child widget in this container from the current selection.
281 var children
= this.getChildren();
282 var index
= array
.indexOf(children
, this.selectedChildWidget
);
283 index
+= forward
? 1 : children
.length
- 1;
284 return children
[ index
% children
.length
]; // dijit._Widget
289 // Advance to next page.
290 return this.selectChild(this._adjacent(true), true);
295 // Go back to previous page.
296 return this.selectChild(this._adjacent(false), true);
299 _onKeyPress: function(e
){
300 topic
.publish(this.id
+"-containerKeyPress", { e
: e
, page
: this}); // publish
304 // Implement _LayoutWidget.layout() virtual method.
305 var child
= this.selectedChildWidget
;
306 if(child
&& child
.resize
){
308 child
.resize(this._containerContentBox
|| this._contentBox
);
315 _showChild: function(/*dijit._Widget*/ page
){
317 // Show the specified child by changing it's CSS, and call _onShow()/onShow() so
318 // it can do any updates it needs regarding loading href's etc.
320 // Promise that fires when page has finished showing, or true if there's no href
321 var children
= this.getChildren();
322 page
.isFirstChild
= (page
== children
[0]);
323 page
.isLastChild
= (page
== children
[children
.length
-1]);
324 page
._set("selected", true);
326 domClass
.replace(page
.domNode
, "dijitVisible", "dijitHidden");
328 return (page
._onShow
&& page
._onShow()) || true;
331 _hideChild: function(/*dijit._Widget*/ page
){
333 // Hide the specified child by changing it's CSS, and call _onHide() so
335 page
._set("selected", false);
336 domClass
.replace(page
.domNode
, "dijitHidden", "dijitVisible");
338 page
.onHide
&& page
.onHide();
341 closeChild: function(/*dijit._Widget*/ page
){
343 // Callback when user clicks the [X] to remove a page.
344 // If onClose() returns true then remove and destroy the child.
347 var remove
= page
.onClose(this, page
);
349 this.removeChild(page
);
350 // makes sure we can clean up executeScripts in ContentPane onUnLoad
351 page
.destroyRecursive();
355 destroyDescendants: function(/*Boolean*/ preserveDom
){
356 this._descendantsBeingDestroyed
= true;
357 this.selectedChildWidget
= undefined;
358 array
.forEach(this.getChildren(), function(child
){
360 this.removeChild(child
);
362 child
.destroyRecursive(preserveDom
);
364 this._descendantsBeingDestroyed
= false;