]>
Commit | Line | Data |
---|---|---|
1354d172 AD |
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 | |
8 | "dojo/ready", | |
9 | "dojo/topic", // publish | |
10 | "../registry", // registry.byId | |
11 | "../_WidgetBase", | |
12 | "./_LayoutWidget", | |
13 | "dojo/i18n!../nls/common" | |
14 | ], function(array, cookie, declare, domClass, kernel, lang, ready, topic, | |
15 | registry, _WidgetBase, _LayoutWidget){ | |
16 | ||
17 | /*===== | |
18 | var _WidgetBase = dijit._WidgetBase; | |
19 | var _LayoutWidget = dijit.layout._LayoutWidget; | |
20 | var StackController = dijit.layout.StackController; | |
21 | =====*/ | |
22 | ||
23 | // module: | |
24 | // dijit/layout/StackContainer | |
25 | // summary: | |
26 | // A container that has multiple children, but shows only one child at a time. | |
27 | ||
28 | // Back compat w/1.6, remove for 2.0 | |
29 | if(!kernel.isAsync){ | |
30 | ready(0, function(){ | |
31 | var requires = ["dijit/layout/StackController"]; | |
32 | require(requires); // use indirection so modules not rolled into a build | |
33 | }); | |
34 | } | |
35 | ||
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, { | |
40 | // selected: Boolean | |
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` | |
44 | selected: false, | |
45 | ||
46 | // closable: Boolean | |
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. | |
49 | closable: false, | |
50 | ||
51 | // iconClass: String | |
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", | |
55 | ||
56 | // showTitle: Boolean | |
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 | |
60 | showTitle: true | |
61 | }); | |
62 | ||
63 | return declare("dijit.layout.StackContainer", _LayoutWidget, { | |
64 | // summary: | |
65 | // A container that has multiple children, but shows only | |
66 | // one child at a time | |
67 | // | |
68 | // description: | |
69 | // A container for widgets (ContentPanes, for example) That displays | |
70 | // only one Widget at a time. | |
71 | // | |
72 | // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild | |
73 | // | |
74 | // Can be base class for container, Wizard, Show, etc. | |
75 | ||
76 | // doLayout: Boolean | |
77 | // If true, change the size of my currently displayed child to match my size | |
78 | doLayout: true, | |
79 | ||
80 | // persist: Boolean | |
81 | // Remembers the selected child across sessions | |
82 | persist: false, | |
83 | ||
84 | baseClass: "dijitStackContainer", | |
85 | ||
86 | /*===== | |
87 | // selectedChildWidget: [readonly] dijit._Widget | |
88 | // References the currently selected child widget, if any. | |
89 | // Adjust selected child with selectChild() method. | |
90 | selectedChildWidget: null, | |
91 | =====*/ | |
92 | ||
93 | buildRendering: function(){ | |
94 | this.inherited(arguments); | |
95 | domClass.add(this.domNode, "dijitLayoutContainer"); | |
96 | this.containerNode.setAttribute("role", "tabpanel"); | |
97 | }, | |
98 | ||
99 | postCreate: function(){ | |
100 | this.inherited(arguments); | |
101 | this.connect(this.domNode, "onkeypress", this._onKeyPress); | |
102 | }, | |
103 | ||
104 | startup: function(){ | |
105 | if(this._started){ return; } | |
106 | ||
107 | var children = this.getChildren(); | |
108 | ||
109 | // Setup each page panel to be initially hidden | |
110 | array.forEach(children, this._setupChild, this); | |
111 | ||
112 | // Figure out which child to initially display, defaulting to first one | |
113 | if(this.persist){ | |
114 | this.selectedChildWidget = registry.byId(cookie(this.id + "_selectedChild")); | |
115 | }else{ | |
116 | array.some(children, function(child){ | |
117 | if(child.selected){ | |
118 | this.selectedChildWidget = child; | |
119 | } | |
120 | return child.selected; | |
121 | }, this); | |
122 | } | |
123 | var selected = this.selectedChildWidget; | |
124 | if(!selected && children[0]){ | |
125 | selected = this.selectedChildWidget = children[0]; | |
126 | selected.selected = true; | |
127 | } | |
128 | ||
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}); | |
133 | ||
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); | |
137 | }, | |
138 | ||
139 | resize: function(){ | |
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; | |
146 | if(selected){ | |
147 | this._showChild(selected); | |
148 | } | |
149 | } | |
150 | this.inherited(arguments); | |
151 | }, | |
152 | ||
153 | _setupChild: function(/*dijit._Widget*/ child){ | |
154 | // Overrides _LayoutWidget._setupChild() | |
155 | ||
156 | this.inherited(arguments); | |
157 | ||
158 | domClass.replace(child.domNode, "dijitHidden", "dijitVisible"); | |
159 | ||
160 | // remove the title attribute so it doesn't show up when i hover | |
161 | // over a node | |
162 | child.domNode.title = ""; | |
163 | }, | |
164 | ||
165 | addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ | |
166 | // Overrides _Container.addChild() to do layout and publish events | |
167 | ||
168 | this.inherited(arguments); | |
169 | ||
170 | if(this._started){ | |
171 | topic.publish(this.id+"-addChild", child, insertIndex); // publish | |
172 | ||
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() | |
180 | this.layout(); | |
181 | ||
182 | // if this is the first child, then select it | |
183 | if(!this.selectedChildWidget){ | |
184 | this.selectChild(child); | |
185 | } | |
186 | } | |
187 | }, | |
188 | ||
189 | removeChild: function(/*dijit._Widget*/ page){ | |
190 | // Overrides _Container.removeChild() to do layout and publish events | |
191 | ||
192 | this.inherited(arguments); | |
193 | ||
194 | if(this._started){ | |
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 | |
197 | } | |
198 | ||
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; } | |
202 | ||
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; | |
207 | if(this._started){ | |
208 | var children = this.getChildren(); | |
209 | if(children.length){ | |
210 | this.selectChild(children[0]); | |
211 | } | |
212 | } | |
213 | } | |
214 | ||
215 | if(this._started){ | |
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 | |
219 | this.layout(); | |
220 | } | |
221 | }, | |
222 | ||
223 | selectChild: function(/*dijit._Widget|String*/ page, /*Boolean*/ animate){ | |
224 | // summary: | |
225 | // Show the given widget (which must be one of my children) | |
226 | // page: | |
227 | // Reference to child widget or id of child widget | |
228 | ||
229 | page = registry.byId(page); | |
230 | ||
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 | |
236 | ||
237 | if(this.persist){ | |
238 | cookie(this.id + "_selectedChild", this.selectedChildWidget.id); | |
239 | } | |
240 | } | |
241 | ||
242 | return d; // If child has an href, promise that fires when the child's href finishes loading | |
243 | }, | |
244 | ||
245 | _transition: function(newWidget, oldWidget /*===== , animate =====*/){ | |
246 | // summary: | |
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. | |
253 | // animate: Boolean | |
254 | // Used by AccordionContainer to turn on/off slide effect. | |
255 | // tags: | |
256 | // protected extension | |
257 | if(oldWidget){ | |
258 | this._hideChild(oldWidget); | |
259 | } | |
260 | var d = this._showChild(newWidget); | |
261 | ||
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){ | |
266 | if(this.doLayout){ | |
267 | newWidget.resize(this._containerContentBox || this._contentBox); | |
268 | }else{ | |
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 | |
271 | newWidget.resize(); | |
272 | } | |
273 | } | |
274 | ||
275 | return d; // If child has an href, promise that fires when the child's href finishes loading | |
276 | }, | |
277 | ||
278 | _adjacent: function(/*Boolean*/ forward){ | |
279 | // summary: | |
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 | |
285 | }, | |
286 | ||
287 | forward: function(){ | |
288 | // summary: | |
289 | // Advance to next page. | |
290 | return this.selectChild(this._adjacent(true), true); | |
291 | }, | |
292 | ||
293 | back: function(){ | |
294 | // summary: | |
295 | // Go back to previous page. | |
296 | return this.selectChild(this._adjacent(false), true); | |
297 | }, | |
298 | ||
299 | _onKeyPress: function(e){ | |
300 | topic.publish(this.id+"-containerKeyPress", { e: e, page: this}); // publish | |
301 | }, | |
302 | ||
303 | layout: function(){ | |
304 | // Implement _LayoutWidget.layout() virtual method. | |
305 | var child = this.selectedChildWidget; | |
306 | if(child && child.resize){ | |
307 | if(this.doLayout){ | |
308 | child.resize(this._containerContentBox || this._contentBox); | |
309 | }else{ | |
310 | child.resize(); | |
311 | } | |
312 | } | |
313 | }, | |
314 | ||
315 | _showChild: function(/*dijit._Widget*/ page){ | |
316 | // summary: | |
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. | |
319 | // returns: | |
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); | |
325 | ||
326 | domClass.replace(page.domNode, "dijitVisible", "dijitHidden"); | |
327 | ||
328 | return (page._onShow && page._onShow()) || true; | |
329 | }, | |
330 | ||
331 | _hideChild: function(/*dijit._Widget*/ page){ | |
332 | // summary: | |
333 | // Hide the specified child by changing it's CSS, and call _onHide() so | |
334 | // it's notified. | |
335 | page._set("selected", false); | |
336 | domClass.replace(page.domNode, "dijitHidden", "dijitVisible"); | |
337 | ||
338 | page.onHide && page.onHide(); | |
339 | }, | |
340 | ||
341 | closeChild: function(/*dijit._Widget*/ page){ | |
342 | // summary: | |
343 | // Callback when user clicks the [X] to remove a page. | |
344 | // If onClose() returns true then remove and destroy the child. | |
345 | // tags: | |
346 | // private | |
347 | var remove = page.onClose(this, page); | |
348 | if(remove){ | |
349 | this.removeChild(page); | |
350 | // makes sure we can clean up executeScripts in ContentPane onUnLoad | |
351 | page.destroyRecursive(); | |
352 | } | |
353 | }, | |
354 | ||
355 | destroyDescendants: function(/*Boolean*/ preserveDom){ | |
356 | this._descendantsBeingDestroyed = true; | |
357 | this.selectedChildWidget = undefined; | |
358 | array.forEach(this.getChildren(), function(child){ | |
359 | if(!preserveDom){ | |
360 | this.removeChild(child); | |
361 | } | |
362 | child.destroyRecursive(preserveDom); | |
363 | }, this); | |
364 | this._descendantsBeingDestroyed = false; | |
365 | } | |
366 | }); | |
367 | ||
368 | }); |