diff options
Diffstat (limited to 'includes/js/dijit/layout/StackContainer.js')
-rw-r--r-- | includes/js/dijit/layout/StackContainer.js | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/includes/js/dijit/layout/StackContainer.js b/includes/js/dijit/layout/StackContainer.js new file mode 100644 index 0000000..3119972 --- /dev/null +++ b/includes/js/dijit/layout/StackContainer.js @@ -0,0 +1,493 @@ +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.require("dijit.Menu"); +dojo.requireLocalization("dijit", "common", null, "zh,pt,da,tr,ru,de,sv,ja,he,fi,nb,el,ar,ROOT,pt-pt,cs,fr,es,ko,nl,zh-tw,pl,it,hu"); + +dojo.declare( + "dijit.layout.StackContainer", + dijit.layout._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. + // + // + // 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 + // + selectedChildWidget: null, +=====*/ + 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(arguments); + }, + + _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 child + var remove = page.onClose(this, page); + if(remove){ + this.removeChild(page); + // makes sure we can clean up executeScripts in ContentPane onUnLoad + page.destroyRecursive(); + } + }, + + destroy: function(){ + this._beingDestroyed = true; + this.inherited(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: "<span wairole='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>", + + // 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"); + + // TODO: change key from object to id, to get more separation from StackContainer + this.pane2button = {}; // mapping from panes to buttons + this.pane2menu = {}; // mapping from panes to close menu + + 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(){ + for(var pane in this.pane2button){ + this.onRemoveChild(pane); + } + dojo.forEach(this._subscriptions, dojo.unsubscribe); + this.inherited(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 = dojo.doc.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)); + if(page.closable){ + dojo.connect(button, "onClickCloseButton", dojo.hitch(this,"onCloseButtonClick",page)); + // add context menu onto title button + var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); + var closeMenu = new dijit.Menu({targetNodeIds:[button.id], id:button.id+"_Menu"}); + var mItem = new dijit.MenuItem({label:_nlsResources.itemClose}); + dojo.connect(mItem, "onClick", dojo.hitch(this, "onCloseButtonClick", page)); + closeMenu.addChild(mItem); + this.pane2menu[page] = closeMenu; + } + if(!this._currentChild){ // put the first child into the tab order + button.focusNode.setAttribute("tabIndex", "0"); + this._currentChild = page; + } + //make sure all tabs have the same length + if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){ + this._rectifyRtlTabList(); + } + }, + + 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]; + var menu = this.pane2menu[page]; + if (menu){ + menu.destroy(); + } + 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.setAttribute('checked', false); + oldButton.focusNode.setAttribute("tabIndex", "-1"); + } + + var newButton=this.pane2button[page]; + newButton.setAttribute('checked', true); + this._currentChild = page; + newButton.focusNode.setAttribute("tabIndex", "0"); + var container = dijit.byId(this.containerId); + dijit.setWaiState(container.containerNode || container.domNode, "labelledby", newButton.id); + }, + + 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){ + if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !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 = null; + if(e.ctrlKey || !e._djpage){ + var k = dojo.keys; + switch(e.keyCode){ + case k.LEFT_ARROW: + case k.UP_ARROW: + if(!e._djpage){ forward = false; } + break; + case k.PAGE_UP: + if(e.ctrlKey){ forward = false; } + break; + case k.RIGHT_ARROW: + case k.DOWN_ARROW: + if(!e._djpage){ forward = true; } + break; + case k.PAGE_DOWN: + if(e.ctrlKey){ forward = true; } + 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. + } + } + } + // handle page navigation + if(forward !== null){ + this.adjacent(forward).onClick(); + dojo.stopEvent(e); + } + } + }, + + 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(arguments); + }, + + onClick: function(/*Event*/ evt){ + // summary: This is for TabContainer where the tabs are <span> 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; + } +}); + +} |