You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
411 lines
14 KiB
411 lines
14 KiB
define([ |
|
"dojo/_base/array", // array.forEach array.indexOf array.some |
|
"dojo/cookie", // cookie |
|
"dojo/_base/declare", // declare |
|
"dojo/dom-class", // domClass.add domClass.replace |
|
"dojo/dom-construct", |
|
"dojo/has", // has("dijit-legacy-requires") |
|
"dojo/_base/lang", // lang.extend |
|
"dojo/on", |
|
"dojo/ready", |
|
"dojo/topic", // publish |
|
"dojo/when", |
|
"../registry", // registry.byId |
|
"../_WidgetBase", |
|
"./_LayoutWidget" |
|
], function(array, cookie, declare, domClass, domConstruct, has, lang, on, ready, topic, when, registry, _WidgetBase, _LayoutWidget){ |
|
|
|
// module: |
|
// dijit/layout/StackContainer |
|
|
|
// Back compat w/1.6, remove for 2.0 |
|
if(has("dijit-legacy-requires")){ |
|
ready(0, function(){ |
|
var requires = ["dijit/layout/StackController"]; |
|
require(requires); // use indirection so modules not rolled into a build |
|
}); |
|
} |
|
|
|
var StackContainer = declare("dijit.layout.StackContainer", _LayoutWidget, { |
|
// summary: |
|
// A container that has multiple children, but shows only |
|
// one child at a time |
|
// |
|
// description: |
|
// A container for widgets (ContentPanes, for example) That displays |
|
// only one Widget at a time. |
|
// |
|
// Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild |
|
// |
|
// Can be base class for container, Wizard, Show, etc. |
|
// |
|
// See `StackContainer.ChildWidgetProperties` for details on the properties that can be set on |
|
// children of a `StackContainer`. |
|
|
|
// doLayout: Boolean |
|
// If true, change the size of my currently displayed child to match my size |
|
doLayout: true, |
|
|
|
// persist: Boolean |
|
// Remembers the selected child across sessions |
|
persist: false, |
|
|
|
baseClass: "dijitStackContainer", |
|
|
|
/*===== |
|
// selectedChildWidget: [readonly] dijit._Widget |
|
// References the currently selected child widget, if any. |
|
// Adjust selected child with selectChild() method. |
|
selectedChildWidget: null, |
|
=====*/ |
|
|
|
buildRendering: function(){ |
|
this.inherited(arguments); |
|
domClass.add(this.domNode, "dijitLayoutContainer"); |
|
}, |
|
|
|
postCreate: function(){ |
|
this.inherited(arguments); |
|
this.own( |
|
on(this.domNode, "keydown", lang.hitch(this, "_onKeyDown")) |
|
); |
|
}, |
|
|
|
startup: function(){ |
|
if(this._started){ |
|
return; |
|
} |
|
|
|
var children = this.getChildren(); |
|
|
|
// Setup each page panel to be initially hidden |
|
array.forEach(children, this._setupChild, this); |
|
|
|
// Figure out which child to initially display, defaulting to first one |
|
if(this.persist){ |
|
this.selectedChildWidget = registry.byId(cookie(this.id + "_selectedChild")); |
|
}else{ |
|
array.some(children, function(child){ |
|
if(child.selected){ |
|
this.selectedChildWidget = child; |
|
} |
|
return child.selected; |
|
}, this); |
|
} |
|
var selected = this.selectedChildWidget; |
|
if(!selected && children[0]){ |
|
selected = this.selectedChildWidget = children[0]; |
|
selected.selected = true; |
|
} |
|
|
|
// Publish information about myself so any StackControllers can initialize. |
|
// This needs to happen before this.inherited(arguments) so that for |
|
// TabContainer, this._contentBox doesn't include the space for the tab labels. |
|
topic.publish(this.id + "-startup", {children: children, selected: selected, textDir: this.textDir}); |
|
|
|
// Startup each child widget, and do initial layout like setting this._contentBox, |
|
// then calls this.resize() which does the initial sizing on the selected child. |
|
this.inherited(arguments); |
|
}, |
|
|
|
resize: function(){ |
|
// Overrides _LayoutWidget.resize() |
|
// Resize is called when we are first made visible (it's called from startup() |
|
// if we are initially visible). If this is the first time we've been made |
|
// visible then show our first child. |
|
if(!this._hasBeenShown){ |
|
this._hasBeenShown = true; |
|
var selected = this.selectedChildWidget; |
|
if(selected){ |
|
this._showChild(selected); |
|
} |
|
} |
|
this.inherited(arguments); |
|
}, |
|
|
|
_setupChild: function(/*dijit/_WidgetBase*/ child){ |
|
// Overrides _LayoutWidget._setupChild() |
|
|
|
// For aria support, wrap child widget in a <div role="tabpanel"> |
|
var childNode = child.domNode, |
|
wrapper = domConstruct.place( |
|
"<div role='tabpanel' class='" + this.baseClass + "ChildWrapper dijitHidden'>", |
|
child.domNode, |
|
"replace"), |
|
label = child["aria-label"] || child.title || child.label; |
|
if(label){ |
|
// setAttribute() escapes special chars, and if() statement avoids setting aria-label="undefined" |
|
wrapper.setAttribute("aria-label", label); |
|
} |
|
domConstruct.place(childNode, wrapper); |
|
child._wrapper = wrapper; // to set the aria-labelledby in StackController |
|
|
|
this.inherited(arguments); |
|
|
|
// child may have style="display: none" (at least our test cases do), so remove that |
|
if(childNode.style.display == "none"){ |
|
childNode.style.display = "block"; |
|
} |
|
|
|
// remove the title attribute so it doesn't show up when i hover over a node |
|
child.domNode.removeAttribute("title"); |
|
}, |
|
|
|
addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex){ |
|
// Overrides _Container.addChild() to do layout and publish events |
|
|
|
this.inherited(arguments); |
|
|
|
if(this._started){ |
|
topic.publish(this.id + "-addChild", child, insertIndex); // publish |
|
|
|
// in case the tab titles have overflowed from one line to two lines |
|
// (or, if this if first child, from zero lines to one line) |
|
// TODO: w/ScrollingTabController this is no longer necessary, although |
|
// ScrollTabController.resize() does need to get called to show/hide |
|
// the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild(). |
|
// If this is updated to not layout [except for initial child added / last child removed], update |
|
// "childless startup" test in StackContainer.html to check for no resize event after second addChild() |
|
this.layout(); |
|
|
|
// if this is the first child, then select it |
|
if(!this.selectedChildWidget){ |
|
this.selectChild(child); |
|
} |
|
} |
|
}, |
|
|
|
removeChild: function(/*dijit/_WidgetBase*/ page){ |
|
// Overrides _Container.removeChild() to do layout and publish events |
|
|
|
var idx = array.indexOf(this.getChildren(), page); |
|
|
|
this.inherited(arguments); |
|
|
|
// Remove the child widget wrapper we use to set aria roles. This won't affect the page itself since it's |
|
// already been detached from page._wrapper via the this.inherited(arguments) call above. |
|
domConstruct.destroy(page._wrapper); |
|
delete page._wrapper; |
|
|
|
if(this._started){ |
|
// This will notify any tablists to remove a button; do this first because it may affect sizing. |
|
topic.publish(this.id + "-removeChild", page); |
|
} |
|
|
|
// If all our children are being destroyed than don't run the code below (to select another page), |
|
// because we are deleting every page one by one |
|
if(this._descendantsBeingDestroyed){ |
|
return; |
|
} |
|
|
|
// Select new page to display, also updating TabController to show the respective tab. |
|
// Do this before layout call because it can affect the height of the TabController. |
|
if(this.selectedChildWidget === page){ |
|
this.selectedChildWidget = undefined; |
|
if(this._started){ |
|
var children = this.getChildren(); |
|
if(children.length){ |
|
this.selectChild(children[Math.max(idx - 1, 0)]); |
|
} |
|
} |
|
} |
|
|
|
if(this._started){ |
|
// In case the tab titles now take up one line instead of two lines |
|
// (note though that ScrollingTabController never overflows to multiple lines), |
|
// or the height has changed slightly because of addition/removal of tab which close icon |
|
this.layout(); |
|
} |
|
}, |
|
|
|
selectChild: function(/*dijit/_WidgetBase|String*/ page, /*Boolean*/ animate){ |
|
// summary: |
|
// Show the given widget (which must be one of my children) |
|
// page: |
|
// Reference to child widget or id of child widget |
|
|
|
var d; |
|
|
|
page = registry.byId(page); |
|
|
|
if(this.selectedChildWidget != page){ |
|
// Deselect old page and select new one |
|
d = this._transition(page, this.selectedChildWidget, animate); |
|
this._set("selectedChildWidget", page); |
|
topic.publish(this.id + "-selectChild", page, this._focused); // publish |
|
|
|
if(this.persist){ |
|
cookie(this.id + "_selectedChild", this.selectedChildWidget.id); |
|
} |
|
} |
|
|
|
// d may be null, or a scalar like true. Return a promise in all cases |
|
return when(d || true); // Promise |
|
}, |
|
|
|
_transition: function(newWidget, oldWidget /*===== , animate =====*/){ |
|
// summary: |
|
// Hide the old widget and display the new widget. |
|
// Subclasses should override this. |
|
// newWidget: dijit/_WidgetBase |
|
// The newly selected widget. |
|
// oldWidget: dijit/_WidgetBase |
|
// The previously selected widget. |
|
// animate: Boolean |
|
// Used by AccordionContainer to turn on/off slide effect. |
|
// tags: |
|
// protected extension |
|
if(oldWidget){ |
|
this._hideChild(oldWidget); |
|
} |
|
var d = this._showChild(newWidget); |
|
|
|
// Size the new widget, in case this is the first time it's being shown, |
|
// or I have been resized since the last time it was shown. |
|
// Note that page must be visible for resizing to work. |
|
if(newWidget.resize){ |
|
if(this.doLayout){ |
|
newWidget.resize(this._containerContentBox || this._contentBox); |
|
}else{ |
|
// the child should pick it's own size but we still need to call resize() |
|
// (with no arguments) to let the widget lay itself out |
|
newWidget.resize(); |
|
} |
|
} |
|
|
|
return d; // If child has an href, promise that fires when the child's href finishes loading |
|
}, |
|
|
|
_adjacent: function(/*Boolean*/ forward){ |
|
// summary: |
|
// Gets the next/previous child widget in this container from the current selection. |
|
|
|
// TODO: remove for 2.0 if this isn't being used. Otherwise, fix to skip disabled tabs. |
|
|
|
var children = this.getChildren(); |
|
var index = array.indexOf(children, this.selectedChildWidget); |
|
index += forward ? 1 : children.length - 1; |
|
return children[ index % children.length ]; // dijit/_WidgetBase |
|
}, |
|
|
|
forward: function(){ |
|
// summary: |
|
// Advance to next page. |
|
return this.selectChild(this._adjacent(true), true); |
|
}, |
|
|
|
back: function(){ |
|
// summary: |
|
// Go back to previous page. |
|
return this.selectChild(this._adjacent(false), true); |
|
}, |
|
|
|
_onKeyDown: function(e){ |
|
topic.publish(this.id + "-containerKeyDown", { e: e, page: this}); // publish |
|
}, |
|
|
|
layout: function(){ |
|
// Implement _LayoutWidget.layout() virtual method. |
|
var child = this.selectedChildWidget; |
|
if(child && child.resize){ |
|
if(this.doLayout){ |
|
child.resize(this._containerContentBox || this._contentBox); |
|
}else{ |
|
child.resize(); |
|
} |
|
} |
|
}, |
|
|
|
_showChild: function(/*dijit/_WidgetBase*/ page){ |
|
// summary: |
|
// Show the specified child by changing it's CSS, and call _onShow()/onShow() so |
|
// it can do any updates it needs regarding loading href's etc. |
|
// returns: |
|
// Promise that fires when page has finished showing, or true if there's no href |
|
var children = this.getChildren(); |
|
page.isFirstChild = (page == children[0]); |
|
page.isLastChild = (page == children[children.length - 1]); |
|
page._set("selected", true); |
|
|
|
if(page._wrapper){ // false if not started yet |
|
domClass.replace(page._wrapper, "dijitVisible", "dijitHidden"); |
|
} |
|
|
|
return (page._onShow && page._onShow()) || true; |
|
}, |
|
|
|
_hideChild: function(/*dijit/_WidgetBase*/ page){ |
|
// summary: |
|
// Hide the specified child by changing it's CSS, and call _onHide() so |
|
// it's notified. |
|
page._set("selected", false); |
|
|
|
if(page._wrapper){ // false if not started yet |
|
domClass.replace(page._wrapper, "dijitHidden", "dijitVisible"); |
|
} |
|
|
|
page.onHide && page.onHide(); |
|
}, |
|
|
|
closeChild: function(/*dijit/_WidgetBase*/ page){ |
|
// summary: |
|
// Callback when user clicks the [X] to remove a page. |
|
// If onClose() returns true then remove and destroy the child. |
|
// tags: |
|
// private |
|
var remove = !page.onClose || page.onClose(this, page); |
|
if(remove){ |
|
this.removeChild(page); |
|
// makes sure we can clean up executeScripts in ContentPane onUnLoad |
|
page.destroyRecursive(); |
|
} |
|
}, |
|
|
|
destroyDescendants: function(/*Boolean*/ preserveDom){ |
|
this._descendantsBeingDestroyed = true; |
|
this.selectedChildWidget = undefined; |
|
array.forEach(this.getChildren(), function(child){ |
|
if(!preserveDom){ |
|
this.removeChild(child); |
|
} |
|
child.destroyRecursive(preserveDom); |
|
}, this); |
|
this._descendantsBeingDestroyed = false; |
|
} |
|
}); |
|
|
|
StackContainer.ChildWidgetProperties = { |
|
// summary: |
|
// These properties can be specified for the children of a StackContainer. |
|
|
|
// selected: Boolean |
|
// Specifies that this widget should be the initially displayed pane. |
|
// Note: to change the selected child use `dijit/layout/StackContainer.selectChild` |
|
selected: false, |
|
|
|
// disabled: Boolean |
|
// Specifies that the button to select this pane should be disabled. |
|
// Doesn't affect programmatic selection of the pane, nor does it deselect the pane if it is currently selected. |
|
disabled: false, |
|
|
|
// closable: Boolean |
|
// True if user can close (destroy) this child, such as (for example) clicking the X on the tab. |
|
closable: false, |
|
|
|
// iconClass: String |
|
// CSS Class specifying icon to use in label associated with this pane. |
|
iconClass: "dijitNoIcon", |
|
|
|
// showTitle: Boolean |
|
// When true, display title of this widget as tab label etc., rather than just using |
|
// icon specified in iconClass |
|
showTitle: true |
|
}; |
|
|
|
// Since any widget can be specified as a StackContainer child, mix them |
|
// into the base widget class. (This is a hack, but it's effective.) |
|
// This is for the benefit of the parser. Remove for 2.0. Also, hide from doc viewer. |
|
lang.extend(_WidgetBase, /*===== {} || =====*/ StackContainer.ChildWidgetProperties); |
|
|
|
return StackContainer; |
|
});
|
|
|