summaryrefslogtreecommitdiff
path: root/lib/dijit/_base/popup.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dijit/_base/popup.js')
-rw-r--r--lib/dijit/_base/popup.js533
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;
+ }
+ }
+});
+
}