source: Dev/branches/rest-dojo-ui/client/dijit/layout/StackContainer.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 11.7 KB
Line 
1define([
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/*=====
18var _WidgetBase = dijit._WidgetBase;
19var _LayoutWidget = dijit.layout._LayoutWidget;
20var 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
29if(!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.)
39lang.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
63return 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});
Note: See TracBrowser for help on using the repository browser.