define([
"dojo/_base/array", // array.forEach array.some
"dojo/aspect",
"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/has", // has("config-bgIframe")
"dojo/keys",
"dojo/_base/lang", // lang.hitch
"dojo/on",
"./place",
"./BackgroundIframe",
"./Viewport",
"./main" // dijit (defining dijit.popup to match API doc)
], function(array, aspect, declare, dom, domAttr, domConstruct, domGeometry, domStyle, has, keys, lang, on,
place, BackgroundIframe, Viewport, dijit){
// module:
// dijit/popup
/*=====
var __OpenArgs = {
// 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: place.__Position
// adding a buffer around the opening position. This is only useful when around is not set.
// maxHeight: Integer
// The max height for the popup. Any popup taller than this will have scrollbars.
// Set to Infinity for no max height. Default is to limit height to available space in viewport,
// above or below the aroundNode or specified x/y position.
};
=====*/
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, {
// summary:
// Used to show drop downs (ex: the select list of a ComboBox)
// or popups (ex: right-click context menus).
// _stack: dijit/_WidgetBase[]
// 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,
_repositionAll: function(){
// summary:
// If screen has been scrolled, reposition all the popups in the stack.
// Then set timer to check again later.
if(this._firstAroundNode){ // guard for when clearTimeout() on IE doesn't work
var oldPos = this._firstAroundPosition,
newPos = domGeometry.position(this._firstAroundNode, true),
dx = newPos.x - oldPos.x,
dy = newPos.y - oldPos.y;
if(dx || dy){
this._firstAroundPosition = newPos;
for(var i = 0; i < this._stack.length; i++){
var style = this._stack[i].wrapper.style;
style.top = (parseFloat(style.top) + dy) + "px";
if(style.right == "auto"){
style.left = (parseFloat(style.left) + dx) + "px";
}else{
style.right = (parseFloat(style.right) - dx) + "px";
}
}
}
this._aroundMoveListener = setTimeout(lang.hitch(this, "_repositionAll"), dx || dy ? 10 : 50);
}
},
_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: "region",
"aria-label": widget["aria-label"] || widget.label || widget.name || widget.id
}, widget.ownerDocumentBody);
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);
// Workaround iOS problem where clicking a Menu can focus an (or click a button) behind it.
// Need to be careful though that you can still focus 's and click