if (!dojo._hasResource["dijit.layout.StackContainer"]) { // _hasResource // checks added by // build. Do not use // _hasResource // directly in your // code. dojo._hasResource["dijit.layout.StackContainer"] = true; dojo.provide("dijit.layout.StackContainer"); dojo.require("dijit._Templated"); dojo.require("dijit.layout._LayoutWidget"); dojo.require("dijit.form.Button"); dojo.declare("dijit.layout.StackContainer", dijit.layout._LayoutWidget, // summary // A container that has multiple children, but shows only // one child at a time (like looking at the pages in a book one by // one). // // Publishes topics -addChild, -removeChild, and // -selectChild // // Can be base class for container, Wizard, Show, etc. { // doLayout: Boolean // if true, change the size of my currently displayed child to match my // size doLayout : true, _started : false, // selectedChildWidget: Widget // References the currently selected child widget, if any postCreate : function() { dijit.setWaiRole((this.containerNode || this.domNode), "tabpanel"); this.connect(this.domNode, "onkeypress", this._onKeyPress); }, startup : function() { if (this._started) { return; } var children = this.getChildren(); // Setup each page panel dojo.forEach(children, this._setupChild, this); // Figure out which child to initially display dojo.some(children, function(child) { if (child.selected) { this.selectedChildWidget = child; } return child.selected; }, this); var selected = this.selectedChildWidget; // Default to the first child if (!selected && children[0]) { selected = this.selectedChildWidget = children[0]; selected.selected = true; } if (selected) { this._showChild(selected); } // Now publish information about myself so any StackControllers can // initialize.. dojo.publish(this.id + "-startup", [{ children : children, selected : selected }]); this.inherited("startup", arguments); this._started = true; }, _setupChild : function(/* Widget */page) { // Summary: prepare the given child page.domNode.style.display = "none"; // since we are setting the width/height of the child elements, they // need // to be position:relative, or IE has problems (See bug #2033) page.domNode.style.position = "relative"; return page; // dijit._Widget }, addChild : function(/* Widget */child, /* Integer? */insertIndex) { // summary: Adds a widget to the stack dijit._Container.prototype.addChild.apply(this, arguments); child = this._setupChild(child); if (this._started) { // in case the tab titles have overflowed from one line to two // lines this.layout(); dojo.publish(this.id + "-addChild", [child, insertIndex]); // if this is the first child, then select it if (!this.selectedChildWidget) { this.selectChild(child); } } }, removeChild : function(/* Widget */page) { // summary: Removes the pane from the stack dijit._Container.prototype.removeChild.apply(this, arguments); // If we 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._beingDestroyed) { return; } if (this._started) { // this will notify any tablists to remove a button; do this // first because it may affect sizing dojo.publish(this.id + "-removeChild", [page]); // in case the tab titles now take up one line instead of two // lines this.layout(); } if (this.selectedChildWidget === page) { this.selectedChildWidget = undefined; if (this._started) { var children = this.getChildren(); if (children.length) { this.selectChild(children[0]); } } } }, selectChild : function(/* Widget */page) { // summary: // Show the given widget (which must be one of my children) page = dijit.byId(page); if (this.selectedChildWidget != page) { // Deselect old page and select new one this._transition(page, this.selectedChildWidget); this.selectedChildWidget = page; dojo.publish(this.id + "-selectChild", [page]); } }, _transition : function(/* Widget */newWidget, /* Widget */oldWidget) { if (oldWidget) { this._hideChild(oldWidget); } 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. // page must be visible for resizing to work if (this.doLayout && newWidget.resize) { newWidget.resize(this._containerContentBox || this._contentBox); } }, _adjacent : function(/* Boolean */forward) { // summary: Gets the next/previous child widget in this container // from the current selection var children = this.getChildren(); var index = dojo.indexOf(children, this.selectedChildWidget); index += forward ? 1 : children.length - 1; return children[index % children.length]; // dijit._Widget }, forward : function() { // Summary: advance to next page this.selectChild(this._adjacent(true)); }, back : function() { // Summary: go back to previous page this.selectChild(this._adjacent(false)); }, _onKeyPress : function(e) { dojo.publish(this.id + "-containerKeyPress", [{ e : e, page : this }]); }, layout : function() { if (this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize) { this.selectedChildWidget.resize(this._contentBox); } }, _showChild : function(/* Widget */page) { var children = this.getChildren(); page.isFirstChild = (page == children[0]); page.isLastChild = (page == children[children.length - 1]); page.selected = true; page.domNode.style.display = ""; if (page._loadCheck) { page._loadCheck(); // trigger load in ContentPane } if (page.onShow) { page.onShow(); } }, _hideChild : function(/* Widget */page) { page.selected = false; page.domNode.style.display = "none"; if (page.onHide) { page.onHide(); } }, closeChild : function(/* Widget */page) { // summary // callback when user clicks the [X] to remove a page // if onClose() returns true then remove and destroy the childd var remove = page.onClose(this, page); if (remove) { this.removeChild(page); // makes sure we can clean up executeScripts in ContentPane // onUnLoad page.destroy(); } }, destroy : function() { this._beingDestroyed = true; this.inherited("destroy", arguments); } }); dojo.declare("dijit.layout.StackController", [dijit._Widget, dijit._Templated, dijit._Container], { // summary: // Set of buttons to select a page in a page list. // Monitors the specified StackContainer, and whenever a page is // added, deleted, or selected, updates itself accordingly. templateString : "", // containerId: String // the id of the page container that I point to containerId : "", // buttonWidget: String // the name of the button widget to create to correspond to each // page buttonWidget : "dijit.layout._StackButton", postCreate : function() { dijit.setWaiRole(this.domNode, "tablist"); this.pane2button = {}; // mapping from panes to buttons this._subscriptions = [ dojo.subscribe(this.containerId + "-startup", this, "onStartup"), dojo.subscribe(this.containerId + "-addChild", this, "onAddChild"), dojo.subscribe(this.containerId + "-removeChild", this, "onRemoveChild"), dojo.subscribe(this.containerId + "-selectChild", this, "onSelectChild"), dojo.subscribe(this.containerId + "-containerKeyPress", this, "onContainerKeyPress")]; }, onStartup : function(/* Object */info) { // summary: called after StackContainer has finished // initializing dojo.forEach(info.children, this.onAddChild, this); this.onSelectChild(info.selected); }, destroy : function() { dojo.forEach(this._subscriptions, dojo.unsubscribe); this.inherited("destroy", arguments); }, onAddChild : function(/* Widget */page, /* Integer? */insertIndex) { // summary: // Called whenever a page is added to the container. // Create button corresponding to the page. // add a node that will be promoted to the button widget var refNode = document.createElement("span"); this.domNode.appendChild(refNode); // create an instance of the button widget var cls = dojo.getObject(this.buttonWidget); var button = new cls({ label : page.title, closeButton : page.closable }, refNode); this.addChild(button, insertIndex); this.pane2button[page] = button; page.controlButton = button; // this value might be // overwritten if two tabs // point to same container dojo.connect(button, "onClick", dojo.hitch(this, "onButtonClick", page)); dojo.connect(button, "onClickCloseButton", dojo.hitch(this, "onCloseButtonClick", page)); if (!this._currentChild) { // put the first child into the // tab order button.focusNode.setAttribute("tabIndex", "0"); this._currentChild = page; } }, onRemoveChild : function(/* Widget */page) { // summary: // Called whenever a page is removed from the container. // Remove the button corresponding to the page. if (this._currentChild === page) { this._currentChild = null; } var button = this.pane2button[page]; if (button) { // TODO? if current child { reassign } button.destroy(); } this.pane2button[page] = null; }, onSelectChild : function(/* Widget */page) { // summary: // Called when a page has been selected in the // StackContainer, either by me or by another // StackController if (!page) { return; } if (this._currentChild) { var oldButton = this.pane2button[this._currentChild]; oldButton.setChecked(false); oldButton.focusNode.setAttribute("tabIndex", "-1"); } var newButton = this.pane2button[page]; newButton.setChecked(true); this._currentChild = page; newButton.focusNode.setAttribute("tabIndex", "0"); }, onButtonClick : function(/* Widget */page) { // summary: // Called whenever one of my child buttons is pressed in an // attempt to select a page var container = dijit.byId(this.containerId); // TODO: do // this via // topics? container.selectChild(page); }, onCloseButtonClick : function(/* Widget */page) { // summary: // Called whenever one of my child buttons [X] is pressed in // an attempt to close a page var container = dijit.byId(this.containerId); container.closeChild(page); var b = this.pane2button[this._currentChild]; if (b) { dijit.focus(b.focusNode || b.domNode); } }, // TODO: this is a bit redundant with forward, back api in // StackContainer adjacent : function(/* Boolean */forward) { // find currently focused button in children array var children = this.getChildren(); var current = dojo.indexOf(children, this.pane2button[this._currentChild]); // pick next button to focus on var offset = forward ? 1 : children.length - 1; return children[(current + offset) % children.length]; // dijit._Widget }, onkeypress : function(/* Event */e) { // summary: // Handle keystrokes on the page list, for advancing to // next/previous button // and closing the current page if the page is closable. if (this.disabled || e.altKey) { return; } var forward = true; if (e.ctrlKey || !e._djpage) { var k = dojo.keys; switch (e.keyCode) { case k.LEFT_ARROW : case k.UP_ARROW : case k.PAGE_UP : forward = false; // fall through case k.RIGHT_ARROW : case k.DOWN_ARROW : case k.PAGE_DOWN : this.adjacent(forward).onClick(); dojo.stopEvent(e); break; case k.DELETE : if (this._currentChild.closable) { this.onCloseButtonClick(this._currentChild); } dojo.stopEvent(e); break; default : if (e.ctrlKey) { if (e.keyCode == k.TAB) { this.adjacent(!e.shiftKey).onClick(); dojo.stopEvent(e); } else if (e.keyChar == "w") { if (this._currentChild.closable) { this .onCloseButtonClick(this._currentChild); } dojo.stopEvent(e); // avoid browser tab // closing. } } } } }, onContainerKeyPress : function(/* Object */info) { info.e._djpage = info.page; this.onkeypress(info.e); } }); dojo.declare("dijit.layout._StackButton", dijit.form.ToggleButton, { // summary // Internal widget used by StackContainer. // The button-like or tab-like object you click to select or // delete a page tabIndex : "-1", // StackContainer buttons are not in the tab // order by default postCreate : function(/* Event */evt) { dijit.setWaiRole((this.focusNode || this.domNode), "tab"); this.inherited("postCreate", arguments); }, onClick : function(/* Event */evt) { // summary: This is for TabContainer where the tabs are // rather than button, // so need to set focus explicitly (on some browsers) dijit.focus(this.focusNode); // ... now let StackController catch the event and tell me // what to do }, onClickCloseButton : function(/* Event */evt) { // summary // StackContainer connects to this function; if your widget // contains a close button // then clicking it should call this function. evt.stopPropagation(); } }); // These arguments can be specified for the children of a StackContainer. // 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.) dojo.extend(dijit._Widget, { // title: String // Title of this widget. Used by TabContainer to the name the // tab, etc. title : "", // selected: Boolean // Is this child currently selected? selected : false, // closable: Boolean // True if user can close (destroy) this child, such as (for // example) clicking the X on the tab. closable : false, // true if user can close this tab pane onClose : function() { // summary: Callback if someone tries to close the child, // child will be closed if func returns true return true; } }); }