/* Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ if(!dojo._hasResource["dijit._base.popup"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit._base.popup"] = true; dojo.provide("dijit._base.popup"); dojo.require("dijit._base.focus"); dojo.require("dijit._base.place"); dojo.require("dijit._base.window"); /*===== 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 an // ordered list of tuples of the form (around-node-corner, popup-node-corner). // dijit.popup.open() tries to position the popup according to each tuple in the list, in order, // until the popup appears fully within the viewport. // // The default value is {BL:'TL', TL:'BL'}, which represents a list of two tuples: // 1. (BL, TL) // 2. (TL, BL) // where BL means "bottom left" and "TL" means "top left". // So by default, it first tries putting the popup below the around node, left-aligning them, // and then tries to put it above the around node, still left-aligning them. Note that the // default is horizontally reversed when in RTL mode. // // 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: // This singleton is used to show/hide widgets as popups. // _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 || DomNode*/ 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.declaredClass ? widget._popupWrapper : (widget.parentNode && dojo.hasClass(widget.parentNode, "dijitPopup")), node = widget.domNode || widget; 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 = dojo.create("div",{ "class":"dijitPopup", style:{ display: "none"}, role: "presentation" }, dojo.body()); wrapper.appendChild(node); var s = node.style; s.display = ""; s.visibility = ""; s.position = ""; s.top = "0px"; if(widget.declaredClass){ // TODO: in 2.0 change signature to always take widget, then remove if() widget._popupWrapper = wrapper; dojo.connect(widget, "destroy", function(){ dojo.destroy(wrapper); delete widget._popupWrapper; }); } } return wrapper; }, moveOffScreen: function(/*Widget || DomNode*/ 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); dojo.style(wrapper, { visibility: "hidden", top: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111) display: "" }); }, hide: function(/*dijit._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); dojo.style(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 // | dijit.popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY}); // // example: // opening the widget as a dropdown // | dijit.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 || ( (args.parent ? args.parent.isLeftToRight() : dojo._isBodyLtr()) ? {'BL':'TL', 'BR':'TR', 'TL':'BL', 'TR':'BR'} : {'BR':'TR', 'BL':'TL', 'TR':'BR', 'TL':'BL'} ), 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 || !dojo.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){ dijit.popup.close(stack[stack.length-1].widget); } // Get pointer to popup wrapper, and create wrapper if it doesn't exist var wrapper = this._createWrapper(widget); dojo.attr(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(dojo.isIE || dojo.isMoz){ if(!widget.bgIframe){ // setting widget.bgIframe triggers cleanup in _Widget.destroy() widget.bgIframe = new dijit.BackgroundIframe(wrapper); } } // position the wrapper node and make it visible var best = around ? dijit.placeOnScreenAroundElement(wrapper, around, orient, widget.orient ? dojo.hitch(widget, "orient") : null) : dijit.placeOnScreen(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(dojo.connect(wrapper, "onkeypress", this, function(evt){ if(evt.charOrCode == dojo.keys.ESCAPE && args.onCancel){ dojo.stopEvent(evt); args.onCancel(); }else if(evt.charOrCode === dojo.keys.TAB){ dojo.stopEvent(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){ handlers.push(dojo.connect(widget, "onCancel", args.onCancel)); } handlers.push(dojo.connect(widget, widget.onExecute ? "onExecute" : "onChange", 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(/*dijit._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 && dojo.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(); } dojo.forEach(top.handlers, dojo.disconnect); // 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(); } } } }; // TODO: remove dijit._frames, it isn't being used much, since popups never release their // iframes (see [22236]) dijit._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(dojo.isIE < 9){ var burl = dojo.config["dojoBlankHtmlUrl"] || (dojo.moduleUrl("dojo", "resources/blank.html")+"") || "javascript:\"\""; var html="