diff options
author | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
commit | 81bea17aefb26859f825b9293c7c99192874806e (patch) | |
tree | fb244408ca271affa2899adb634788802c9a89d8 /lib/dijit/_base/popup.js | |
parent | 870a70e109ac9e80a88047044530de53d0404ec7 (diff) |
upgrade Dojo to 1.6.1
Diffstat (limited to 'lib/dijit/_base/popup.js')
-rw-r--r-- | lib/dijit/_base/popup.js | 533 |
1 files changed, 390 insertions, 143 deletions
diff --git a/lib/dijit/_base/popup.js b/lib/dijit/_base/popup.js index aed046489..e6fa6a5ed 100644 --- a/lib/dijit/_base/popup.js +++ b/lib/dijit/_base/popup.js @@ -1,158 +1,405 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + 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"]){ -dojo._hasResource["dijit._base.popup"]=true; +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={_stack:[],_beginZIndex:1000,_idGen:1,moveOffScreen:function(_1){ -var _2=_1.parentNode; -if(!_2||!dojo.hasClass(_2,"dijitPopup")){ -_2=dojo.create("div",{"class":"dijitPopup",style:{visibility:"hidden",top:"-9999px"}},dojo.body()); -dijit.setWaiRole(_2,"presentation"); -_2.appendChild(_1); -} -var s=_1.style; -s.display=""; -s.visibility=""; -s.position=""; -s.top="0px"; -dojo.style(_2,{visibility:"hidden",top:"-9999px"}); -},getTopPopup:function(){ -var _3=this._stack; -for(var pi=_3.length-1;pi>0&&_3[pi].parent===_3[pi-1].widget;pi--){ -} -return _3[pi]; -},open:function(_4){ -var _5=this._stack,_6=_4.popup,_7=_4.orient||((_4.parent?_4.parent.isLeftToRight():dojo._isBodyLtr())?{"BL":"TL","BR":"TR","TL":"BL","TR":"BR"}:{"BR":"TR","BL":"TL","TR":"BR","TL":"BL"}),_8=_4.around,id=(_4.around&&_4.around.id)?(_4.around.id+"_dropdown"):("popup_"+this._idGen++); -var _9=_6.domNode.parentNode; -if(!_9||!dojo.hasClass(_9,"dijitPopup")){ -this.moveOffScreen(_6.domNode); -_9=_6.domNode.parentNode; -} -dojo.attr(_9,{id:id,style:{zIndex:this._beginZIndex+_5.length},"class":"dijitPopup "+(_6.baseClass||_6["class"]||"").split(" ")[0]+"Popup",dijitPopupParent:_4.parent?_4.parent.id:""}); -if(dojo.isIE||dojo.isMoz){ -var _a=_9.childNodes[1]; -if(!_a){ -_a=new dijit.BackgroundIframe(_9); -} -} -var _b=_8?dijit.placeOnScreenAroundElement(_9,_8,_7,_6.orient?dojo.hitch(_6,"orient"):null):dijit.placeOnScreen(_9,_4,_7=="R"?["TR","BR","TL","BL"]:["TL","BL","TR","BR"],_4.padding); -_9.style.visibility="visible"; -_6.domNode.style.visibility="visible"; -var _c=[]; -_c.push(dojo.connect(_9,"onkeypress",this,function(_d){ -if(_d.charOrCode==dojo.keys.ESCAPE&&_4.onCancel){ -dojo.stopEvent(_d); -_4.onCancel(); -}else{ -if(_d.charOrCode===dojo.keys.TAB){ -dojo.stopEvent(_d); -var _e=this.getTopPopup(); -if(_e&&_e.onCancel){ -_e.onCancel(); -} -} -} -})); -if(_6.onCancel){ -_c.push(dojo.connect(_6,"onCancel",_4.onCancel)); -} -_c.push(dojo.connect(_6,_6.onExecute?"onExecute":"onChange",this,function(){ -var _f=this.getTopPopup(); -if(_f&&_f.onExecute){ -_f.onExecute(); -} -})); -_5.push({wrapper:_9,iframe:_a,widget:_6,parent:_4.parent,onExecute:_4.onExecute,onCancel:_4.onCancel,onClose:_4.onClose,handlers:_c}); -if(_6.onOpen){ -_6.onOpen(_b); -} -return _b; -},close:function(_10){ -var _11=this._stack; -while(dojo.some(_11,function(_12){ -return _12.widget==_10; -})){ -var top=_11.pop(),_13=top.wrapper,_14=top.iframe,_15=top.widget,_16=top.onClose; -if(_15.onClose){ -_15.onClose(); -} -dojo.forEach(top.handlers,dojo.disconnect); -if(_15&&_15.domNode){ -this.moveOffScreen(_15.domNode); -}else{ -dojo.destroy(_13); -} -if(_16){ -_16(); -} -} -}}; -dijit._frames=new function(){ -var _17=[]; -this.pop=function(){ -var _18; -if(_17.length){ -_18=_17.pop(); -_18.style.display=""; -}else{ -if(dojo.isIE){ -var _19=dojo.config["dojoBlankHtmlUrl"]||(dojo.moduleUrl("dojo","resources/blank.html")+"")||"javascript:\"\""; -var _1a="<iframe src='"+_19+"'"+" style='position: absolute; left: 0px; top: 0px;"+"z-index: -1; filter:Alpha(Opacity=\"0\");'>"; -_18=dojo.doc.createElement(_1a); -}else{ -_18=dojo.create("iframe"); -_18.src="javascript:\"\""; -_18.className="dijitBackgroundIframe"; -dojo.style(_18,"opacity",0.1); -} -_18.tabIndex=-1; -dijit.setWaiRole(_18,"presentation"); + + +/*===== +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; } -return _18; -}; -this.push=function(_1b){ -_1b.style.display="none"; -_17.push(_1b); +=====*/ + +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 <div> 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="<iframe src='" + burl + "'" + + " style='position: absolute; left: 0px; top: 0px;" + + "z-index: -1; filter:Alpha(Opacity=\"0\");'>"; + iframe = dojo.doc.createElement(html); + }else{ + iframe = dojo.create("iframe"); + iframe.src = 'javascript:""'; + iframe.className = "dijitBackgroundIframe"; + dojo.style(iframe, "opacity", 0.1); + } + iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didn't work. + dijit.setWaiRole(iframe,"presentation"); + } + return iframe; + }; + + this.push = function(iframe){ + iframe.style.display="none"; + queue.push(iframe); + } }(); -dijit.BackgroundIframe=function(_1c){ -if(!_1c.id){ -throw new Error("no id"); -} -if(dojo.isIE||dojo.isMoz){ -var _1d=dijit._frames.pop(); -_1c.appendChild(_1d); -if(dojo.isIE<7){ -this.resize(_1c); -this._conn=dojo.connect(_1c,"onresize",this,function(){ -this.resize(_1c); -}); -}else{ -dojo.style(_1d,{width:"100%",height:"100%"}); -} -this.iframe=_1d; -} + + +dijit.BackgroundIframe = function(/*DomNode*/ node){ + // summary: + // For IE/FF z-index schenanigans. id attribute is required. + // + // description: + // new dijit.BackgroundIframe(node) + // Makes a background iframe as a child of node, that fills + // area (and position) of node + + if(!node.id){ throw new Error("no id"); } + if(dojo.isIE || dojo.isMoz){ + var iframe = (this.iframe = dijit._frames.pop()); + node.appendChild(iframe); + if(dojo.isIE<7 || dojo.isQuirks){ + this.resize(node); + this._conn = dojo.connect(node, 'onresize', this, function(){ + this.resize(node); + }); + }else{ + dojo.style(iframe, { + width: '100%', + height: '100%' + }); + } + } }; -dojo.extend(dijit.BackgroundIframe,{resize:function(_1e){ -if(this.iframe&&dojo.isIE<7){ -dojo.style(this.iframe,{width:_1e.offsetWidth+"px",height:_1e.offsetHeight+"px"}); -} -},destroy:function(){ -if(this._conn){ -dojo.disconnect(this._conn); -this._conn=null; -} -if(this.iframe){ -dijit._frames.push(this.iframe); -delete this.iframe; -} -}}); + +dojo.extend(dijit.BackgroundIframe, { + resize: function(node){ + // summary: + // Resize the iframe so it's the same size as node. + // Needed on IE6 and IE/quirks because height:100% doesn't work right. + if(this.iframe){ + dojo.style(this.iframe, { + width: node.offsetWidth + 'px', + height: node.offsetHeight + 'px' + }); + } + }, + destroy: function(){ + // summary: + // destroy the iframe + if(this._conn){ + dojo.disconnect(this._conn); + this._conn = null; + } + if(this.iframe){ + dijit._frames.push(this.iframe); + delete this.iframe; + } + } +}); + } |