1 | define([ |
---|
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 | }); |
---|