require({cache:{ 'url:dijit/templates/CheckedMenuItem.html':"\n\t\n\t\t\"\"\n\t\t\n\t\n\t\n\t\n\t \n\n", 'dijit/_base/scroll':function(){ define("dijit/_base/scroll", [ "dojo/window", // windowUtils.scrollIntoView ".." // export symbol to dijit ], function(windowUtils, dijit){ // module: // dijit/_base/scroll // summary: // Back compatibility module, new code should use windowUtils directly instead of using this module. dijit.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){ // summary: // Scroll the passed node into view, if it is not already. // Deprecated, use `windowUtils.scrollIntoView` instead. windowUtils.scrollIntoView(node, pos); }; }); }, 'dijit/_TemplatedMixin':function(){ define("dijit/_TemplatedMixin", [ "dojo/_base/lang", // lang.getObject "dojo/touch", "./_WidgetBase", "dojo/string", // string.substitute string.trim "dojo/cache", // dojo.cache "dojo/_base/array", // array.forEach "dojo/_base/declare", // declare "dojo/dom-construct", // domConstruct.destroy, domConstruct.toDom "dojo/_base/sniff", // has("ie") "dojo/_base/unload", // unload.addOnWindowUnload "dojo/_base/window" // win.doc ], function(lang, touch, _WidgetBase, string, cache, array, declare, domConstruct, has, unload, win) { /*===== var _WidgetBase = dijit._WidgetBase; =====*/ // module: // dijit/_TemplatedMixin // summary: // Mixin for widgets that are instantiated from a template var _TemplatedMixin = declare("dijit._TemplatedMixin", null, { // summary: // Mixin for widgets that are instantiated from a template // templateString: [protected] String // A string that represents the widget template. // Use in conjunction with dojo.cache() to load from a file. templateString: null, // templatePath: [protected deprecated] String // Path to template (HTML file) for this widget relative to dojo.baseUrl. // Deprecated: use templateString with require([... "dojo/text!..."], ...) instead templatePath: null, // skipNodeCache: [protected] Boolean // If using a cached widget template nodes poses issues for a // particular widget class, it can set this property to ensure // that its template is always re-built from a string _skipNodeCache: false, // _earlyTemplatedStartup: Boolean // A fallback to preserve the 1.0 - 1.3 behavior of children in // templates having their startup called before the parent widget // fires postCreate. Defaults to 'false', causing child widgets to // have their .startup() called immediately before a parent widget // .startup(), but always after the parent .postCreate(). Set to // 'true' to re-enable to previous, arguably broken, behavior. _earlyTemplatedStartup: false, /*===== // _attachPoints: [private] String[] // List of widget attribute names associated with data-dojo-attach-point=... in the // template, ex: ["containerNode", "labelNode"] _attachPoints: [], =====*/ /*===== // _attachEvents: [private] Handle[] // List of connections associated with data-dojo-attach-event=... in the // template _attachEvents: [], =====*/ constructor: function(){ this._attachPoints = []; this._attachEvents = []; }, _stringRepl: function(tmpl){ // summary: // Does substitution of ${foo} type properties in template string // tags: // private var className = this.declaredClass, _this = this; // Cache contains a string because we need to do property replacement // do the property replacement return string.substitute(tmpl, this, function(value, key){ if(key.charAt(0) == '!'){ value = lang.getObject(key.substr(1), false, _this); } if(typeof value == "undefined"){ throw new Error(className+" template:"+key); } // a debugging aide if(value == null){ return ""; } // Substitution keys beginning with ! will skip the transform step, // in case a user wishes to insert unescaped markup, e.g. ${!foo} return key.charAt(0) == "!" ? value : // Safer substitution, see heading "Attribute values" in // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 value.toString().replace(/"/g,"""); //TODO: add &? use encodeXML method? }, this); }, buildRendering: function(){ // summary: // Construct the UI for this widget from a template, setting this.domNode. // tags: // protected if(!this.templateString){ this.templateString = cache(this.templatePath, {sanitize: true}); } // Lookup cached version of template, and download to cache if it // isn't there already. Returns either a DomNode or a string, depending on // whether or not the template contains ${foo} replacement parameters. var cached = _TemplatedMixin.getCachedTemplate(this.templateString, this._skipNodeCache); var node; if(lang.isString(cached)){ node = domConstruct.toDom(this._stringRepl(cached)); if(node.nodeType != 1){ // Flag common problems such as templates with multiple top level nodes (nodeType == 11) throw new Error("Invalid template: " + cached); } }else{ // if it's a node, all we have to do is clone it node = cached.cloneNode(true); } this.domNode = node; // Call down to _Widget.buildRendering() to get base classes assigned // TODO: change the baseClass assignment to _setBaseClassAttr this.inherited(arguments); // recurse through the node, looking for, and attaching to, our // attachment points and events, which should be defined on the template node. this._attachTemplateNodes(node, function(n,p){ return n.getAttribute(p); }); this._beforeFillContent(); // hook for _WidgetsInTemplateMixin this._fillContent(this.srcNodeRef); }, _beforeFillContent: function(){ }, _fillContent: function(/*DomNode*/ source){ // summary: // Relocate source contents to templated container node. // this.containerNode must be able to receive children, or exceptions will be thrown. // tags: // protected var dest = this.containerNode; if(source && dest){ while(source.hasChildNodes()){ dest.appendChild(source.firstChild); } } }, _attachTemplateNodes: function(rootNode, getAttrFunc){ // summary: // Iterate through the template and attach functions and nodes accordingly. // Alternately, if rootNode is an array of widgets, then will process data-dojo-attach-point // etc. for those widgets. // description: // Map widget properties and functions to the handlers specified in // the dom node and it's descendants. This function iterates over all // nodes and looks for these properties: // * dojoAttachPoint/data-dojo-attach-point // * dojoAttachEvent/data-dojo-attach-event // rootNode: DomNode|Widget[] // the node to search for properties. All children will be searched. // getAttrFunc: Function // a function which will be used to obtain property for a given // DomNode/Widget // tags: // private var nodes = lang.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*")); var x = lang.isArray(rootNode) ? 0 : -1; for(; x
\n", 'dijit/layout/ScrollingTabController':function(){ require({cache:{ 'url:dijit/layout/templates/ScrollingTabController.html':"
\n\t
\n\t
\n\t
\n\t
\n\t\t
\n\t
\n
", 'url:dijit/layout/templates/_ScrollingTabControllerButton.html':"
\n\t
\n\t\t
\n\t\t\t\"\"\n\t\t\t\n\t\t
\n\t
\n
"}}); define("dijit/layout/ScrollingTabController", [ "dojo/_base/array", // array.forEach "dojo/_base/declare", // declare "dojo/dom-class", // domClass.add domClass.contains "dojo/dom-geometry", // domGeometry.contentBox "dojo/dom-style", // domStyle.style "dojo/_base/fx", // Animation "dojo/_base/lang", // lang.hitch "dojo/query", // query "dojo/_base/sniff", // has("ie"), has("webkit"), has("quirks") "../registry", // registry.byId() "dojo/text!./templates/ScrollingTabController.html", "dojo/text!./templates/_ScrollingTabControllerButton.html", "./TabController", "./utils", // marginBox2contextBox, layoutChildren "../_WidgetsInTemplateMixin", "../Menu", "../MenuItem", "../form/Button", "../_HasDropDown", "dojo/NodeList-dom" // NodeList.style ], function(array, declare, domClass, domGeometry, domStyle, fx, lang, query, has, registry, tabControllerTemplate, buttonTemplate, TabController, layoutUtils, _WidgetsInTemplateMixin, Menu, MenuItem, Button, _HasDropDown){ /*===== var _WidgetsInTemplateMixin = dijit._WidgetsInTemplateMixin; var Menu = dijit.Menu; var _HasDropDown = dijit._HasDropDown; var TabController = dijit.layout.TabController; =====*/ // module: // dijit/layout/ScrollingTabController // summary: // Set of tabs with left/right arrow keys and a menu to switch between tabs not // all fitting on a single row. var ScrollingTabController = declare("dijit.layout.ScrollingTabController", [TabController, _WidgetsInTemplateMixin], { // summary: // Set of tabs with left/right arrow keys and a menu to switch between tabs not // all fitting on a single row. // Works only for horizontal tabs (either above or below the content, not to the left // or right). // tags: // private baseClass: "dijitTabController dijitScrollingTabController", templateString: tabControllerTemplate, // useMenu: [const] Boolean // True if a menu should be used to select tabs when they are too // wide to fit the TabContainer, false otherwise. useMenu: true, // useSlider: [const] Boolean // True if a slider should be used to select tabs when they are too // wide to fit the TabContainer, false otherwise. useSlider: true, // tabStripClass: [const] String // The css class to apply to the tab strip, if it is visible. tabStripClass: "", widgetsInTemplate: true, // _minScroll: Number // The distance in pixels from the edge of the tab strip which, // if a scroll animation is less than, forces the scroll to // go all the way to the left/right. _minScroll: 5, // Override default behavior mapping class to DOMNode _setClassAttr: { node: "containerNode", type: "class" }, buildRendering: function(){ this.inherited(arguments); var n = this.domNode; this.scrollNode = this.tablistWrapper; this._initButtons(); if(!this.tabStripClass){ this.tabStripClass = "dijitTabContainer" + this.tabPosition.charAt(0).toUpperCase() + this.tabPosition.substr(1).replace(/-.*/, "") + "None"; domClass.add(n, "tabStrip-disabled") } domClass.add(this.tablistWrapper, this.tabStripClass); }, onStartup: function(){ this.inherited(arguments); // TabController is hidden until it finishes drawing, to give // a less visually jumpy instantiation. When it's finished, set visibility to "" // to that the tabs are hidden/shown depending on the container's visibility setting. domStyle.set(this.domNode, "visibility", ""); this._postStartup = true; }, onAddChild: function(page, insertIndex){ this.inherited(arguments); // changes to the tab button label or iconClass will have changed the width of the // buttons, so do a resize array.forEach(["label", "iconClass"], function(attr){ this.pane2watches[page.id].push( this.pane2button[page.id].watch(attr, lang.hitch(this, function(){ if(this._postStartup && this._dim){ this.resize(this._dim); } })) ); }, this); // Increment the width of the wrapper when a tab is added // This makes sure that the buttons never wrap. // The value 200 is chosen as it should be bigger than most // Tab button widths. domStyle.set(this.containerNode, "width", (domStyle.get(this.containerNode, "width") + 200) + "px"); }, onRemoveChild: function(page, insertIndex){ // null out _selectedTab because we are about to delete that dom node var button = this.pane2button[page.id]; if(this._selectedTab === button.domNode){ this._selectedTab = null; } this.inherited(arguments); }, _initButtons: function(){ // summary: // Creates the buttons used to scroll to view tabs that // may not be visible if the TabContainer is too narrow. // Make a list of the buttons to display when the tab labels become // wider than the TabContainer, and hide the other buttons. // Also gets the total width of the displayed buttons. this._btnWidth = 0; this._buttons = query("> .tabStripButton", this.domNode).filter(function(btn){ if((this.useMenu && btn == this._menuBtn.domNode) || (this.useSlider && (btn == this._rightBtn.domNode || btn == this._leftBtn.domNode))){ this._btnWidth += domGeometry.getMarginSize(btn).w; return true; }else{ domStyle.set(btn, "display", "none"); return false; } }, this); }, _getTabsWidth: function(){ var children = this.getChildren(); if(children.length){ var leftTab = children[this.isLeftToRight() ? 0 : children.length - 1].domNode, rightTab = children[this.isLeftToRight() ? children.length - 1 : 0].domNode; return rightTab.offsetLeft + domStyle.get(rightTab, "width") - leftTab.offsetLeft; }else{ return 0; } }, _enableBtn: function(width){ // summary: // Determines if the tabs are wider than the width of the TabContainer, and // thus that we need to display left/right/menu navigation buttons. var tabsWidth = this._getTabsWidth(); width = width || domStyle.get(this.scrollNode, "width"); return tabsWidth > 0 && width < tabsWidth; }, resize: function(dim){ // summary: // Hides or displays the buttons used to scroll the tab list and launch the menu // that selects tabs. // Save the dimensions to be used when a child is renamed. this._dim = dim; // Set my height to be my natural height (tall enough for one row of tab labels), // and my content-box width based on margin-box width specified in dim parameter. // But first reset scrollNode.height in case it was set by layoutChildren() call // in a previous run of this method. this.scrollNode.style.height = "auto"; var cb = this._contentBox = layoutUtils.marginBox2contentBox(this.domNode, {h: 0, w: dim.w}); cb.h = this.scrollNode.offsetHeight; domGeometry.setContentSize(this.domNode, cb); // Show/hide the left/right/menu navigation buttons depending on whether or not they // are needed. var enable = this._enableBtn(this._contentBox.w); this._buttons.style("display", enable ? "" : "none"); // Position and size the navigation buttons and the tablist this._leftBtn.layoutAlign = "left"; this._rightBtn.layoutAlign = "right"; this._menuBtn.layoutAlign = this.isLeftToRight() ? "right" : "left"; layoutUtils.layoutChildren(this.domNode, this._contentBox, [this._menuBtn, this._leftBtn, this._rightBtn, {domNode: this.scrollNode, layoutAlign: "client"}]); // set proper scroll so that selected tab is visible if(this._selectedTab){ if(this._anim && this._anim.status() == "playing"){ this._anim.stop(); } this.scrollNode.scrollLeft = this._convertToScrollLeft(this._getScrollForSelectedTab()); } // Enable/disabled left right buttons depending on whether or not user can scroll to left or right this._setButtonClass(this._getScroll()); this._postResize = true; // Return my size so layoutChildren() can use it. // Also avoids IE9 layout glitch on browser resize when scroll buttons present return {h: this._contentBox.h, w: dim.w}; }, _getScroll: function(){ // summary: // Returns the current scroll of the tabs where 0 means // "scrolled all the way to the left" and some positive number, based on # // of pixels of possible scroll (ex: 1000) means "scrolled all the way to the right" return (this.isLeftToRight() || has("ie") < 8 || (has("ie") && has("quirks")) || has("webkit")) ? this.scrollNode.scrollLeft : domStyle.get(this.containerNode, "width") - domStyle.get(this.scrollNode, "width") + (has("ie") == 8 ? -1 : 1) * this.scrollNode.scrollLeft; }, _convertToScrollLeft: function(val){ // summary: // Given a scroll value where 0 means "scrolled all the way to the left" // and some positive number, based on # of pixels of possible scroll (ex: 1000) // means "scrolled all the way to the right", return value to set this.scrollNode.scrollLeft // to achieve that scroll. // // This method is to adjust for RTL funniness in various browsers and versions. if(this.isLeftToRight() || has("ie") < 8 || (has("ie") && has("quirks")) || has("webkit")){ return val; }else{ var maxScroll = domStyle.get(this.containerNode, "width") - domStyle.get(this.scrollNode, "width"); return (has("ie") == 8 ? -1 : 1) * (val - maxScroll); } }, onSelectChild: function(/*dijit._Widget*/ page){ // summary: // Smoothly scrolls to a tab when it is selected. var tab = this.pane2button[page.id]; if(!tab || !page){return;} var node = tab.domNode; // Save the selection if(node != this._selectedTab){ this._selectedTab = node; // Scroll to the selected tab, except on startup, when scrolling is handled in resize() if(this._postResize){ var sl = this._getScroll(); if(sl > node.offsetLeft || sl + domStyle.get(this.scrollNode, "width") < node.offsetLeft + domStyle.get(node, "width")){ this.createSmoothScroll().play(); } } } this.inherited(arguments); }, _getScrollBounds: function(){ // summary: // Returns the minimum and maximum scroll setting to show the leftmost and rightmost // tabs (respectively) var children = this.getChildren(), scrollNodeWidth = domStyle.get(this.scrollNode, "width"), // about 500px containerWidth = domStyle.get(this.containerNode, "width"), // 50,000px maxPossibleScroll = containerWidth - scrollNodeWidth, // scrolling until right edge of containerNode visible tabsWidth = this._getTabsWidth(); if(children.length && tabsWidth > scrollNodeWidth){ // Scrolling should happen return { min: this.isLeftToRight() ? 0 : children[children.length-1].domNode.offsetLeft, max: this.isLeftToRight() ? (children[children.length-1].domNode.offsetLeft + domStyle.get(children[children.length-1].domNode, "width")) - scrollNodeWidth : maxPossibleScroll }; }else{ // No scrolling needed, all tabs visible, we stay either scrolled to far left or far right (depending on dir) var onlyScrollPosition = this.isLeftToRight() ? 0 : maxPossibleScroll; return { min: onlyScrollPosition, max: onlyScrollPosition }; } }, _getScrollForSelectedTab: function(){ // summary: // Returns the scroll value setting so that the selected tab // will appear in the center var w = this.scrollNode, n = this._selectedTab, scrollNodeWidth = domStyle.get(this.scrollNode, "width"), scrollBounds = this._getScrollBounds(); // TODO: scroll minimal amount (to either right or left) so that // selected tab is fully visible, and just return if it's already visible? var pos = (n.offsetLeft + domStyle.get(n, "width")/2) - scrollNodeWidth/2; pos = Math.min(Math.max(pos, scrollBounds.min), scrollBounds.max); // TODO: // If scrolling close to the left side or right side, scroll // all the way to the left or right. See this._minScroll. // (But need to make sure that doesn't scroll the tab out of view...) return pos; }, createSmoothScroll: function(x){ // summary: // Creates a dojo._Animation object that smoothly scrolls the tab list // either to a fixed horizontal pixel value, or to the selected tab. // description: // If an number argument is passed to the function, that horizontal // pixel position is scrolled to. Otherwise the currently selected // tab is scrolled to. // x: Integer? // An optional pixel value to scroll to, indicating distance from left. // Calculate position to scroll to if(arguments.length > 0){ // position specified by caller, just make sure it's within bounds var scrollBounds = this._getScrollBounds(); x = Math.min(Math.max(x, scrollBounds.min), scrollBounds.max); }else{ // scroll to center the current tab x = this._getScrollForSelectedTab(); } if(this._anim && this._anim.status() == "playing"){ this._anim.stop(); } var self = this, w = this.scrollNode, anim = new fx.Animation({ beforeBegin: function(){ if(this.curve){ delete this.curve; } var oldS = w.scrollLeft, newS = self._convertToScrollLeft(x); anim.curve = new fx._Line(oldS, newS); }, onAnimate: function(val){ w.scrollLeft = val; } }); this._anim = anim; // Disable/enable left/right buttons according to new scroll position this._setButtonClass(x); return anim; // dojo._Animation }, _getBtnNode: function(/*Event*/ e){ // summary: // Gets a button DOM node from a mouse click event. // e: // The mouse click event. var n = e.target; while(n && !domClass.contains(n, "tabStripButton")){ n = n.parentNode; } return n; }, doSlideRight: function(/*Event*/ e){ // summary: // Scrolls the menu to the right. // e: // The mouse click event. this.doSlide(1, this._getBtnNode(e)); }, doSlideLeft: function(/*Event*/ e){ // summary: // Scrolls the menu to the left. // e: // The mouse click event. this.doSlide(-1,this._getBtnNode(e)); }, doSlide: function(/*Number*/ direction, /*DomNode*/ node){ // summary: // Scrolls the tab list to the left or right by 75% of the widget width. // direction: // If the direction is 1, the widget scrolls to the right, if it is // -1, it scrolls to the left. if(node && domClass.contains(node, "dijitTabDisabled")){return;} var sWidth = domStyle.get(this.scrollNode, "width"); var d = (sWidth * 0.75) * direction; var to = this._getScroll() + d; this._setButtonClass(to); this.createSmoothScroll(to).play(); }, _setButtonClass: function(/*Number*/ scroll){ // summary: // Disables the left scroll button if the tabs are scrolled all the way to the left, // or the right scroll button in the opposite case. // scroll: Integer // amount of horizontal scroll var scrollBounds = this._getScrollBounds(); this._leftBtn.set("disabled", scroll <= scrollBounds.min); this._rightBtn.set("disabled", scroll >= scrollBounds.max); } }); var ScrollingTabControllerButtonMixin = declare("dijit.layout._ScrollingTabControllerButtonMixin", null, { baseClass: "dijitTab tabStripButton", templateString: buttonTemplate, // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be // able to tab to the left/right/menu buttons tabIndex: "", // Similarly, override FormWidget.isFocusable() because clicking a button shouldn't focus it // either (this override avoids focus() call in FormWidget.js) isFocusable: function(){ return false; } }); /*===== ScrollingTabControllerButtonMixin = dijit.layout._ScrollingTabControllerButtonMixin; =====*/ // Class used in template declare("dijit.layout._ScrollingTabControllerButton", [Button, ScrollingTabControllerButtonMixin]); // Class used in template declare( "dijit.layout._ScrollingTabControllerMenuButton", [Button, _HasDropDown, ScrollingTabControllerButtonMixin], { // id of the TabContainer itself containerId: "", // -1 so user can't tab into the button, but so that button can still be focused programatically. // Because need to move focus to the button (or somewhere) before the menu is hidden or IE6 will crash. tabIndex: "-1", isLoaded: function(){ // recreate menu every time, in case the TabContainer's list of children (or their icons/labels) have changed return false; }, loadDropDown: function(callback){ this.dropDown = new Menu({ id: this.containerId + "_menu", dir: this.dir, lang: this.lang, textDir: this.textDir }); var container = registry.byId(this.containerId); array.forEach(container.getChildren(), function(page){ var menuItem = new MenuItem({ id: page.id + "_stcMi", label: page.title, iconClass: page.iconClass, dir: page.dir, lang: page.lang, textDir: page.textDir, onClick: function(){ container.selectChild(page); } }); this.dropDown.addChild(menuItem); }, this); callback(); }, closeDropDown: function(/*Boolean*/ focus){ this.inherited(arguments); if(this.dropDown){ this.dropDown.destroyRecursive(); delete this.dropDown; } } }); return ScrollingTabController; }); }, 'dijit/place':function(){ define("dijit/place", [ "dojo/_base/array", // array.forEach array.map array.some "dojo/dom-geometry", // domGeometry.position "dojo/dom-style", // domStyle.getComputedStyle "dojo/_base/kernel", // kernel.deprecated "dojo/_base/window", // win.body "./Viewport", // getEffectiveBox "." // dijit (defining dijit.place to match API doc) ], function(array, domGeometry, domStyle, kernel, win, Viewport, dijit){ // module: // dijit/place // summary: // Code to place a popup relative to another node function _place(/*DomNode*/ node, choices, layoutNode, aroundNodeCoords){ // summary: // Given a list of spots to put node, put it at the first spot where it fits, // of if it doesn't fit anywhere then the place with the least overflow // choices: Array // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} } // Above example says to put the top-left corner of the node at (10,20) // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size) // for things like tooltip, they are displayed differently (and have different dimensions) // based on their orientation relative to the parent. This adjusts the popup based on orientation. // It also passes in the available size for the popup, which is useful for tooltips to // tell them that their width is limited to a certain amount. layoutNode() may return a value expressing // how much the popup had to be modified to fit into the available space. This is used to determine // what the best placement is. // aroundNodeCoords: Object // Size of aroundNode, ex: {w: 200, h: 50} // get {x: 10, y: 10, w: 100, h:100} type obj representing position of // viewport over document var view = Viewport.getEffectiveBox(node.ownerDocument); // This won't work if the node is inside a
, // so reattach it to win.doc.body. (Otherwise, the positioning will be wrong // and also it might get cutoff) if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){ win.body().appendChild(node); } var best = null; array.some(choices, function(choice){ var corner = choice.corner; var pos = choice.pos; var overflow = 0; // calculate amount of space available given specified position of node var spaceAvailable = { w: { 'L': view.l + view.w - pos.x, 'R': pos.x - view.l, 'M': view.w }[corner.charAt(1)], h: { 'T': view.t + view.h - pos.y, 'B': pos.y - view.t, 'M': view.h }[corner.charAt(0)] }; // Clear left/right position settings set earlier so they don't interfere with calculations, // specifically when layoutNode() (a.k.a. Tooltip.orient()) measures natural width of Tooltip var s = node.style; s.left = s.right = "auto"; // configure node to be displayed in given position relative to button // (need to do this in order to get an accurate size for the node, because // a tooltip's size changes based on position, due to triangle) if(layoutNode){ var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords); overflow = typeof res == "undefined" ? 0 : res; } // get node's size var style = node.style; var oldDisplay = style.display; var oldVis = style.visibility; if(style.display == "none"){ style.visibility = "hidden"; style.display = ""; } var bb = domGeometry.position(node); style.display = oldDisplay; style.visibility = oldVis; // coordinates and size of node with specified corner placed at pos, // and clipped by viewport var startXpos = { 'L': pos.x, 'R': pos.x - bb.w, 'M': Math.max(view.l, Math.min(view.l + view.w, pos.x + (bb.w >> 1)) - bb.w) // M orientation is more flexible }[corner.charAt(1)], startYpos = { 'T': pos.y, 'B': pos.y - bb.h, 'M': Math.max(view.t, Math.min(view.t + view.h, pos.y + (bb.h >> 1)) - bb.h) }[corner.charAt(0)], startX = Math.max(view.l, startXpos), startY = Math.max(view.t, startYpos), endX = Math.min(view.l + view.w, startXpos + bb.w), endY = Math.min(view.t + view.h, startYpos + bb.h), width = endX - startX, height = endY - startY; overflow += (bb.w - width) + (bb.h - height); if(best == null || overflow < best.overflow){ best = { corner: corner, aroundCorner: choice.aroundCorner, x: startX, y: startY, w: width, h: height, overflow: overflow, spaceAvailable: spaceAvailable }; } return !overflow; }); // In case the best position is not the last one we checked, need to call // layoutNode() again. if(best.overflow && layoutNode){ layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords); } // And then position the node. Do this last, after the layoutNode() above // has sized the node, due to browser quirks when the viewport is scrolled // (specifically that a Tooltip will shrink to fit as though the window was // scrolled to the left). var s = node.style; s.top = best.y + "px"; s.left = best.x + "px"; s.right = "auto"; // needed for FF or else tooltip goes to far left return best; } /*===== dijit.place.__Position = function(){ // x: Integer // horizontal coordinate in pixels, relative to document body // y: Integer // vertical coordinate in pixels, relative to document body this.x = x; this.y = y; }; =====*/ /*===== dijit.place.__Rectangle = function(){ // x: Integer // horizontal offset in pixels, relative to document body // y: Integer // vertical offset in pixels, relative to document body // w: Integer // width in pixels. Can also be specified as "width" for backwards-compatibility. // h: Integer // height in pixels. Can also be specified as "height" from backwards-compatibility. this.x = x; this.y = y; this.w = w; this.h = h; }; =====*/ return (dijit.place = { // summary: // Code to place a DOMNode relative to another DOMNode. // Load using require(["dijit/place"], function(place){ ... }). at: function(node, pos, corners, padding){ // summary: // Positions one of the node's corners at specified position // such that node is fully visible in viewport. // description: // NOTE: node is assumed to be absolutely or relatively positioned. // node: DOMNode // The node to position // pos: dijit.place.__Position // Object like {x: 10, y: 20} // corners: String[] // Array of Strings representing order to try corners in, like ["TR", "BL"]. // Possible values are: // * "BL" - bottom left // * "BR" - bottom right // * "TL" - top left // * "TR" - top right // padding: dijit.place.__Position? // optional param to set padding, to put some buffer around the element you want to position. // example: // Try to place node's top right corner at (10,20). // If that makes node go (partially) off screen, then try placing // bottom left corner at (10,20). // | place(node, {x: 10, y: 20}, ["TR", "BL"]) var choices = array.map(corners, function(corner){ var c = { corner: corner, pos: {x:pos.x,y:pos.y} }; if(padding){ c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x; c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y; } return c; }); return _place(node, choices); }, around: function( /*DomNode*/ node, /*DomNode || dijit.place.__Rectangle*/ anchor, /*String[]*/ positions, /*Boolean*/ leftToRight, /*Function?*/ layoutNode){ // summary: // Position node adjacent or kitty-corner to anchor // such that it's fully visible in viewport. // // description: // Place node such that corner of node touches a corner of // aroundNode, and that node is fully visible. // // anchor: // Either a DOMNode or a __Rectangle (object with x, y, width, height). // // positions: // Ordered list of positions to try matching up. // * before: places drop down to the left of the anchor node/widget, or to the right in the case // of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down // with the top of the anchor, or the bottom of the drop down with bottom of the anchor. // * after: places drop down to the right of the anchor node/widget, or to the left in the case // of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down // with the top of the anchor, or the bottom of the drop down with bottom of the anchor. // * before-centered: centers drop down to the left of the anchor node/widget, or to the right // in the case of RTL scripts like Hebrew and Arabic // * after-centered: centers drop down to the right of the anchor node/widget, or to the left // in the case of RTL scripts like Hebrew and Arabic // * above-centered: drop down is centered above anchor node // * above: drop down goes above anchor node, left sides aligned // * above-alt: drop down goes above anchor node, right sides aligned // * below-centered: drop down is centered above anchor node // * below: drop down goes below anchor node // * below-alt: drop down goes below anchor node, right sides aligned // // layoutNode: Function(node, aroundNodeCorner, nodeCorner) // For things like tooltip, they are displayed differently (and have different dimensions) // based on their orientation relative to the parent. This adjusts the popup based on orientation. // // leftToRight: // True if widget is LTR, false if widget is RTL. Affects the behavior of "above" and "below" // positions slightly. // // example: // | placeAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'}); // This will try to position node such that node's top-left corner is at the same position // as the bottom left corner of the aroundNode (ie, put node below // aroundNode, with left edges aligned). If that fails it will try to put // the bottom-right corner of node where the top right corner of aroundNode is // (ie, put node above aroundNode, with right edges aligned) // // if around is a DOMNode (or DOMNode id), convert to coordinates var aroundNodePos = (typeof anchor == "string" || "offsetWidth" in anchor) ? domGeometry.position(anchor, true) : anchor; // Compute position and size of visible part of anchor (it may be partially hidden by ancestor nodes w/scrollbars) if(anchor.parentNode){ // ignore nodes between position:relative and position:absolute var sawPosAbsolute = domStyle.getComputedStyle(anchor).position == "absolute"; var parent = anchor.parentNode; while(parent && parent.nodeType == 1 && parent.nodeName != "BODY"){ //ignoring the body will help performance var parentPos = domGeometry.position(parent, true), pcs = domStyle.getComputedStyle(parent); if(/relative|absolute/.test(pcs.position)){ sawPosAbsolute = false; } if(!sawPosAbsolute && /hidden|auto|scroll/.test(pcs.overflow)){ var bottomYCoord = Math.min(aroundNodePos.y + aroundNodePos.h, parentPos.y + parentPos.h); var rightXCoord = Math.min(aroundNodePos.x + aroundNodePos.w, parentPos.x + parentPos.w); aroundNodePos.x = Math.max(aroundNodePos.x, parentPos.x); aroundNodePos.y = Math.max(aroundNodePos.y, parentPos.y); aroundNodePos.h = bottomYCoord - aroundNodePos.y; aroundNodePos.w = rightXCoord - aroundNodePos.x; } if(pcs.position == "absolute"){ sawPosAbsolute = true; } parent = parent.parentNode; } } var x = aroundNodePos.x, y = aroundNodePos.y, width = "w" in aroundNodePos ? aroundNodePos.w : (aroundNodePos.w = aroundNodePos.width), height = "h" in aroundNodePos ? aroundNodePos.h : (kernel.deprecated("place.around: dijit.place.__Rectangle: { x:"+x+", y:"+y+", height:"+aroundNodePos.height+", width:"+width+" } has been deprecated. Please use { x:"+x+", y:"+y+", h:"+aroundNodePos.height+", w:"+width+" }", "", "2.0"), aroundNodePos.h = aroundNodePos.height); // Convert positions arguments into choices argument for _place() var choices = []; function push(aroundCorner, corner){ choices.push({ aroundCorner: aroundCorner, corner: corner, pos: { x: { 'L': x, 'R': x + width, 'M': x + (width >> 1) }[aroundCorner.charAt(1)], y: { 'T': y, 'B': y + height, 'M': y + (height >> 1) }[aroundCorner.charAt(0)] } }) } array.forEach(positions, function(pos){ var ltr = leftToRight; switch(pos){ case "above-centered": push("TM", "BM"); break; case "below-centered": push("BM", "TM"); break; case "after-centered": ltr = !ltr; // fall through case "before-centered": push(ltr ? "ML" : "MR", ltr ? "MR" : "ML"); break; case "after": ltr = !ltr; // fall through case "before": push(ltr ? "TL" : "TR", ltr ? "TR" : "TL"); push(ltr ? "BL" : "BR", ltr ? "BR" : "BL"); break; case "below-alt": ltr = !ltr; // fall through case "below": // first try to align left borders, next try to align right borders (or reverse for RTL mode) push(ltr ? "BL" : "BR", ltr ? "TL" : "TR"); push(ltr ? "BR" : "BL", ltr ? "TR" : "TL"); break; case "above-alt": ltr = !ltr; // fall through case "above": // first try to align left borders, next try to align right borders (or reverse for RTL mode) push(ltr ? "TL" : "TR", ltr ? "BL" : "BR"); push(ltr ? "TR" : "TL", ltr ? "BR" : "BL"); break; default: // To assist dijit/_base/place, accept arguments of type {aroundCorner: "BL", corner: "TL"}. // Not meant to be used directly. push(pos.aroundCorner, pos.corner); } }); var position = _place(node, choices, layoutNode, {w: width, h: height}); position.aroundNodePos = aroundNodePos; return position; } }); }); }, 'dijit/_HasDropDown':function(){ define("dijit/_HasDropDown", [ "dojo/_base/declare", // declare "dojo/_base/Deferred", "dojo/_base/event", // event.stop "dojo/dom", // dom.isDescendant "dojo/dom-attr", // domAttr.set "dojo/dom-class", // domClass.add domClass.contains domClass.remove "dojo/dom-geometry", // domGeometry.marginBox domGeometry.position "dojo/dom-style", // domStyle.set "dojo/has", "dojo/keys", // keys.DOWN_ARROW keys.ENTER keys.ESCAPE "dojo/_base/lang", // lang.hitch lang.isFunction "dojo/touch", "dojo/_base/window", // win.doc "./registry", // registry.byNode() "./focus", "./popup", "./_FocusMixin", "./Viewport" ], function(declare, Deferred, event,dom, domAttr, domClass, domGeometry, domStyle, has, keys, lang, touch, win, registry, focus, popup, _FocusMixin, Viewport){ /*===== var _FocusMixin = dijit._FocusMixin; =====*/ // module: // dijit/_HasDropDown // summary: // Mixin for widgets that need drop down ability. return declare("dijit._HasDropDown", _FocusMixin, { // summary: // Mixin for widgets that need drop down ability. // _buttonNode: [protected] DomNode // The button/icon/node to click to display the drop down. // Can be set via a data-dojo-attach-point assignment. // If missing, then either focusNode or domNode (if focusNode is also missing) will be used. _buttonNode: null, // _arrowWrapperNode: [protected] DomNode // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending // on where the drop down is set to be positioned. // Can be set via a data-dojo-attach-point assignment. // If missing, then _buttonNode will be used. _arrowWrapperNode: null, // _popupStateNode: [protected] DomNode // The node to set the popupActive class on. // Can be set via a data-dojo-attach-point assignment. // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used. _popupStateNode: null, // _aroundNode: [protected] DomNode // The node to display the popup around. // Can be set via a data-dojo-attach-point assignment. // If missing, then domNode will be used. _aroundNode: null, // dropDown: [protected] Widget // The widget to display as a popup. This widget *must* be // defined before the startup function is called. dropDown: null, // autoWidth: [protected] Boolean // Set to true to make the drop down at least as wide as this // widget. Set to false if the drop down should just be its // default width autoWidth: true, // forceWidth: [protected] Boolean // Set to true to make the drop down exactly as wide as this // widget. Overrides autoWidth. forceWidth: false, // maxHeight: [protected] Integer // The max height for our dropdown. // Any dropdown taller than this will have scrollbars. // Set to 0 for no max height, or -1 to limit height to available space in viewport maxHeight: 0, // dropDownPosition: [const] String[] // This variable controls the position of the drop down. // It's an array of strings with the following values: // // * before: places drop down to the left of the target node/widget, or to the right in // the case of RTL scripts like Hebrew and Arabic // * after: places drop down to the right of the target node/widget, or to the left in // the case of RTL scripts like Hebrew and Arabic // * above: drop down goes above target node // * below: drop down goes below target node // // The list is positions is tried, in order, until a position is found where the drop down fits // within the viewport. // dropDownPosition: ["below","above"], // _stopClickEvents: Boolean // When set to false, the click events will not be stopped, in // case you want to use them in your subwidget _stopClickEvents: true, _onDropDownMouseDown: function(/*Event*/ e){ // summary: // Callback when the user mousedown's on the arrow icon if(this.disabled || this.readOnly){ return; } // Prevent default to stop things like text selection, but don't stop propogation, so that: // 1. TimeTextBox etc. can focusthe on mousedown // 2. dropDownButtonActive class applied by _CssStateMixin (on button depress) // 3. user defined onMouseDown handler fires // // Also, don't call preventDefault() on MSPointerDown event (on IE10) because that prevents the button // from getting focus, and then the focus manager doesn't know what's going on (#17262) if(e.type != "MSPointerDown" && e.type != "pointerdown"){ e.preventDefault(); } this._docHandler = this.connect(win.doc, touch.release, "_onDropDownMouseUp"); this.toggleDropDown(); }, _onDropDownMouseUp: function(/*Event?*/ e){ // summary: // Callback when the user lifts their mouse after mouse down on the arrow icon. // If the drop down is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our // drop down widget. If the event is missing, then we are not // a mouseup event. // // This is useful for the common mouse movement pattern // with native browser and doesn't // generate touchstart/touchend events. Pretend we just got a mouse down / mouse up. // The if(has("ios") is necessary since IE and desktop safari get spurious onclick events // when there are nested tables (specifically, clicking on a table that holds a dijit.form.Select, // but not on the Select itself, causes an onclick event on the Select) this._onDropDownMouseDown(e); this._onDropDownMouseUp(e); } // The drop down was already opened on mousedown/keydown; just need to call stopEvent(). if(this._stopClickEvents){ event.stop(e); } }, buildRendering: function(){ this.inherited(arguments); this._buttonNode = this._buttonNode || this.focusNode || this.domNode; this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode; // Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow // based on where drop down will normally appear var defaultPos = { "after" : this.isLeftToRight() ? "Right" : "Left", "before" : this.isLeftToRight() ? "Left" : "Right", "above" : "Up", "below" : "Down", "left" : "Left", "right" : "Right" }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down"; domClass.add(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton"); }, postCreate: function(){ // summary: // set up nodes and connect our mouse and keyboard events this.inherited(arguments); this.connect(this._buttonNode, touch.press, "_onDropDownMouseDown"); this.connect(this._buttonNode, "onclick", "_onDropDownClick"); this.connect(this.focusNode, "onkeydown", "_onKey"); this.connect(this.focusNode, "onkeyup", "_onKeyUp"); }, destroy: function(){ if(this.dropDown){ // Destroy the drop down, unless it's already been destroyed. This can happen because // the drop down is a direct child of even though it's logically my child. if(!this.dropDown._destroyed){ this.dropDown.destroyRecursive(); } delete this.dropDown; } this.inherited(arguments); }, _onKey: function(/*Event*/ e){ // summary: // Callback when the user presses a key while focused on the button node if(this.disabled || this.readOnly){ return; } var d = this.dropDown, target = e.target; if(d && this._opened && d.handleKey){ if(d.handleKey(e) === false){ /* false return code means that the drop down handled the key */ event.stop(e); return; } } if(d && this._opened && e.keyCode == keys.ESCAPE){ this.closeDropDown(); event.stop(e); }else if(!this._opened && (e.keyCode == keys.DOWN_ARROW || ( (e.keyCode == keys.ENTER || e.keyCode == dojo.keys.SPACE) && //ignore enter and space if the event is for a text input ((target.tagName || "").toLowerCase() !== 'input' || (target.type && target.type.toLowerCase() !== 'text'))))){ // Toggle the drop down, but wait until keyup so that the drop down doesn't // get a stray keyup event, or in the case of key-repeat (because user held // down key for too long), stray keydown events this._toggleOnKeyUp = true; event.stop(e); } }, _onKeyUp: function(){ if(this._toggleOnKeyUp){ delete this._toggleOnKeyUp; this.toggleDropDown(); var d = this.dropDown; // drop down may not exist until toggleDropDown() call if(d && d.focus){ setTimeout(lang.hitch(d, "focus"), 1); } } }, _onBlur: function(){ // summary: // Called magically when focus has shifted away from this widget and it's dropdown // Don't focus on button if the user has explicitly focused on something else (happens // when user clicks another control causing the current popup to close).. // But if focus is inside of the drop down then reset focus to me, because IE doesn't like // it when you display:none a node with focus. var focusMe = focus.curNode && this.dropDown && dom.isDescendant(focus.curNode, this.dropDown.domNode); this.closeDropDown(focusMe); this.inherited(arguments); }, isLoaded: function(){ // summary: // Returns true if the dropdown exists and it's data is loaded. This can // be overridden in order to force a call to loadDropDown(). // tags: // protected return true; }, loadDropDown: function(/*Function*/ loadCallback){ // summary: // Creates the drop down if it doesn't exist, loads the data // if there's an href and it hasn't been loaded yet, and then calls // the given callback. // tags: // protected // TODO: for 2.0, change API to return a Deferred, instead of calling loadCallback? loadCallback(); }, loadAndOpenDropDown: function(){ // summary: // Creates the drop down if it doesn't exist, loads the data // if there's an href and it hasn't been loaded yet, and // then opens the drop down. This is basically a callback when the // user presses the down arrow button to open the drop down. // returns: Deferred // Deferred for the drop down widget that // fires when drop down is created and loaded // tags: // protected var d = new Deferred(), afterLoad = lang.hitch(this, function(){ this.openDropDown(); d.resolve(this.dropDown); }); if(!this.isLoaded()){ this.loadDropDown(afterLoad); }else{ afterLoad(); } return d; }, toggleDropDown: function(){ // summary: // Callback when the user presses the down arrow button or presses // the down arrow key to open/close the drop down. // Toggle the drop-down widget; if it is up, close it, if not, open it // tags: // protected if(this.disabled || this.readOnly){ return; } if(!this._opened){ this.loadAndOpenDropDown(); }else{ this.closeDropDown(); } }, openDropDown: function(){ // summary: // Opens the dropdown for this widget. To be called only when this.dropDown // has been created and is ready to display (ie, it's data is loaded). // returns: // return value of dijit.popup.open() // tags: // protected var dropDown = this.dropDown, ddNode = dropDown.domNode, aroundNode = this._aroundNode || this.domNode, self = this; // Prepare our popup's height and honor maxHeight if it exists. // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(), // ie, dependent on how much space is available (BK) if(!this._preparedNode){ this._preparedNode = true; // Check if we have explicitly set width and height on the dropdown widget dom node if(ddNode.style.width){ this._explicitDDWidth = true; } if(ddNode.style.height){ this._explicitDDHeight = true; } } // Code for resizing dropdown (height limitation, or increasing width to match my width) if(this.maxHeight || this.forceWidth || this.autoWidth){ var myStyle = { display: "", visibility: "hidden" }; if(!this._explicitDDWidth){ myStyle.width = ""; } if(!this._explicitDDHeight){ myStyle.height = ""; } domStyle.set(ddNode, myStyle); // Figure out maximum height allowed (if there is a height restriction) var maxHeight = this.maxHeight; if(maxHeight == -1){ // limit height to space available in viewport either above or below my domNode // (whichever side has more room) var viewport = Viewport.getEffectiveBox(this.ownerDocument), position = domGeometry.position(aroundNode, false); maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h))); } // Attach dropDown to DOM and make make visibility:hidden rather than display:none // so we call startup() and also get the size popup.moveOffScreen(dropDown); if(dropDown.startup && !dropDown._started){ dropDown.startup(); // this has to be done after being added to the DOM } // Get size of drop down, and determine if vertical scroll bar needed var mb = domGeometry.getMarginSize(ddNode); var overHeight = (maxHeight && mb.h > maxHeight); domStyle.set(ddNode, { overflow: overHeight ? "auto" : "visible" }); if(overHeight){ mb.h = maxHeight; if("w" in mb){ mb.w += 16; // room for vertical scrollbar } }else{ delete mb.h; } // Adjust dropdown width to match or be larger than my width if(this.forceWidth){ mb.w = aroundNode.offsetWidth; }else if(this.autoWidth){ mb.w = Math.max(mb.w, aroundNode.offsetWidth); }else{ delete mb.w; } // And finally, resize the dropdown to calculated height and width if(lang.isFunction(dropDown.resize)){ dropDown.resize(mb); }else{ domGeometry.setMarginBox(ddNode, mb); } } var retVal = popup.open({ parent: this, popup: dropDown, around: aroundNode, orient: this.dropDownPosition, onExecute: function(){ self.closeDropDown(true); }, onCancel: function(){ self.closeDropDown(true); }, onClose: function(){ domAttr.set(self._popupStateNode, "popupActive", false); domClass.remove(self._popupStateNode, "dijitHasDropDownOpen"); self._opened = false; } }); domAttr.set(this._popupStateNode, "popupActive", "true"); domClass.add(self._popupStateNode, "dijitHasDropDownOpen"); this._opened=true; // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown return retVal; }, closeDropDown: function(/*Boolean*/ focus){ // summary: // Closes the drop down on this widget // focus: // If true, refocuses the button widget // tags: // protected if(this._opened){ if(focus){ this.focus(); } popup.close(this.dropDown); this._opened = false; } } }); }); }, 'dijit/tree/TreeStoreModel':function(){ define("dijit/tree/TreeStoreModel", [ "dojo/_base/array", // array.filter array.forEach array.indexOf array.some "dojo/aspect", // aspect.after "dojo/_base/declare", // declare "dojo/_base/json", // json.stringify "dojo/_base/lang" // lang.hitch ], function(array, aspect, declare, json, lang){ // module: // dijit/tree/TreeStoreModel // summary: // Implements dijit.Tree.model connecting to a dojo.data store with a single // root item. return declare("dijit.tree.TreeStoreModel", null, { // summary: // Implements dijit.Tree.model connecting to a dojo.data store with a single // root item. Any methods passed into the constructor will override // the ones defined here. // store: dojo.data.Store // Underlying store store: null, // childrenAttrs: String[] // One or more attribute names (attributes in the dojo.data item) that specify that item's children childrenAttrs: ["children"], // newItemIdAttr: String // Name of attribute in the Object passed to newItem() that specifies the id. // // If newItemIdAttr is set then it's used when newItem() is called to see if an // item with the same id already exists, and if so just links to the old item // (so that the old item ends up with two parents). // // Setting this to null or "" will make every drop create a new item. newItemIdAttr: "id", // labelAttr: String // If specified, get label for tree node from this attribute, rather // than by calling store.getLabel() labelAttr: "", // root: [readonly] dojo.data.Item // Pointer to the root item (read only, not a parameter) root: null, // query: anything // Specifies datastore query to return the root item for the tree. // Must only return a single item. Alternately can just pass in pointer // to root item. // example: // | {id:'ROOT'} query: null, // deferItemLoadingUntilExpand: Boolean // Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes // until they are expanded. This allows for lazying loading where only one // loadItem (and generally one network call, consequently) per expansion // (rather than one for each child). // This relies on partial loading of the children items; each children item of a // fully loaded item should contain the label and info about having children. deferItemLoadingUntilExpand: false, constructor: function(/* Object */ args){ // summary: // Passed the arguments listed above (store, etc) // tags: // private lang.mixin(this, args); this.connects = []; var store = this.store; if(!store.getFeatures()['dojo.data.api.Identity']){ throw new Error("dijit.Tree: store must support dojo.data.Identity"); } // if the store supports Notification, subscribe to the notification events if(store.getFeatures()['dojo.data.api.Notification']){ this.connects = this.connects.concat([ aspect.after(store, "onNew", lang.hitch(this, "onNewItem"), true), aspect.after(store, "onDelete", lang.hitch(this, "onDeleteItem"), true), aspect.after(store, "onSet", lang.hitch(this, "onSetItem"), true) ]); } }, destroy: function(){ var h; while(h = this.connects.pop()){ h.remove(); } // TODO: should cancel any in-progress processing of getRoot(), getChildren() }, // ======================================================================= // Methods for traversing hierarchy getRoot: function(onItem, onError){ // summary: // Calls onItem with the root item for the tree, possibly a fabricated item. // Calls onError on error. if(this.root){ onItem(this.root); }else{ this.store.fetch({ query: this.query, onComplete: lang.hitch(this, function(items){ if(items.length != 1){ throw new Error(this.declaredClass + ": query " + json.stringify(this.query) + " returned " + items.length + " items, but must return exactly one item"); } this.root = items[0]; onItem(this.root); }), onError: onError }); } }, mayHaveChildren: function(/*dojo.data.Item*/ item){ // summary: // Tells if an item has or may have children. Implementing logic here // avoids showing +/- expando icon for nodes that we know don't have children. // (For efficiency reasons we may not want to check if an element actually // has children until user clicks the expando node) return array.some(this.childrenAttrs, function(attr){ return this.store.hasAttribute(item, attr); }, this); }, getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){ // summary: // Calls onComplete() with array of child items of given parent item, all loaded. var store = this.store; if(!store.isItemLoaded(parentItem)){ // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand // mode, so we will load it and just return the children (without loading each // child item) var getChildren = lang.hitch(this, arguments.callee); store.loadItem({ item: parentItem, onItem: function(parentItem){ getChildren(parentItem, onComplete, onError); }, onError: onError }); return; } // get children of specified item var childItems = []; for(var i=0; i // The set of id's that are currently selected, such that this.selection[id] == 1 // if the node w/that id is selected. Can iterate over selected node's id's like: // | for(var id in this.selection) selection: {}, =====*/ constructor: function(node, params){ // summary: // constructor of the Selector // node: Node||String // node or node's id to build the selector on // params: dojo.dnd.__SelectorArgs? // a dictionary of parameters if(!params){ params = {}; } this.singular = params.singular; this.autoSync = params.autoSync; // class-specific variables this.selection = {}; this.anchor = null; this.simpleSelection = false; // set up events this.events.push( dojo.connect(this.node, "onmousedown", this, "onMouseDown"), dojo.connect(this.node, "onmouseup", this, "onMouseUp")); }, // object attributes (for markup) singular: false, // is singular property // methods getSelectedNodes: function(){ // summary: // returns a list (an array) of selected nodes var t = new dojo.NodeList(); var e = dojo.dnd._empty; for(var i in this.selection){ if(i in e){ continue; } t.push(dojo.byId(i)); } return t; // NodeList }, selectNone: function(){ // summary: // unselects all items return this._removeSelection()._removeAnchor(); // self }, selectAll: function(){ // summary: // selects all items this.forInItems(function(data, id){ this._addItemClass(dojo.byId(id), "Selected"); this.selection[id] = 1; }, this); return this._removeAnchor(); // self }, deleteSelectedNodes: function(){ // summary: // deletes all selected items var e = dojo.dnd._empty; for(var i in this.selection){ if(i in e){ continue; } var n = dojo.byId(i); this.delItem(i); dojo.destroy(n); } this.anchor = null; this.selection = {}; return this; // self }, forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){ // summary: // iterates over selected items; // see `dojo.dnd.Container.forInItems()` for details o = o || dojo.global; var s = this.selection, e = dojo.dnd._empty; for(var i in s){ if(i in e){ continue; } f.call(o, this.getItem(i), i, this); } }, sync: function(){ // summary: // sync up the node list with the data map dojo.dnd.Selector.superclass.sync.call(this); // fix the anchor if(this.anchor){ if(!this.getItem(this.anchor.id)){ this.anchor = null; } } // fix the selection var t = [], e = dojo.dnd._empty; for(var i in this.selection){ if(i in e){ continue; } if(!this.getItem(i)){ t.push(i); } } dojo.forEach(t, function(i){ delete this.selection[i]; }, this); return this; // self }, insertNodes: function(addSelected, data, before, anchor){ // summary: // inserts new data items (see `dojo.dnd.Container.insertNodes()` method for details) // addSelected: Boolean // all new nodes will be added to selected items, if true, no selection change otherwise // data: Array // a list of data items, which should be processed by the creator function // before: Boolean // insert before the anchor, if true, and after the anchor otherwise // anchor: Node // the anchor node to be used as a point of insertion var oldCreator = this._normalizedCreator; this._normalizedCreator = function(item, hint){ var t = oldCreator.call(this, item, hint); if(addSelected){ if(!this.anchor){ this.anchor = t.node; this._removeItemClass(t.node, "Selected"); this._addItemClass(this.anchor, "Anchor"); }else if(this.anchor != t.node){ this._removeItemClass(t.node, "Anchor"); this._addItemClass(t.node, "Selected"); } this.selection[t.node.id] = 1; }else{ this._removeItemClass(t.node, "Selected"); this._removeItemClass(t.node, "Anchor"); } return t; }; dojo.dnd.Selector.superclass.insertNodes.call(this, data, before, anchor); this._normalizedCreator = oldCreator; return this; // self }, destroy: function(){ // summary: // prepares the object to be garbage-collected dojo.dnd.Selector.superclass.destroy.call(this); this.selection = this.anchor = null; }, // mouse events onMouseDown: function(e){ // summary: // event processor for onmousedown // e: Event // mouse event if(this.autoSync){ this.sync(); } if(!this.current){ return; } if(!this.singular && !dojo.isCopyKey(e) && !e.shiftKey && (this.current.id in this.selection)){ this.simpleSelection = true; if(e.button === dojo.mouseButtons.LEFT){ // accept the left button and stop the event // for IE we don't stop event when multiple buttons are pressed dojo.stopEvent(e); } return; } if(!this.singular && e.shiftKey){ if(!dojo.isCopyKey(e)){ this._removeSelection(); } var c = this.getAllNodes(); if(c.length){ if(!this.anchor){ this.anchor = c[0]; this._addItemClass(this.anchor, "Anchor"); } this.selection[this.anchor.id] = 1; if(this.anchor != this.current){ var i = 0; for(; i < c.length; ++i){ var node = c[i]; if(node == this.anchor || node == this.current){ break; } } for(++i; i < c.length; ++i){ var node = c[i]; if(node == this.anchor || node == this.current){ break; } this._addItemClass(node, "Selected"); this.selection[node.id] = 1; } this._addItemClass(this.current, "Selected"); this.selection[this.current.id] = 1; } } }else{ if(this.singular){ if(this.anchor == this.current){ if(dojo.isCopyKey(e)){ this.selectNone(); } }else{ this.selectNone(); this.anchor = this.current; this._addItemClass(this.anchor, "Anchor"); this.selection[this.current.id] = 1; } }else{ if(dojo.isCopyKey(e)){ if(this.anchor == this.current){ delete this.selection[this.anchor.id]; this._removeAnchor(); }else{ if(this.current.id in this.selection){ this._removeItemClass(this.current, "Selected"); delete this.selection[this.current.id]; }else{ if(this.anchor){ this._removeItemClass(this.anchor, "Anchor"); this._addItemClass(this.anchor, "Selected"); } this.anchor = this.current; this._addItemClass(this.current, "Anchor"); this.selection[this.current.id] = 1; } } }else{ if(!(this.current.id in this.selection)){ this.selectNone(); this.anchor = this.current; this._addItemClass(this.current, "Anchor"); this.selection[this.current.id] = 1; } } } } dojo.stopEvent(e); }, onMouseUp: function(e){ // summary: // event processor for onmouseup // e: Event // mouse event if(!this.simpleSelection){ return; } this.simpleSelection = false; this.selectNone(); if(this.current){ this.anchor = this.current; this._addItemClass(this.anchor, "Anchor"); this.selection[this.current.id] = 1; } }, onMouseMove: function(e){ // summary: // event processor for onmousemove // e: Event // mouse event this.simpleSelection = false; }, // utilities onOverEvent: function(){ // summary: // this function is called once, when mouse is over our container this.onmousemoveEvent = dojo.connect(this.node, "onmousemove", this, "onMouseMove"); }, onOutEvent: function(){ // summary: // this function is called once, when mouse is out of our container dojo.disconnect(this.onmousemoveEvent); delete this.onmousemoveEvent; }, _removeSelection: function(){ // summary: // unselects all items var e = dojo.dnd._empty; for(var i in this.selection){ if(i in e){ continue; } var node = dojo.byId(i); if(node){ this._removeItemClass(node, "Selected"); } } this.selection = {}; return this; // self }, _removeAnchor: function(){ if(this.anchor){ this._removeItemClass(this.anchor, "Anchor"); this.anchor = null; } return this; // self } }); return dojo.dnd.Selector; }); }, 'dijit/_MenuBase':function(){ define("dijit/_MenuBase", [ "./popup", "dojo/window", "./_Widget", "./_KeyNavContainer", "./_TemplatedMixin", "dojo/_base/declare", // declare "dojo/dom", // dom.isDescendant domClass.replace "dojo/dom-attr", "dojo/dom-class", // domClass.replace "dojo/_base/lang", // lang.hitch "dojo/_base/array" // array.indexOf ], function(pm, winUtils, _Widget, _KeyNavContainer, _TemplatedMixin, declare, dom, domAttr, domClass, lang, array){ /*===== var _Widget = dijit._Widget; var _TemplatedMixin = dijit._TemplatedMixin; var _KeyNavContainer = dijit._KeyNavContainer; =====*/ // module: // dijit/_MenuBase // summary: // Base class for Menu and MenuBar return declare("dijit._MenuBase", [_Widget, _TemplatedMixin, _KeyNavContainer], { // summary: // Base class for Menu and MenuBar // parentMenu: [readonly] Widget // pointer to menu that displayed me parentMenu: null, // popupDelay: Integer // number of milliseconds before hovering (without clicking) causes the popup to automatically open. popupDelay: 500, onExecute: function(){ // summary: // Attach point for notification about when a menu item has been executed. // This is an internal mechanism used for Menus to signal to their parent to // close them, because they are about to execute the onClick handler. In // general developers should not attach to or override this method. // tags: // protected }, onCancel: function(/*Boolean*/ /*===== closeAll =====*/){ // summary: // Attach point for notification about when the user cancels the current menu // This is an internal mechanism used for Menus to signal to their parent to // close them. In general developers should not attach to or override this method. // tags: // protected }, _moveToPopup: function(/*Event*/ evt){ // summary: // This handles the right arrow key (left arrow key on RTL systems), // which will either open a submenu, or move to the next item in the // ancestor MenuBar // tags: // private if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){ this.focusedChild._onClick(evt); }else{ var topMenu = this._getTopMenu(); if(topMenu && topMenu._isMenuBar){ topMenu.focusNext(); } } }, _onPopupHover: function(/*Event*/ /*===== evt =====*/){ // summary: // This handler is called when the mouse moves over the popup. // tags: // private // if the mouse hovers over a menu popup that is in pending-close state, // then stop the close operation. // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker) if(this.currentPopup && this.currentPopup._pendingClose_timer){ var parentMenu = this.currentPopup.parentMenu; // highlight the parent menu item pointing to this popup if(parentMenu.focusedChild){ parentMenu.focusedChild._setSelected(false); } parentMenu.focusedChild = this.currentPopup.from_item; parentMenu.focusedChild._setSelected(true); // cancel the pending close this._stopPendingCloseTimer(this.currentPopup); } }, onItemHover: function(/*MenuItem*/ item){ // summary: // Called when cursor is over a MenuItem. // tags: // protected // Don't do anything unless user has "activated" the menu by: // 1) clicking it // 2) opening it from a parent menu (which automatically focuses it) if(this.isActive){ this.focusChild(item); if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){ this.hover_timer = setTimeout(lang.hitch(this, "_openPopup"), this.popupDelay); } } // if the user is mixing mouse and keyboard navigation, // then the menu may not be active but a menu item has focus, // but it's not the item that the mouse just hovered over. // To avoid both keyboard and mouse selections, use the latest. if(this.focusedChild){ this.focusChild(item); } this._hoveredChild = item; }, _onChildBlur: function(item){ // summary: // Called when a child MenuItem becomes inactive because focus // has been removed from the MenuItem *and* it's descendant menus. // tags: // private this._stopPopupTimer(); item._setSelected(false); // Close all popups that are open and descendants of this menu var itemPopup = item.popup; if(itemPopup){ this._stopPendingCloseTimer(itemPopup); itemPopup._pendingClose_timer = setTimeout(function(){ itemPopup._pendingClose_timer = null; if(itemPopup.parentMenu){ itemPopup.parentMenu.currentPopup = null; } pm.close(itemPopup); // this calls onClose }, this.popupDelay); } }, onItemUnhover: function(/*MenuItem*/ item){ // summary: // Callback fires when mouse exits a MenuItem // tags: // protected if(this.isActive){ this._stopPopupTimer(); } if(this._hoveredChild == item){ this._hoveredChild = null; } }, _stopPopupTimer: function(){ // summary: // Cancels the popup timer because the user has stop hovering // on the MenuItem, etc. // tags: // private if(this.hover_timer){ clearTimeout(this.hover_timer); this.hover_timer = null; } }, _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){ // summary: // Cancels the pending-close timer because the close has been preempted // tags: // private if(popup._pendingClose_timer){ clearTimeout(popup._pendingClose_timer); popup._pendingClose_timer = null; } }, _stopFocusTimer: function(){ // summary: // Cancels the pending-focus timer because the menu was closed before focus occured // tags: // private if(this._focus_timer){ clearTimeout(this._focus_timer); this._focus_timer = null; } }, _getTopMenu: function(){ // summary: // Returns the top menu in this chain of Menus // tags: // private for(var top=this; top.parentMenu; top=top.parentMenu); return top; }, onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){ // summary: // Handle clicks on an item. // tags: // private // this can't be done in _onFocus since the _onFocus events occurs asynchronously if(typeof this.isShowingNow == 'undefined'){ // non-popup menu this._markActive(); } this.focusChild(item); if(item.disabled){ return false; } if(item.popup){ this._openPopup(); }else{ // before calling user defined handler, close hierarchy of menus // and restore focus to place it was when menu was opened this.onExecute(); // user defined handler for click item.onClick(evt); } }, _openPopup: function(){ // summary: // Open the popup to the side of/underneath the current menu item // tags: // protected this._stopPopupTimer(); var from_item = this.focusedChild; if(!from_item){ return; } // the focused child lost focus since the timer was started var popup = from_item.popup; if(popup.isShowingNow){ return; } if(this.currentPopup){ this._stopPendingCloseTimer(this.currentPopup); pm.close(this.currentPopup); } popup.parentMenu = this; popup.from_item = from_item; // helps finding the parent item that should be focused for this popup var self = this; pm.open({ parent: this, popup: popup, around: from_item.domNode, orient: this._orient || ["after", "before"], onCancel: function(){ // called when the child menu is canceled // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro) self.focusChild(from_item); // put focus back on my node self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved) from_item._setSelected(true); // oops, _cleanUp() deselected the item self.focusedChild = from_item; // and unset focusedChild }, onExecute: lang.hitch(this, "_cleanUp") }); this.currentPopup = popup; // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items if(this.popupHoverHandle){ this.disconnect(this.popupHoverHandle); } this.popupHoverHandle = this.connect(popup.domNode, "onmouseenter", "_onPopupHover"); if(popup.focus){ // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar), // if the cursor happens to collide with the popup, it will generate an onmouseover event // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742) popup._focus_timer = setTimeout(lang.hitch(popup, function(){ this._focus_timer = null; this.focus(); }), 0); } }, _markActive: function(){ // summary: // Mark this menu's state as active. // Called when this Menu gets focus from: // 1) clicking it (mouse or via space/arrow key) // 2) being opened by a parent menu. // This is not called just from mouse hover. // Focusing a menu via TAB does NOT automatically set isActive // since TAB is a navigation operation and not a selection one. // For Windows apps, pressing the ALT key focuses the menubar // menus (similar to TAB navigation) but the menu is not active // (ie no dropdown) until an item is clicked. this.isActive = true; domClass.replace(this.domNode, "dijitMenuActive", "dijitMenuPassive"); }, onOpen: function(/*Event*/ /*===== e =====*/){ // summary: // Callback when this menu is opened. // This is called by the popup manager as notification that the menu // was opened. // tags: // private this.isShowingNow = true; this._markActive(); }, _markInactive: function(){ // summary: // Mark this menu's state as inactive. this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here domClass.replace(this.domNode, "dijitMenuPassive", "dijitMenuActive"); }, onClose: function(){ // summary: // Callback when this menu is closed. // This is called by the popup manager as notification that the menu // was closed. // tags: // private this._stopFocusTimer(); this._markInactive(); this.isShowingNow = false; this.parentMenu = null; }, _closeChild: function(){ // summary: // Called when submenu is clicked or focus is lost. Close hierarchy of menus. // tags: // private this._stopPopupTimer(); if(this.currentPopup){ // If focus is on a descendant MenuItem then move focus to me, // because IE doesn't like it when you display:none a node with focus, // and also so keyboard users don't lose control. // Likely, immediately after a user defined onClick handler will move focus somewhere // else, like a Dialog. if(array.indexOf(this._focusManager.activeStack, this.id) >= 0){ domAttr.set(this.focusedChild.focusNode, "tabIndex", this.tabIndex); this.focusedChild.focusNode.focus(); } // Close all popups that are open and descendants of this menu pm.close(this.currentPopup); this.currentPopup = null; } if(this.focusedChild){ // unhighlight the focused item this.focusedChild._setSelected(false); this.focusedChild._onUnhover(); this.focusedChild = null; } }, _onItemFocus: function(/*MenuItem*/ item){ // summary: // Called when child of this Menu gets focus from: // 1) clicking it // 2) tabbing into it // 3) being opened by a parent menu. // This is not called just from mouse hover. if(this._hoveredChild && this._hoveredChild != item){ this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection } }, _onBlur: function(){ // summary: // Called when focus is moved away from this Menu and it's submenus. // tags: // protected this._cleanUp(); this.inherited(arguments); }, _cleanUp: function(){ // summary: // Called when the user is done with this menu. Closes hierarchy of menus. // tags: // private this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose this._markInactive(); } } }); }); }, 'dijit/focus':function(){ define("dijit/focus", [ "dojo/aspect", "dojo/_base/declare", // declare "dojo/dom", // domAttr.get dom.isDescendant "dojo/dom-attr", // domAttr.get dom.isDescendant "dojo/dom-construct", // connect to domConstruct.empty, domConstruct.destroy "dojo/Evented", "dojo/_base/lang", // lang.hitch "dojo/on", "dojo/domReady", "dojo/_base/sniff", // has("ie") "dojo/Stateful", "dojo/_base/window", // win.body "dojo/window", // winUtils.get "./a11y", // a11y.isTabNavigable "./registry", // registry.byId "./main" // to set dijit.focus ], function(aspect, declare, dom, domAttr, domConstruct, Evented, lang, on, domReady, has, Stateful, win, winUtils, a11y, registry, dijit){ // module: // dijit/focus var FocusManager = declare([Stateful, Evented], { // summary: // Tracks the currently focused node, and which widgets are currently "active". // Access via require(["dijit/focus"], function(focus){ ... }). // // A widget is considered active if it or a descendant widget has focus, // or if a non-focusable node of this widget or a descendant was recently clicked. // // Call focus.watch("curNode", callback) to track the current focused DOMNode, // or focus.watch("activeStack", callback) to track the currently focused stack of widgets. // // Call focus.on("widget-blur", func) or focus.on("widget-focus", ...) to monitor when // when widgets become active/inactive // // Finally, focus(node) will focus a node, suppressing errors if the node doesn't exist. // curNode: DomNode // Currently focused item on screen curNode: null, // activeStack: dijit/_WidgetBase[] // List of currently active widgets (focused widget and it's ancestors) activeStack: [], constructor: function(){ // Don't leave curNode/prevNode pointing to bogus elements var check = lang.hitch(this, function(node){ if(dom.isDescendant(this.curNode, node)){ this.set("curNode", null); } if(dom.isDescendant(this.prevNode, node)){ this.set("prevNode", null); } }); aspect.before(domConstruct, "empty", check); aspect.before(domConstruct, "destroy", check); }, registerIframe: function(/*DomNode*/ iframe){ // summary: // Registers listeners on the specified iframe so that any click // or focus event on that iframe (or anything in it) is reported // as a focus/click event on the `'); } }; back.setInitialState = function(/*Object*/args){ //summary: // Sets the state object and back callback for the very first page // that is loaded. //description: // It is recommended that you call this method as part of an event // listener that is registered via dojo.addOnLoad(). //args: Object // See the addToHistory() function for the list of valid args properties. initialState = createState(initialHref, args, initialHash); }; //FIXME: Make these doc comments not be awful. At least they're not wrong. //FIXME: Would like to support arbitrary back/forward jumps. Have to rework iframeLoaded among other things. //FIXME: is there a slight race condition in moz using change URL with the timer check and when // the hash gets set? I think I have seen a back/forward call in quick succession, but not consistent. /*===== dojo.__backArgs = function(kwArgs){ // back: Function? // A function to be called when this state is reached via the user // clicking the back button. // forward: Function? // Upon return to this state from the "back, forward" combination // of navigation steps, this function will be called. Somewhat // analgous to the semantic of an "onRedo" event handler. // changeUrl: Boolean?|String? // Boolean indicating whether or not to create a unique hash for // this state. If a string is passed instead, it is used as the // hash. } =====*/ back.addToHistory = function(/*dojo.__backArgs*/ args){ // summary: // adds a state object (args) to the history list. // args: dojo.__backArgs // The state object that will be added to the history list. // description: // To support getting back button notifications, the object // argument should implement a function called either "back", // "backButton", or "handle". The string "back" will be passed as // the first and only argument to this callback. // // To support getting forward button notifications, the object // argument should implement a function called either "forward", // "forwardButton", or "handle". The string "forward" will be // passed as the first and only argument to this callback. // // If you want the browser location string to change, define "changeUrl" on the object. If the // value of "changeUrl" is true, then a unique number will be appended to the URL as a fragment // identifier (http://some.domain.com/path#uniquenumber). If it is any other value that does // not evaluate to false, that value will be used as the fragment identifier. For example, // if changeUrl: 'page1', then the URL will look like: http://some.domain.com/path#page1 // // There are problems with using dojo.back with semantically-named fragment identifiers // ("hash values" on an URL). In most browsers it will be hard for dojo.back to know // distinguish a back from a forward event in those cases. For back/forward support to // work best, the fragment ID should always be a unique value (something using new Date().getTime() // for example). If you want to detect hash changes using semantic fragment IDs, then // consider using dojo.hash instead (in Dojo 1.4+). // // example: // | dojo.back.addToHistory({ // | back: function(){ console.log('back pressed'); }, // | forward: function(){ console.log('forward pressed'); }, // | changeUrl: true // | }); // BROWSER NOTES: // Safari 1.2: // back button "works" fine, however it's not possible to actually // DETECT that you've moved backwards by inspecting window.location. // Unless there is some other means of locating. // FIXME: perhaps we can poll on history.length? // Safari 2.0.3+ (and probably 1.3.2+): // works fine, except when changeUrl is used. When changeUrl is used, // Safari jumps all the way back to whatever page was shown before // the page that uses dojo.undo.browser support. // IE 5.5 SP2: // back button behavior is macro. It does not move back to the // previous hash value, but to the last full page load. This suggests // that the iframe is the correct way to capture the back button in // these cases. // Don't test this page using local disk for MSIE. MSIE will not create // a history list for iframe_history.html if served from a file: URL. // The XML served back from the XHR tests will also not be properly // created if served from local disk. Serve the test pages from a web // server to test in that browser. // IE 6.0: // same behavior as IE 5.5 SP2 // Firefox 1.0+: // the back button will return us to the previous hash on the same // page, thereby not requiring an iframe hack, although we do then // need to run a timer to detect inter-page movement. //If addToHistory is called, then that means we prune the //forward stack -- the user went back, then wanted to //start a new forward path. forwardStack = []; var hash = null; var url = null; if(!historyIframe){ if(dojo.config["useXDomain"] && !dojo.config["dojoIframeHistoryUrl"]){ console.warn("dojo.back: When using cross-domain Dojo builds," + " please save iframe_history.html to your domain and set djConfig.dojoIframeHistoryUrl" + " to the path on your domain to iframe_history.html"); } historyIframe = window.frames["dj_history"]; } if(!bookmarkAnchor){ bookmarkAnchor = domConstruct.create("a", {style: {display: "none"}}, baseWindow.body()); } if(args["changeUrl"]){ hash = ""+ ((args["changeUrl"]!==true) ? args["changeUrl"] : (new Date()).getTime()); //If the current hash matches the new one, just replace the history object with //this new one. It doesn't make sense to track different state objects for the same //logical URL. This matches the browser behavior of only putting in one history //item no matter how many times you click on the same #hash link, at least in Firefox //and Safari, and there is no reliable way in those browsers to know if a #hash link //has been clicked on multiple times. So making this the standard behavior in all browsers //so that dojo.back's behavior is the same in all browsers. if(historyStack.length == 0 && initialState.urlHash == hash){ initialState = createState(url, args, hash); return; }else if(historyStack.length > 0 && historyStack[historyStack.length - 1].urlHash == hash){ historyStack[historyStack.length - 1] = createState(url, args, hash); return; } changingUrl = true; setTimeout(function() { setHash(hash); changingUrl = false; }, 1); bookmarkAnchor.href = hash; if(sniff("ie")){ url = loadIframeHistory(); var oldCB = args["back"]||args["backButton"]||args["handle"]; //The function takes handleName as a parameter, in case the //callback we are overriding was "handle". In that case, //we will need to pass the handle name to handle. var tcb = function(handleName){ if(getHash() != ""){ setTimeout(function() { setHash(hash); }, 1); } //Use apply to set "this" to args, and to try to avoid memory leaks. oldCB.apply(this, [handleName]); }; //Set interceptor function in the right place. if(args["back"]){ args.back = tcb; }else if(args["backButton"]){ args.backButton = tcb; }else if(args["handle"]){ args.handle = tcb; } var oldFW = args["forward"]||args["forwardButton"]||args["handle"]; //The function takes handleName as a parameter, in case the //callback we are overriding was "handle". In that case, //we will need to pass the handle name to handle. var tfw = function(handleName){ if(getHash() != ""){ setHash(hash); } if(oldFW){ // we might not actually have one //Use apply to set "this" to args, and to try to avoid memory leaks. oldFW.apply(this, [handleName]); } }; //Set interceptor function in the right place. if(args["forward"]){ args.forward = tfw; }else if(args["forwardButton"]){ args.forwardButton = tfw; }else if(args["handle"]){ args.handle = tfw; } }else if(!sniff("ie")){ // start the timer if(!locationTimer){ locationTimer = setInterval(checkLocation, 200); } } }else{ url = loadIframeHistory(); } historyStack.push(createState(url, args, hash)); }; back._iframeLoaded = function(evt, ifrLoc){ //summary: // private method. Do not call this directly. var query = getUrlQuery(ifrLoc.href); if(query == null){ // alert("iframeLoaded"); // we hit the end of the history, so we should go back if(historyStack.length == 1){ handleBackButton(); } return; } if(moveForward){ // we were expecting it, so it's not either a forward or backward movement moveForward = false; return; } //Check the back stack first, since it is more likely. //Note that only one step back or forward is supported. if(historyStack.length >= 2 && query == getUrlQuery(historyStack[historyStack.length-2].url)){ handleBackButton(); }else if(forwardStack.length > 0 && query == getUrlQuery(forwardStack[forwardStack.length-1].url)){ handleForwardButton(); } }; return dojo.back; }); }, 'dijit/popup':function(){ define("dijit/popup", [ "dojo/_base/array", // array.forEach array.some "dojo/aspect", "dojo/_base/connect", // connect._keypress "dojo/_base/declare", // declare "dojo/dom", // dom.isDescendant "dojo/dom-attr", // domAttr.set "dojo/dom-construct", // domConstruct.create domConstruct.destroy "dojo/dom-geometry", // domGeometry.isBodyLtr "dojo/dom-style", // domStyle.set "dojo/_base/event", // event.stop "dojo/has", "dojo/keys", "dojo/_base/lang", // lang.hitch "dojo/on", "dojo/_base/window", // win.body "./place", "./BackgroundIframe", "." // dijit (defining dijit.popup to match API doc) ], function(array, aspect, connect, declare, dom, domAttr, domConstruct, domGeometry, domStyle, event, has, keys, lang, on, win, place, BackgroundIframe, dijit){ // module: // dijit/popup // summary: // Used to show drop downs (ex: the select list of a ComboBox) // or popups (ex: right-click context menus) /*===== dijit.popup.__OpenArgs = function(){ // popup: Widget // widget to display // parent: Widget // the button etc. that is displaying this popup // around: DomNode // DOM node (typically a button); place popup relative to this node. (Specify this *or* "x" and "y" parameters.) // x: Integer // Absolute horizontal position (in pixels) to place node at. (Specify this *or* "around" parameter.) // y: Integer // Absolute vertical position (in pixels) to place node at. (Specify this *or* "around" parameter.) // orient: Object|String // When the around parameter is specified, orient should be a list of positions to try, ex: // | [ "below", "above" ] // For backwards compatibility it can also be an (ordered) hash of tuples of the form // (around-node-corner, popup-node-corner), ex: // | { "BL": "TL", "TL": "BL" } // where BL means "bottom left" and "TL" means "top left", etc. // // dijit.popup.open() tries to position the popup according to each specified position, in order, // until the popup appears fully within the viewport. // // The default value is ["below", "above"] // // When an (x,y) position is specified rather than an around node, orient is either // "R" or "L". R (for right) means that it tries to put the popup to the right of the mouse, // specifically positioning the popup's top-right corner at the mouse position, and if that doesn't // fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner, // and the top-right corner. // onCancel: Function // callback when user has canceled the popup by // 1. hitting ESC or // 2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog); // i.e. whenever popupWidget.onCancel() is called, args.onCancel is called // onClose: Function // callback whenever this popup is closed // onExecute: Function // callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only) // padding: dijit.__Position // adding a buffer around the opening position. This is only useful when around is not set. this.popup = popup; this.parent = parent; this.around = around; this.x = x; this.y = y; this.orient = orient; this.onCancel = onCancel; this.onClose = onClose; this.onExecute = onExecute; this.padding = padding; } =====*/ /*===== dijit.popup = { // summary: // Used to show drop downs (ex: the select list of a ComboBox) // or popups (ex: right-click context menus). // // Access via require(["dijit/popup"], function(popup){ ... }). moveOffScreen: function(widget){ // summary: // Moves the popup widget off-screen. // Do not use this method to hide popups when not in use, because // that will create an accessibility issue: the offscreen popup is // still in the tabbing order. // widget: dijit._WidgetBase // The widget }, hide: function(widget){ // summary: // Hide this popup widget (until it is ready to be shown). // Initialization for widgets that will be used as popups // // Also puts widget inside a wrapper DIV (if not already in one) // // If popup widget needs to layout it should // do so when it is made visible, and popup._onShow() is called. // widget: dijit._WidgetBase // The widget }, open: function(args){ // summary: // Popup the widget at the specified position // example: // opening at the mouse position // | popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY}); // example: // opening the widget as a dropdown // | popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}}); // // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed. // args: dijit.popup.__OpenArgs // Parameters return {}; // Object specifying which position was chosen }, close: function(popup){ // summary: // Close specified popup and any popups that it parented. // If no popup is specified, closes all popups. // widget: dijit._WidgetBase? // The widget, optional } }; =====*/ function destroyWrapper(){ // summary: // Function to destroy wrapper when popup widget is destroyed. // Left in this scope to avoid memory leak on IE8 on refresh page, see #15206. if(this._popupWrapper){ domConstruct.destroy(this._popupWrapper); delete this._popupWrapper; } } var PopupManager = declare(null, { // _stack: dijit._Widget[] // Stack of currently popped up widgets. // (someone opened _stack[0], and then it opened _stack[1], etc.) _stack: [], // _beginZIndex: Number // Z-index of the first popup. (If first popup opens other // popups they get a higher z-index.) _beginZIndex: 1000, _idGen: 1, _createWrapper: function(/*Widget*/ widget){ // summary: // Initialization for widgets that will be used as popups. // Puts widget inside a wrapper DIV (if not already in one), // and returns pointer to that wrapper DIV. var wrapper = widget._popupWrapper, node = widget.domNode; if(!wrapper){ // Create wrapper
for when this widget [in the future] will be used as a popup. // This is done early because of IE bugs where creating/moving DOM nodes causes focus // to go wonky, see tests/robot/Toolbar.html to reproduce wrapper = domConstruct.create("div", { "class":"dijitPopup", style:{ display: "none"}, role: "presentation" }, win.body()); wrapper.appendChild(node); var s = node.style; s.display = ""; s.visibility = ""; s.position = ""; s.top = "0px"; widget._popupWrapper = wrapper; aspect.after(widget, "destroy", destroyWrapper, true); } return wrapper; }, moveOffScreen: function(/*Widget*/ widget){ // summary: // Moves the popup widget off-screen. // Do not use this method to hide popups when not in use, because // that will create an accessibility issue: the offscreen popup is // still in the tabbing order. // Create wrapper if not already there var wrapper = this._createWrapper(widget); domStyle.set(wrapper, { visibility: "hidden", top: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111) display: "" }); }, hide: function(/*Widget*/ widget){ // summary: // Hide this popup widget (until it is ready to be shown). // Initialization for widgets that will be used as popups // // Also puts widget inside a wrapper DIV (if not already in one) // // If popup widget needs to layout it should // do so when it is made visible, and popup._onShow() is called. // Create wrapper if not already there var wrapper = this._createWrapper(widget); domStyle.set(wrapper, "display", "none"); }, getTopPopup: function(){ // summary: // Compute the closest ancestor popup that's *not* a child of another popup. // Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button. var stack = this._stack; for(var pi=stack.length-1; pi > 0 && stack[pi].parent === stack[pi-1].widget; pi--){ /* do nothing, just trying to get right value for pi */ } return stack[pi]; }, open: function(/*dijit.popup.__OpenArgs*/ args){ // summary: // Popup the widget at the specified position // // example: // opening at the mouse position // | popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY}); // // example: // opening the widget as a dropdown // | popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}}); // // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed. var stack = this._stack, widget = args.popup, orient = args.orient || ["below", "below-alt", "above", "above-alt"], ltr = args.parent ? args.parent.isLeftToRight() : domGeometry.isBodyLtr(), around = args.around, id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++); // If we are opening a new popup that isn't a child of a currently opened popup, then // close currently opened popup(s). This should happen automatically when the old popups // gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198]. while(stack.length && (!args.parent || !dom.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){ this.close(stack[stack.length-1].widget); } // Get pointer to popup wrapper, and create wrapper if it doesn't exist var wrapper = this._createWrapper(widget); domAttr.set(wrapper, { id: id, style: { zIndex: this._beginZIndex + stack.length }, "class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup", dijitPopupParent: args.parent ? args.parent.id : "" }); if(has("bgIframe") && !widget.bgIframe){ // setting widget.bgIframe triggers cleanup in _Widget.destroy() widget.bgIframe = new BackgroundIframe(wrapper); } // position the wrapper node and make it visible var best = around ? place.around(wrapper, around, orient, ltr, widget.orient ? lang.hitch(widget, "orient") : null) : place.at(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding); wrapper.style.display = ""; wrapper.style.visibility = "visible"; widget.domNode.style.visibility = "visible"; // counteract effects from _HasDropDown var handlers = []; // provide default escape and tab key handling // (this will work for any widget, not just menu) handlers.push(on(wrapper, connect._keypress, lang.hitch(this, function(evt){ if(evt.charOrCode == keys.ESCAPE && args.onCancel){ event.stop(evt); args.onCancel(); }else if(evt.charOrCode === keys.TAB){ event.stop(evt); var topPopup = this.getTopPopup(); if(topPopup && topPopup.onCancel){ topPopup.onCancel(); } } }))); // watch for cancel/execute events on the popup and notify the caller // (for a menu, "execute" means clicking an item) if(widget.onCancel && args.onCancel){ handlers.push(widget.on("cancel", args.onCancel)); } handlers.push(widget.on(widget.onExecute ? "execute" : "change", lang.hitch(this, function(){ var topPopup = this.getTopPopup(); if(topPopup && topPopup.onExecute){ topPopup.onExecute(); } }))); stack.push({ widget: widget, parent: args.parent, onExecute: args.onExecute, onCancel: args.onCancel, onClose: args.onClose, handlers: handlers }); if(widget.onOpen){ // TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here) widget.onOpen(best); } return best; }, close: function(/*Widget?*/ popup){ // summary: // Close specified popup and any popups that it parented. // If no popup is specified, closes all popups. var stack = this._stack; // Basically work backwards from the top of the stack closing popups // until we hit the specified popup, but IIRC there was some issue where closing // a popup would cause others to close too. Thus if we are trying to close B in [A,B,C] // closing C might close B indirectly and then the while() condition will run where stack==[A]... // so the while condition is constructed defensively. while((popup && array.some(stack, function(elem){return elem.widget == popup;})) || (!popup && stack.length)){ var top = stack.pop(), widget = top.widget, onClose = top.onClose; if(widget.onClose){ // TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here) widget.onClose(); } var h; while(h = top.handlers.pop()){ h.remove(); } // Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc. if(widget && widget.domNode){ this.hide(widget); } if(onClose){ onClose(); } } } }); return (dijit.popup = new PopupManager()); }); }, 'dijit/_base/manager':function(){ define("dijit/_base/manager", [ "dojo/_base/array", "dojo/_base/config", // defaultDuration "../registry", ".." // for setting exports to dijit namespace ], function(array, config, registry, dijit){ // module: // dijit/_base/manager // summary: // Shim to methods on registry, plus a few other declarations. // New code should access dijit/registry directly when possible. /*===== dijit.byId = function(id){ // summary: // Returns a widget by it's id, or if passed a widget, no-op (like dom.byId()) // id: String|dijit._Widget return registry.byId(id); // dijit._Widget }; dijit.getUniqueId = function(widgetType){ // summary: // Generates a unique id for a given widgetType // widgetType: String return registry.getUniqueId(widgetType); // String }; dijit.findWidgets = function(root){ // summary: // Search subtree under root returning widgets found. // Doesn't search for nested widgets (ie, widgets inside other widgets). // root: DOMNode return registry.findWidgets(root); }; dijit._destroyAll = function(){ // summary: // Code to destroy all widgets and do other cleanup on page unload return registry._destroyAll(); }; dijit.byNode = function(node){ // summary: // Returns the widget corresponding to the given DOMNode // node: DOMNode return registry.byNode(node); // dijit._Widget }; dijit.getEnclosingWidget = function(node){ // summary: // Returns the widget whose DOM tree contains the specified DOMNode, or null if // the node is not contained within the DOM tree of any widget // node: DOMNode return registry.getEnclosingWidget(node); }; =====*/ array.forEach(["byId", "getUniqueId", "findWidgets", "_destroyAll", "byNode", "getEnclosingWidget"], function(name){ dijit[name] = registry[name]; }); /*===== dojo.mixin(dijit, { // defaultDuration: Integer // The default fx.animation speed (in ms) to use for all Dijit // transitional fx.animations, unless otherwise specified // on a per-instance basis. Defaults to 200, overrided by // `djConfig.defaultDuration` defaultDuration: 200 }); =====*/ dijit.defaultDuration = config["defaultDuration"] || 200; return dijit; }); }, 'dijit/layout/StackController':function(){ define("dijit/layout/StackController", [ "dojo/_base/array", // array.forEach array.indexOf array.map "dojo/_base/declare", // declare "dojo/_base/event", // event.stop "dojo/keys", // keys "dojo/_base/lang", // lang.getObject "dojo/_base/sniff", // has("ie") "../focus", // focus.focus() "../registry", // registry.byId "../_Widget", "../_TemplatedMixin", "../_Container", "../form/ToggleButton", "dojo/i18n!../nls/common" ], function(array, declare, event, keys, lang, has, focus, registry, _Widget, _TemplatedMixin, _Container, ToggleButton){ /*===== var _Widget = dijit._Widget; var _TemplatedMixin = dijit._TemplatedMixin; var _Container = dijit._Container; var ToggleButton = dijit.form.ToggleButton; =====*/ // module: // dijit/layout/StackController // summary: // Set of buttons to select a page in a `dijit.layout.StackContainer` var StackButton = declare("dijit.layout._StackButton", ToggleButton, { // summary: // Internal widget used by StackContainer. // description: // The button-like or tab-like object you click to select or delete a page // tags: // private // Override _FormWidget.tabIndex. // StackContainer buttons are not in the tab order by default. // Probably we should be calling this.startupKeyNavChildren() instead. tabIndex: "-1", // closeButton: Boolean // When true, display close button for this tab closeButton: false, _setCheckedAttr: function(/*Boolean*/ value, /*Boolean?*/ priorityChange){ this.inherited(arguments); this.focusNode.removeAttribute("aria-pressed"); }, buildRendering: function(/*Event*/ evt){ this.inherited(arguments); (this.focusNode || this.domNode).setAttribute("role", "tab"); }, 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) // Note that you shouldn't override this method, but you can connect to it. focus.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. // Note that you shouldn't override this method, but you can connect to it. evt.stopPropagation(); } }); var StackController = declare("dijit.layout.StackController", [_Widget, _TemplatedMixin, _Container], { // summary: // Set of buttons to select a page in a `dijit.layout.StackContainer` // description: // Monitors the specified StackContainer, and whenever a page is // added, deleted, or selected, updates itself accordingly. baseClass: "dijitStackController", templateString: "", // containerId: [const] String // The id of the page container that I point to containerId: "", // buttonWidget: [const] Constructor // The button widget to create to correspond to each page buttonWidget: StackButton, constructor: function(){ this.pane2button = {}; // mapping from pane id to buttons this.pane2connects = {}; // mapping from pane id to this.connect() handles this.pane2watches = {}; // mapping from pane id to watch() handles }, postCreate: function(){ this.inherited(arguments); // Listen to notifications from StackContainer this.subscribe(this.containerId+"-startup", "onStartup"); this.subscribe(this.containerId+"-addChild", "onAddChild"); this.subscribe(this.containerId+"-removeChild", "onRemoveChild"); this.subscribe(this.containerId+"-selectChild", "onSelectChild"); this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress"); }, onStartup: function(/*Object*/ info){ // summary: // Called after StackContainer has finished initializing // tags: // private array.forEach(info.children, this.onAddChild, this); if(info.selected){ // Show button corresponding to selected pane (unless selected // is null because there are no panes) this.onSelectChild(info.selected); } }, destroy: function(){ for(var pane in this.pane2button){ this.onRemoveChild(registry.byId(pane)); } this.inherited(arguments); }, onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){ // summary: // Called whenever a page is added to the container. // Create button corresponding to the page. // tags: // private // create an instance of the button widget // (remove typeof buttonWidget == string support in 2.0) var cls = lang.isString(this.buttonWidget) ? lang.getObject(this.buttonWidget) : this.buttonWidget; var button = new cls({ id: this.id + "_" + page.id, label: page.title, dir: page.dir, lang: page.lang, textDir: page.textDir, showLabel: page.showTitle, iconClass: page.iconClass, closeButton: page.closable, title: page.tooltip }); button.focusNode.setAttribute("aria-selected", "false"); // map from page attribute to corresponding tab button attribute var pageAttrList = ["title", "showTitle", "iconClass", "closable", "tooltip"], buttonAttrList = ["label", "showLabel", "iconClass", "closeButton", "title"]; // watch() so events like page title changes are reflected in tab button this.pane2watches[page.id] = array.map(pageAttrList, function(pageAttr, idx){ return page.watch(pageAttr, function(name, oldVal, newVal){ button.set(buttonAttrList[idx], newVal); }); }); // connections so that clicking a tab button selects the corresponding page this.pane2connects[page.id] = [ this.connect(button, 'onClick', lang.hitch(this,"onButtonClick", page)), this.connect(button, 'onClickCloseButton', lang.hitch(this,"onCloseButtonClick", page)) ]; this.addChild(button, insertIndex); this.pane2button[page.id] = button; page.controlButton = button; // this value might be overwritten if two tabs point to same container if(!this._currentChild){ // put the first child into the tab order button.focusNode.setAttribute("tabIndex", "0"); button.focusNode.setAttribute("aria-selected", "true"); this._currentChild = page; } // make sure all tabs have the same length if(!this.isLeftToRight() && has("ie") && this._rectifyRtlTabList){ this._rectifyRtlTabList(); } }, onRemoveChild: function(/*dijit._Widget*/ page){ // summary: // Called whenever a page is removed from the container. // Remove the button corresponding to the page. // tags: // private if(this._currentChild === page){ this._currentChild = null; } // disconnect/unwatch connections/watches related to page being removed array.forEach(this.pane2connects[page.id], lang.hitch(this, "disconnect")); delete this.pane2connects[page.id]; array.forEach(this.pane2watches[page.id], function(w){ w.unwatch(); }); delete this.pane2watches[page.id]; var button = this.pane2button[page.id]; if(button){ this.removeChild(button); delete this.pane2button[page.id]; button.destroy(); } delete page.controlButton; }, onSelectChild: function(/*dijit._Widget*/ page){ // summary: // Called when a page has been selected in the StackContainer, either by me or by another StackController // tags: // private if(!page){ return; } if(this._currentChild){ var oldButton=this.pane2button[this._currentChild.id]; oldButton.set('checked', false); oldButton.focusNode.setAttribute("aria-selected", "false"); oldButton.focusNode.setAttribute("tabIndex", "-1"); } var newButton=this.pane2button[page.id]; newButton.set('checked', true); newButton.focusNode.setAttribute("aria-selected", "true"); this._currentChild = page; newButton.focusNode.setAttribute("tabIndex", "0"); var container = registry.byId(this.containerId); container.containerNode.setAttribute("aria-labelledby", newButton.id); }, onButtonClick: function(/*dijit._Widget*/ page){ // summary: // Called whenever one of my child buttons is pressed in an attempt to select a page // tags: // private if(this._currentChild.id === page.id) { //In case the user clicked the checked button, keep it in the checked state because it remains to be the selected stack page. var button=this.pane2button[page.id]; button.set('checked', true); } var container = registry.byId(this.containerId); container.selectChild(page); }, onCloseButtonClick: function(/*dijit._Widget*/ page){ // summary: // Called whenever one of my child buttons [X] is pressed in an attempt to close a page // tags: // private var container = registry.byId(this.containerId); container.closeChild(page); if(this._currentChild){ var b = this.pane2button[this._currentChild.id]; if(b){ focus.focus(b.focusNode || b.domNode); } } }, // TODO: this is a bit redundant with forward, back api in StackContainer adjacent: function(/*Boolean*/ forward){ // summary: // Helper for onkeypress to find next/previous button // tags: // private 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 = array.indexOf(children, this.pane2button[this._currentChild.id]); // 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. // tags: // private if(this.disabled || e.altKey ){ return; } var forward = null; if(e.ctrlKey || !e._djpage){ switch(e.charOrCode){ case keys.LEFT_ARROW: case keys.UP_ARROW: if(!e._djpage){ forward = false; } break; case keys.PAGE_UP: if(e.ctrlKey){ forward = false; } break; case keys.RIGHT_ARROW: case keys.DOWN_ARROW: if(!e._djpage){ forward = true; } break; case keys.PAGE_DOWN: if(e.ctrlKey){ forward = true; } break; case keys.HOME: case keys.END: var children = this.getChildren(); if(children && children.length){ children[e.charOrCode == keys.HOME ? 0 : children.length-1].onClick(); } event.stop(e); break; case keys.DELETE: if(this._currentChild.closable){ this.onCloseButtonClick(this._currentChild); } event.stop(e); break; default: if(e.ctrlKey){ if(e.charOrCode === keys.TAB){ this.adjacent(!e.shiftKey).onClick(); event.stop(e); }else if(e.charOrCode == "w"){ if(this._currentChild.closable){ this.onCloseButtonClick(this._currentChild); } event.stop(e); // avoid browser tab closing. } } } // handle next/previous page navigation (left/right arrow, etc.) if(forward !== null){ this.adjacent(forward).onClick(); event.stop(e); } } }, onContainerKeyPress: function(/*Object*/ info){ // summary: // Called when there was a keypress on the container // tags: // private info.e._djpage = info.page; this.onkeypress(info.e); } }); StackController.StackButton = StackButton; // for monkey patching return StackController; }); }, 'url:dijit/templates/TooltipDialog.html':"
\n\t
\n\t\t
\n\t
\n\t
\n
\n", 'dojo/dnd/Mover':function(){ define("dojo/dnd/Mover", ["../main", "../Evented", "../touch", "./common", "./autoscroll"], function(dojo, Evented, touch) { // module: // dojo/dnd/Mover // summary: // TODOC dojo.declare("dojo.dnd.Mover", [Evented], { constructor: function(node, e, host){ // summary: // an object which makes a node follow the mouse, or touch-drag on touch devices. // Used as a default mover, and as a base class for custom movers. // node: Node // a node (or node's id) to be moved // e: Event // a mouse event, which started the move; // only pageX and pageY properties are used // host: Object? // object which implements the functionality of the move, // and defines proper events (onMoveStart and onMoveStop) this.node = dojo.byId(node); this.marginBox = {l: e.pageX, t: e.pageY}; this.mouseButton = e.button; var h = (this.host = host), d = node.ownerDocument; this.events = [ // At the start of a drag, onFirstMove is called, and then the following two // connects are disconnected dojo.connect(d, touch.move, this, "onFirstMove"), // These are called continually during the drag dojo.connect(d, touch.move, this, "onMouseMove"), // And these are called at the end of the drag dojo.connect(d, touch.release, this, "onMouseUp"), // cancel text selection and text dragging dojo.connect(d, "ondragstart", dojo.stopEvent), dojo.connect(d.body, "onselectstart", dojo.stopEvent) ]; // notify that the move has started if(h && h.onMoveStart){ h.onMoveStart(this); } }, // mouse event processors onMouseMove: function(e){ // summary: // event processor for onmousemove/ontouchmove // e: Event // mouse/touch event dojo.dnd.autoScroll(e); var m = this.marginBox; this.host.onMove(this, {l: m.l + e.pageX, t: m.t + e.pageY}, e); dojo.stopEvent(e); }, onMouseUp: function(e){ if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ? e.button == 0 : this.mouseButton == e.button){ // TODO Should condition be met for touch devices, too? this.destroy(); } dojo.stopEvent(e); }, // utilities onFirstMove: function(e){ // summary: // makes the node absolute; it is meant to be called only once. // relative and absolutely positioned nodes are assumed to use pixel units var s = this.node.style, l, t, h = this.host; switch(s.position){ case "relative": case "absolute": // assume that left and top values are in pixels already l = Math.round(parseFloat(s.left)) || 0; t = Math.round(parseFloat(s.top)) || 0; break; default: s.position = "absolute"; // enforcing the absolute mode var m = dojo.marginBox(this.node); // event.pageX/pageY (which we used to generate the initial // margin box) includes padding and margin set on the body. // However, setting the node's position to absolute and then // doing dojo.marginBox on it *doesn't* take that additional // space into account - so we need to subtract the combined // padding and margin. We use getComputedStyle and // _getMarginBox/_getContentBox to avoid the extra lookup of // the computed style. var b = dojo.doc.body; var bs = dojo.getComputedStyle(b); var bm = dojo._getMarginBox(b, bs); var bc = dojo._getContentBox(b, bs); l = m.l - (bc.l - bm.l); t = m.t - (bc.t - bm.t); break; } this.marginBox.l = l - this.marginBox.l; this.marginBox.t = t - this.marginBox.t; if(h && h.onFirstMove){ h.onFirstMove(this, e); } // Disconnect onmousemove and ontouchmove events that call this function dojo.disconnect(this.events.shift()); }, destroy: function(){ // summary: // stops the move, deletes all references, so the object can be garbage-collected dojo.forEach(this.events, dojo.disconnect); // undo global settings var h = this.host; if(h && h.onMoveStop){ h.onMoveStop(this); } // destroy objects this.events = this.node = this.host = null; } }); return dojo.dnd.Mover; }); }, 'dijit/layout/TabContainer':function(){ define("dijit/layout/TabContainer", [ "dojo/_base/lang", // lang.getObject "dojo/_base/declare", // declare "./_TabContainerBase", "./TabController", "./ScrollingTabController" ], function(lang, declare, _TabContainerBase, TabController, ScrollingTabController){ /*===== var _TabContainerBase = dijit.layout._TabContainerBase; var TabController = dijit.layout.TabController; var ScrollingTabController = dijit.layout.ScrollingTabController; =====*/ // module: // dijit/layout/TabContainer // summary: // A Container with tabs to select each child (only one of which is displayed at a time). return declare("dijit.layout.TabContainer", _TabContainerBase, { // summary: // A Container with tabs to select each child (only one of which is displayed at a time). // description: // A TabContainer is a container that has multiple panes, but shows only // one pane at a time. There are a set of tabs corresponding to each pane, // where each tab has the name (aka title) of the pane, and optionally a close button. // useMenu: [const] Boolean // True if a menu should be used to select tabs when they are too // wide to fit the TabContainer, false otherwise. useMenu: true, // useSlider: [const] Boolean // True if a slider should be used to select tabs when they are too // wide to fit the TabContainer, false otherwise. useSlider: true, // controllerWidget: String // An optional parameter to override the widget used to display the tab labels controllerWidget: "", _makeController: function(/*DomNode*/ srcNode){ // summary: // Instantiate tablist controller widget and return reference to it. // Callback from _TabContainerBase.postCreate(). // tags: // protected extension var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"), TabController = lang.getObject(this.controllerWidget); return new TabController({ id: this.id + "_tablist", dir: this.dir, lang: this.lang, textDir: this.textDir, tabPosition: this.tabPosition, doLayout: this.doLayout, containerId: this.id, "class": cls, nested: this.nested, useMenu: this.useMenu, useSlider: this.useSlider, tabStripClass: this.tabStrip ? this.baseClass + (this.tabStrip ? "":"No") + "Strip": null }, srcNode); }, postMixInProperties: function(){ this.inherited(arguments); // Scrolling controller only works for horizontal non-nested tabs if(!this.controllerWidget){ this.controllerWidget = (this.tabPosition == "top" || this.tabPosition == "bottom") && !this.nested ? "dijit.layout.ScrollingTabController" : "dijit.layout.TabController"; } } }); }); }, 'dijit/BackgroundIframe':function(){ define("dijit/BackgroundIframe", [ "require", // require.toUrl ".", // to export dijit.BackgroundIframe "dojo/_base/config", "dojo/dom-construct", // domConstruct.create "dojo/dom-style", // domStyle.set "dojo/_base/lang", // lang.extend lang.hitch "dojo/on", "dojo/_base/sniff", // has("ie"), has("mozilla"), has("quirks") "dojo/_base/window" // win.doc.createElement ], function(require, dijit, config, domConstruct, domStyle, lang, on, has, win){ // module: // dijit/BackgroundIFrame // Flag for whether to create background iframe behind popups like Menus and Dialog. // A background iframe is useful to prevent problems with popups appearing behind applets/pdf files, // and is also useful on older versions of IE (IE6 and IE7) to prevent the "bleed through select" problem. // TODO: For 2.0, make this false by default. Also, possibly move definition to has.js so that this module can be // conditionally required via dojo/has!bgIfame?dijit/BackgroundIframe has.add("bgIframe", has("ie") || has("mozilla")); // summary: // new dijit.BackgroundIframe(node) // Makes a background iframe as a child of node, that fills // area (and position) of node // TODO: remove _frames, it isn't being used much, since popups never release their // iframes (see [22236]) var _frames = new function(){ // summary: // cache of iframes var queue = []; this.pop = function(){ var iframe; if(queue.length){ iframe = queue.pop(); iframe.style.display=""; }else{ if(has("ie") < 9){ var burl = config["dojoBlankHtmlUrl"] || require.toUrl("dojo/resources/blank.html") || "javascript:\"\""; var html="