You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
9.6 KiB
JavaScript
302 lines
9.6 KiB
JavaScript
![]()
15 years ago
|
dojo.provide("dijit._base.popup");
|
||
|
|
||
|
dojo.require("dijit._base.focus");
|
||
|
dojo.require("dijit._base.place");
|
||
|
dojo.require("dijit._base.window");
|
||
|
|
||
|
dijit.popup = new function(){
|
||
|
// summary:
|
||
|
// This class is used to show/hide widgets as popups.
|
||
|
|
||
|
var stack = [],
|
||
|
beginZIndex=1000,
|
||
|
idGen = 1;
|
||
|
|
||
|
this.prepare = function(/*DomNode*/ node){
|
||
|
// summary:
|
||
|
// Prepares a node to be used as a popup
|
||
|
//
|
||
|
// description:
|
||
|
// Attaches node to dojo.doc.body, and
|
||
|
// positions it off screen, but not display:none, so that
|
||
|
// the widget doesn't appear in the page flow and/or cause a blank
|
||
|
// area at the bottom of the viewport (making scrollbar longer), but
|
||
|
// initialization of contained widgets works correctly
|
||
|
|
||
|
var s = node.style;
|
||
|
s.visibility = "hidden"; // so TAB key doesn't navigate to hidden popup
|
||
|
s.position = "absolute";
|
||
|
s.top = "-9999px";
|
||
|
if(s.display == "none"){
|
||
|
s.display="";
|
||
|
}
|
||
|
dojo.body().appendChild(node);
|
||
|
};
|
||
|
|
||
|
/*=====
|
||
|
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. (Specity 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;
|
||
|
}
|
||
|
=====*/
|
||
|
this.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 widget = args.popup,
|
||
|
orient = args.orient || {'BL':'TL', 'TL':'BL'},
|
||
|
around = args.around,
|
||
|
id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+idGen++);
|
||
|
|
||
|
// make wrapper div to hold widget and possibly hold iframe behind it.
|
||
|
// we can't attach the iframe as a child of the widget.domNode because
|
||
|
// widget.domNode might be a <table>, <ul>, etc.
|
||
|
var wrapper = dojo.create("div",{
|
||
|
id: id,
|
||
|
"class":"dijitPopup",
|
||
|
style:{
|
||
|
zIndex: beginZIndex + stack.length,
|
||
|
visibility:"hidden"
|
||
|
}
|
||
|
}, dojo.body());
|
||
|
dijit.setWaiRole(wrapper, "presentation");
|
||
|
|
||
|
// prevent transient scrollbar causing misalign (#5776)
|
||
|
wrapper.style.left = wrapper.style.top = "0px";
|
||
|
|
||
|
if(args.parent){
|
||
|
wrapper.dijitPopupParent=args.parent.id;
|
||
|
}
|
||
|
|
||
|
var s = widget.domNode.style;
|
||
|
s.display = "";
|
||
|
s.visibility = "";
|
||
|
s.position = "";
|
||
|
s.top = "0px";
|
||
|
wrapper.appendChild(widget.domNode);
|
||
|
|
||
|
var iframe = new dijit.BackgroundIframe(wrapper);
|
||
|
|
||
|
// position the wrapper node
|
||
|
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.visibility = "visible";
|
||
|
// TODO: use effects to fade in wrapper
|
||
|
|
||
|
var handlers = [];
|
||
|
|
||
|
// 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 getTopPopup = function(){
|
||
|
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];
|
||
|
}
|
||
|
|
||
|
// 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 = 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", null, args.onCancel));
|
||
|
}
|
||
|
|
||
|
handlers.push(dojo.connect(widget, widget.onExecute ? "onExecute" : "onChange", null, function(){
|
||
|
var topPopup = getTopPopup();
|
||
|
if(topPopup && topPopup.onExecute){
|
||
|
topPopup.onExecute();
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
stack.push({
|
||
|
wrapper: wrapper,
|
||
|
iframe: iframe,
|
||
|
widget: widget,
|
||
|
parent: args.parent,
|
||
|
onExecute: args.onExecute,
|
||
|
onCancel: args.onCancel,
|
||
|
onClose: args.onClose,
|
||
|
handlers: handlers
|
||
|
});
|
||
|
|
||
|
if(widget.onOpen){
|
||
|
widget.onOpen(best);
|
||
|
}
|
||
|
|
||
|
return best;
|
||
|
};
|
||
|
|
||
|
this.close = function(/*Widget*/ popup){
|
||
|
// summary:
|
||
|
// Close specified popup and any popups that it parented
|
||
|
while(dojo.some(stack, function(elem){return elem.widget == popup;})){
|
||
|
var top = stack.pop(),
|
||
|
wrapper = top.wrapper,
|
||
|
iframe = top.iframe,
|
||
|
widget = top.widget,
|
||
|
onClose = top.onClose;
|
||
|
|
||
|
if(widget.onClose){
|
||
|
widget.onClose();
|
||
|
}
|
||
|
dojo.forEach(top.handlers, dojo.disconnect);
|
||
|
|
||
|
// #2685: check if the widget still has a domNode so ContentPane can change its URL without getting an error
|
||
|
if(!widget||!widget.domNode){ return; }
|
||
|
|
||
|
this.prepare(widget.domNode);
|
||
|
|
||
|
iframe.destroy();
|
||
|
dojo.destroy(wrapper);
|
||
|
|
||
|
if(onClose){
|
||
|
onClose();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}();
|
||
|
|
||
|
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){
|
||
|
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";
|
||
|
}
|
||
|
iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didnt work.
|
||
|
dojo.body().appendChild(iframe);
|
||
|
}
|
||
|
return iframe;
|
||
|
};
|
||
|
|
||
|
this.push = function(iframe){
|
||
|
iframe.style.display="none";
|
||
|
if(dojo.isIE){
|
||
|
iframe.style.removeExpression("width");
|
||
|
iframe.style.removeExpression("height");
|
||
|
}
|
||
|
queue.push(iframe);
|
||
|
}
|
||
|
}();
|
||
|
|
||
|
|
||
|
dijit.BackgroundIframe = function(/* DomNode */node){
|
||
|
// summary:
|
||
|
// For IE 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 < 7 || (dojo.isFF < 3 && dojo.hasClass(dojo.body(), "dijit_a11y"))){
|
||
|
var iframe = dijit._frames.pop();
|
||
|
node.appendChild(iframe);
|
||
|
if(dojo.isIE){
|
||
|
iframe.style.setExpression("width", dojo._scopeName + ".doc.getElementById('" + node.id + "').offsetWidth");
|
||
|
iframe.style.setExpression("height", dojo._scopeName + ".doc.getElementById('" + node.id + "').offsetHeight");
|
||
|
}
|
||
|
this.iframe = iframe;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
dojo.extend(dijit.BackgroundIframe, {
|
||
|
destroy: function(){
|
||
|
// summary: destroy the iframe
|
||
|
if(this.iframe){
|
||
|
dijit._frames.push(this.iframe);
|
||
|
delete this.iframe;
|
||
|
}
|
||
|
}
|
||
|
});
|