source: Dev/trunk/src/client/dijit/layout/StackContainer.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 13.5 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/dom-construct",
7        "dojo/has", // has("dijit-legacy-requires")
8        "dojo/_base/lang", // lang.extend
9        "dojo/on",
10        "dojo/ready",
11        "dojo/topic", // publish
12        "dojo/when",
13        "../registry", // registry.byId
14        "../_WidgetBase",
15        "./_LayoutWidget",
16        "dojo/i18n!../nls/common"
17], function(array, cookie, declare, domClass, domConstruct, has, lang, on, ready, topic, when, registry, _WidgetBase, _LayoutWidget){
18
19        // module:
20        //              dijit/layout/StackContainer
21
22        // Back compat w/1.6, remove for 2.0
23        if(has("dijit-legacy-requires")){
24                ready(0, function(){
25                        var requires = ["dijit/layout/StackController"];
26                        require(requires);      // use indirection so modules not rolled into a build
27                });
28        }
29
30        var StackContainer = declare("dijit.layout.StackContainer", _LayoutWidget, {
31                // summary:
32                //              A container that has multiple children, but shows only
33                //              one child at a time
34                //
35                // description:
36                //              A container for widgets (ContentPanes, for example) That displays
37                //              only one Widget at a time.
38                //
39                //              Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild
40                //
41                //              Can be base class for container, Wizard, Show, etc.
42                //
43                //              See `StackContainer.ChildWidgetProperties` for details on the properties that can be set on
44                //              children of a `StackContainer`.
45
46                // doLayout: Boolean
47                //              If true, change the size of my currently displayed child to match my size
48                doLayout: true,
49
50                // persist: Boolean
51                //              Remembers the selected child across sessions
52                persist: false,
53
54                baseClass: "dijitStackContainer",
55
56                /*=====
57                // selectedChildWidget: [readonly] dijit._Widget
58                //              References the currently selected child widget, if any.
59                //              Adjust selected child with selectChild() method.
60                selectedChildWidget: null,
61                =====*/
62
63                buildRendering: function(){
64                        this.inherited(arguments);
65                        domClass.add(this.domNode, "dijitLayoutContainer");
66                },
67
68                postCreate: function(){
69                        this.inherited(arguments);
70                        this.own(
71                                on(this.domNode, "keydown", lang.hitch(this, "_onKeyDown"))
72                        );
73                },
74
75                startup: function(){
76                        if(this._started){
77                                return;
78                        }
79
80                        var children = this.getChildren();
81
82                        // Setup each page panel to be initially hidden
83                        array.forEach(children, this._setupChild, this);
84
85                        // Figure out which child to initially display, defaulting to first one
86                        if(this.persist){
87                                this.selectedChildWidget = registry.byId(cookie(this.id + "_selectedChild"));
88                        }else{
89                                array.some(children, function(child){
90                                        if(child.selected){
91                                                this.selectedChildWidget = child;
92                                        }
93                                        return child.selected;
94                                }, this);
95                        }
96                        var selected = this.selectedChildWidget;
97                        if(!selected && children[0]){
98                                selected = this.selectedChildWidget = children[0];
99                                selected.selected = true;
100                        }
101
102                        // Publish information about myself so any StackControllers can initialize.
103                        // This needs to happen before this.inherited(arguments) so that for
104                        // TabContainer, this._contentBox doesn't include the space for the tab labels.
105                        topic.publish(this.id + "-startup", {children: children, selected: selected, textDir: this.textDir});
106
107                        // Startup each child widget, and do initial layout like setting this._contentBox,
108                        // then calls this.resize() which does the initial sizing on the selected child.
109                        this.inherited(arguments);
110                },
111
112                resize: function(){
113                        // Overrides _LayoutWidget.resize()
114                        // Resize is called when we are first made visible (it's called from startup()
115                        // if we are initially visible). If this is the first time we've been made
116                        // visible then show our first child.
117                        if(!this._hasBeenShown){
118                                this._hasBeenShown = true;
119                                var selected = this.selectedChildWidget;
120                                if(selected){
121                                        this._showChild(selected);
122                                }
123                        }
124                        this.inherited(arguments);
125                },
126
127                _setupChild: function(/*dijit/_WidgetBase*/ child){
128                        // Overrides _LayoutWidget._setupChild()
129
130                        // For aria support, wrap child widget in a <div role="tabpanel">
131                        var childNode = child.domNode,
132                                wrapper = domConstruct.place(
133                                        "<div role='tabpanel' class='" + this.baseClass + "ChildWrapper dijitHidden'>",
134                                        child.domNode,
135                                        "replace"),
136                                label = child["aria-label"] || child.title || child.label;
137                        if(label){
138                                // setAttribute() escapes special chars, and if() statement avoids setting aria-label="undefined"
139                                wrapper.setAttribute("aria-label", label);
140                        }
141                        domConstruct.place(childNode, wrapper);
142                        child._wrapper = wrapper;       // to set the aria-labelledby in StackController
143
144                        this.inherited(arguments);
145
146                        // child may have style="display: none" (at least our test cases do), so remove that
147                        if(childNode.style.display == "none"){
148                                childNode.style.display = "block";
149                        }
150
151                        // remove the title attribute so it doesn't show up when i hover over a node
152                        child.domNode.title = "";
153                },
154
155                addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex){
156                        // Overrides _Container.addChild() to do layout and publish events
157
158                        this.inherited(arguments);
159
160                        if(this._started){
161                                topic.publish(this.id + "-addChild", child, insertIndex);       // publish
162
163                                // in case the tab titles have overflowed from one line to two lines
164                                // (or, if this if first child, from zero lines to one line)
165                                // TODO: w/ScrollingTabController this is no longer necessary, although
166                                // ScrollTabController.resize() does need to get called to show/hide
167                                // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild().
168                                // If this is updated to not layout [except for initial child added / last child removed], update
169                                // "childless startup" test in StackContainer.html to check for no resize event after second addChild()
170                                this.layout();
171
172                                // if this is the first child, then select it
173                                if(!this.selectedChildWidget){
174                                        this.selectChild(child);
175                                }
176                        }
177                },
178
179                removeChild: function(/*dijit/_WidgetBase*/ page){
180                        // Overrides _Container.removeChild() to do layout and publish events
181
182                        var idx = array.indexOf(this.getChildren(), page);
183
184                        this.inherited(arguments);
185
186                        // Remove the child widget wrapper we use to set aria roles.  This won't affect the page itself since it's
187                        // already been detached from page._wrapper via the this.inherited(arguments) call above.
188                        domConstruct.destroy(page._wrapper);
189                        delete page._wrapper;
190
191                        if(this._started){
192                                // This will notify any tablists to remove a button; do this first because it may affect sizing.
193                                topic.publish(this.id + "-removeChild", page);
194                        }
195
196                        // If all our children are being destroyed than don't run the code below (to select another page),
197                        // because we are deleting every page one by one
198                        if(this._descendantsBeingDestroyed){
199                                return;
200                        }
201
202                        // Select new page to display, also updating TabController to show the respective tab.
203                        // Do this before layout call because it can affect the height of the TabController.
204                        if(this.selectedChildWidget === page){
205                                this.selectedChildWidget = undefined;
206                                if(this._started){
207                                        var children = this.getChildren();
208                                        if(children.length){
209                                                this.selectChild(children[Math.max(idx - 1, 0)]);
210                                        }
211                                }
212                        }
213
214                        if(this._started){
215                                // In case the tab titles now take up one line instead of two lines
216                                // (note though that ScrollingTabController never overflows to multiple lines),
217                                // or the height has changed slightly because of addition/removal of tab which close icon
218                                this.layout();
219                        }
220                },
221
222                selectChild: function(/*dijit/_WidgetBase|String*/ page, /*Boolean*/ animate){
223                        // summary:
224                        //              Show the given widget (which must be one of my children)
225                        // page:
226                        //              Reference to child widget or id of child widget
227
228                        var d;
229
230                        page = registry.byId(page);
231
232                        if(this.selectedChildWidget != page){
233                                // Deselect old page and select new one
234                                d = this._transition(page, this.selectedChildWidget, animate);
235                                this._set("selectedChildWidget", page);
236                                topic.publish(this.id + "-selectChild", page);  // publish
237
238                                if(this.persist){
239                                        cookie(this.id + "_selectedChild", this.selectedChildWidget.id);
240                                }
241                        }
242
243                        // d may be null, or a scalar like true.  Return a promise in all cases
244                        return when(d || true);         // Promise
245                },
246
247                _transition: function(newWidget, oldWidget /*===== ,  animate =====*/){
248                        // summary:
249                        //              Hide the old widget and display the new widget.
250                        //              Subclasses should override this.
251                        // newWidget: dijit/_WidgetBase
252                        //              The newly selected widget.
253                        // oldWidget: dijit/_WidgetBase
254                        //              The previously selected widget.
255                        // animate: Boolean
256                        //              Used by AccordionContainer to turn on/off slide effect.
257                        // tags:
258                        //              protected extension
259                        if(oldWidget){
260                                this._hideChild(oldWidget);
261                        }
262                        var d = this._showChild(newWidget);
263
264                        // Size the new widget, in case this is the first time it's being shown,
265                        // or I have been resized since the last time it was shown.
266                        // Note that page must be visible for resizing to work.
267                        if(newWidget.resize){
268                                if(this.doLayout){
269                                        newWidget.resize(this._containerContentBox || this._contentBox);
270                                }else{
271                                        // the child should pick it's own size but we still need to call resize()
272                                        // (with no arguments) to let the widget lay itself out
273                                        newWidget.resize();
274                                }
275                        }
276
277                        return d;       // If child has an href, promise that fires when the child's href finishes loading
278                },
279
280                _adjacent: function(/*Boolean*/ forward){
281                        // summary:
282                        //              Gets the next/previous child widget in this container from the current selection.
283
284                        // TODO: remove for 2.0 if this isn't being used.   Otherwise, fix to skip disabled tabs.
285
286                        var children = this.getChildren();
287                        var index = array.indexOf(children, this.selectedChildWidget);
288                        index += forward ? 1 : children.length - 1;
289                        return children[ index % children.length ]; // dijit/_WidgetBase
290                },
291
292                forward: function(){
293                        // summary:
294                        //              Advance to next page.
295                        return this.selectChild(this._adjacent(true), true);
296                },
297
298                back: function(){
299                        // summary:
300                        //              Go back to previous page.
301                        return this.selectChild(this._adjacent(false), true);
302                },
303
304                _onKeyDown: function(e){
305                        topic.publish(this.id + "-containerKeyDown", { e: e, page: this});      // publish
306                },
307
308                layout: function(){
309                        // Implement _LayoutWidget.layout() virtual method.
310                        var child = this.selectedChildWidget;
311                        if(child && child.resize){
312                                if(this.doLayout){
313                                        child.resize(this._containerContentBox || this._contentBox);
314                                }else{
315                                        child.resize();
316                                }
317                        }
318                },
319
320                _showChild: function(/*dijit/_WidgetBase*/ page){
321                        // summary:
322                        //              Show the specified child by changing it's CSS, and call _onShow()/onShow() so
323                        //              it can do any updates it needs regarding loading href's etc.
324                        // returns:
325                        //              Promise that fires when page has finished showing, or true if there's no href
326                        var children = this.getChildren();
327                        page.isFirstChild = (page == children[0]);
328                        page.isLastChild = (page == children[children.length - 1]);
329                        page._set("selected", true);
330
331                        if(page._wrapper){      // false if not started yet
332                                domClass.replace(page._wrapper, "dijitVisible", "dijitHidden");
333                        }
334
335                        return (page._onShow && page._onShow()) || true;
336                },
337
338                _hideChild: function(/*dijit/_WidgetBase*/ page){
339                        // summary:
340                        //              Hide the specified child by changing it's CSS, and call _onHide() so
341                        //              it's notified.
342                        page._set("selected", false);
343
344                        if(page._wrapper){      // false if not started yet
345                                domClass.replace(page._wrapper, "dijitHidden", "dijitVisible");
346                        }
347
348                        page.onHide && page.onHide();
349                },
350
351                closeChild: function(/*dijit/_WidgetBase*/ page){
352                        // summary:
353                        //              Callback when user clicks the [X] to remove a page.
354                        //              If onClose() returns true then remove and destroy the child.
355                        // tags:
356                        //              private
357                        var remove = page.onClose && page.onClose(this, page);
358                        if(remove){
359                                this.removeChild(page);
360                                // makes sure we can clean up executeScripts in ContentPane onUnLoad
361                                page.destroyRecursive();
362                        }
363                },
364
365                destroyDescendants: function(/*Boolean*/ preserveDom){
366                        this._descendantsBeingDestroyed = true;
367                        this.selectedChildWidget = undefined;
368                        array.forEach(this.getChildren(), function(child){
369                                if(!preserveDom){
370                                        this.removeChild(child);
371                                }
372                                child.destroyRecursive(preserveDom);
373                        }, this);
374                        this._descendantsBeingDestroyed = false;
375                }
376        });
377
378        StackContainer.ChildWidgetProperties = {
379                // summary:
380                //              These properties can be specified for the children of a StackContainer.
381
382                // selected: Boolean
383                //              Specifies that this widget should be the initially displayed pane.
384                //              Note: to change the selected child use `dijit/layout/StackContainer.selectChild`
385                selected: false,
386
387                // disabled: Boolean
388                //              Specifies that the button to select this pane should be disabled.
389                //              Doesn't affect programmatic selection of the pane, nor does it deselect the pane if it is currently selected.
390                disabled: false,
391
392                // closable: Boolean
393                //              True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
394                closable: false,
395
396                // iconClass: String
397                //              CSS Class specifying icon to use in label associated with this pane.
398                iconClass: "dijitNoIcon",
399
400                // showTitle: Boolean
401                //              When true, display title of this widget as tab label etc., rather than just using
402                //              icon specified in iconClass
403                showTitle: true
404        };
405
406        // Since any widget can be specified as a StackContainer child, mix them
407        // into the base widget class.  (This is a hack, but it's effective.)
408        // This is for the benefit of the parser.   Remove for 2.0.  Also, hide from doc viewer.
409        lang.extend(_WidgetBase, /*===== {} || =====*/ StackContainer.ChildWidgetProperties);
410
411        return StackContainer;
412});
Note: See TracBrowser for help on using the repository browser.