/* Copyright (c) 2004-2006, The Dojo Foundation All Rights Reserved. Licensed under the Academic Free License version 2.1 or above OR the modified BSD license. For more information on Dojo licensing, see: http://dojotoolkit.org/community/licensing.shtml */ dojo.provide("dojo.widget.PopupContainer"); dojo.require("dojo.html.style"); dojo.require("dojo.html.layout"); dojo.require("dojo.html.selection"); dojo.require("dojo.html.iframe"); dojo.require("dojo.event.*"); dojo.require("dojo.widget.*"); dojo.require("dojo.widget.HtmlWidget"); dojo.declare( "dojo.widget.PopupContainerBase", null, function(){ this.queueOnAnimationFinish = []; }, { // summary: // PopupContainerBase is the mixin class which provide popup behaviors: // it can open in a given position x,y or around a given node. // In addition, it handles animation and IE bleed through workaround. // description: // This class can not be used standalone: it should be mixed-in to a // dojo.widget.HtmlWidget. Use PopupContainer instead if you want a // a standalone popup widget isContainer: true, templateString: '
', // isShowingNow: Boolean: whether this popup is shown isShowingNow: false, // currentSubpopup: Widget: the shown sub popup if any currentSubpopup: null, // beginZIndex: Integer: the minimal popup zIndex beginZIndex: 1000, // parentPopup: Widget: parent popup widget parentPopup: null, // parent: Widget: the widget that caused me to be displayed; the logical parent. parent: null, // popupIndex: Integer: level of sub popup popupIndex: 0, // aroundBox: dojo.html.boxSizing: which bounding box to use for open aroundNode. By default use BORDER box of the aroundNode aroundBox: dojo.html.boxSizing.BORDER_BOX, // openedForWindow: Object: in which window the open() is triggered openedForWindow: null, processKey: function(/*Event*/evt){ // summary: key event handler return false; }, applyPopupBasicStyle: function(){ // summary: apply necessary css rules to the top domNode // description: // this function should be called in sub class where a custom // templateString/templateStringPath is used (see Tooltip widget) with(this.domNode.style){ display = 'none'; position = 'absolute'; } }, aboutToShow: function() { // summary: connect to this stub to modify the content of the popup }, open: function(/*Integer*/x, /*Integer*/y, /*DomNode*/parent, /*Object*/explodeSrc, /*String?*/orient, /*Array?*/padding){ // summary: // Open the popup at position (x,y), relative to dojo.body() // Or open(node, parent, explodeSrc, aroundOrient) to open // around node if (this.isShowingNow){ return; } // if I click right button and menu is opened, then it gets 2 commands: close -> open // so close enables animation and next "open" is put to queue to occur at new location if(this.animationInProgress){ this.queueOnAnimationFinish.push(this.open, arguments); return; } this.aboutToShow(); var around = false, node, aroundOrient; if(typeof x == 'object'){ node = x; aroundOrient = explodeSrc; explodeSrc = parent; parent = y; around = true; } // save this so that the focus can be returned this.parent = parent; // for unknown reasons even if the domNode is attached to the body in postCreate(), // it's not attached here, so have to attach it here. dojo.body().appendChild(this.domNode); // if explodeSrc isn't specified then explode from my parent widget explodeSrc = explodeSrc || parent["domNode"] || []; //keep track of parent popup to decided whether this is a top level popup var parentPopup = null; this.isTopLevel = true; while(parent){ if(parent !== this && (parent.setOpenedSubpopup != undefined && parent.applyPopupBasicStyle != undefined)){ parentPopup = parent; this.isTopLevel = false; parentPopup.setOpenedSubpopup(this); break; } parent = parent.parent; } this.parentPopup = parentPopup; this.popupIndex = parentPopup ? parentPopup.popupIndex + 1 : 1; if(this.isTopLevel){ var button = dojo.html.isNode(explodeSrc) ? explodeSrc : null; dojo.widget.PopupManager.opened(this, button); } //Store the current selection and restore it before the action for a menu item //is executed. This is required as clicking on an menu item deselects current selection if(this.isTopLevel && !dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.isCollapsed)){ this._bookmark = dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.getBookmark); }else{ this._bookmark = null; } //convert explodeSrc from format [x, y] to //{left: x, top: y, width: 0, height: 0} which is the new //format required by dojo.html.toCoordinateObject if(explodeSrc instanceof Array){ explodeSrc = {left: explodeSrc[0], top: explodeSrc[1], width: 0, height: 0}; } // display temporarily, and move into position, then hide again with(this.domNode.style){ display=""; zIndex = this.beginZIndex + this.popupIndex; } if(around){ this.move(node, padding, aroundOrient); }else{ this.move(x, y, padding, orient); } this.domNode.style.display="none"; this.explodeSrc = explodeSrc; // then use the user defined method to display it this.show(); this.isShowingNow = true; }, // TODOC: move(node, padding, aroundOrient) how to do this? move: function(/*Int*/x, /*Int*/y, /*Integer?*/padding, /*String?*/orient){ // summary: calculate where to place the popup var around = (typeof x == "object"); if(around){ var aroundOrient=padding; var node=x; padding=y; if(!aroundOrient){ //By default, attempt to open above the aroundNode, or below aroundOrient = {'BL': 'TL', 'TL': 'BL'}; } dojo.html.placeOnScreenAroundElement(this.domNode, node, padding, this.aroundBox, aroundOrient); }else{ if(!orient){ orient = 'TL,TR,BL,BR';} dojo.html.placeOnScreen(this.domNode, x, y, padding, true, orient); } }, close: function(/*Boolean?*/force){ // summary: hide the popup if(force){ this.domNode.style.display="none"; } // If we are in the process of opening the menu and we are asked to close it if(this.animationInProgress){ this.queueOnAnimationFinish.push(this.close, []); return; } this.closeSubpopup(force); this.hide(); if(this.bgIframe){ this.bgIframe.hide(); this.bgIframe.size({left: 0, top: 0, width: 0, height: 0}); } if(this.isTopLevel){ dojo.widget.PopupManager.closed(this); } this.isShowingNow = false; // return focus to the widget that opened the menu if(this.parent){ setTimeout( dojo.lang.hitch(this, function(){ try{ if(this.parent['focus']){ this.parent.focus(); }else{ this.parent.domNode.focus(); } }catch(e){dojo.debug("No idea how to focus to parent", e);} } ), 10 ); } //do not need to restore if current selection is not empty //(use keyboard to select a menu item) if(this._bookmark && dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.isCollapsed)){ if(this.openedForWindow){ this.openedForWindow.focus() } try{ dojo.withGlobal(this.openedForWindow||dojo.global(), "moveToBookmark", dojo.html.selection, [this._bookmark]); }catch(e){ /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */ } } this._bookmark = null; }, closeAll: function(/*Boolean?*/force){ // summary: hide all popups including sub ones if (this.parentPopup){ this.parentPopup.closeAll(force); }else{ this.close(force); } }, setOpenedSubpopup: function(/*Widget*/popup) { // summary: used by sub popup to set currentSubpopup in the parent popup this.currentSubpopup = popup; }, closeSubpopup: function(/*Boolean?*/force) { // summary: close opened sub popup if(this.currentSubpopup == null){ return; } this.currentSubpopup.close(force); this.currentSubpopup = null; }, onShow: function() { dojo.widget.PopupContainer.superclass.onShow.apply(this, arguments); // With some animation (wipe), after close, the size of the domnode is 0 // and next time when shown, the open() function can not determine // the correct place to popup, so we store the opened size here and // set it after close (in function onHide()) this.openedSize={w: this.domNode.style.width, h: this.domNode.style.height}; // prevent IE bleed through if(dojo.render.html.ie){ if(!this.bgIframe){ this.bgIframe = new dojo.html.BackgroundIframe(); this.bgIframe.setZIndex(this.domNode); } this.bgIframe.size(this.domNode); this.bgIframe.show(); } this.processQueue(); }, processQueue: function() { // summary: do events from queue if (!this.queueOnAnimationFinish.length) return; var func = this.queueOnAnimationFinish.shift(); var args = this.queueOnAnimationFinish.shift(); func.apply(this, args); }, onHide: function() { dojo.widget.HtmlWidget.prototype.onHide.call(this); //restore size of the domnode, see comment in //function onShow() if(this.openedSize){ with(this.domNode.style){ width=this.openedSize.w; height=this.openedSize.h; } } this.processQueue(); } }); dojo.widget.defineWidget( "dojo.widget.PopupContainer", [dojo.widget.HtmlWidget, dojo.widget.PopupContainerBase], { // summary: dojo.widget.PopupContainer is the widget version of dojo.widget.PopupContainerBase }); dojo.widget.PopupManager = new function(){ // summary: // the popup manager makes sure we don't have several popups // open at once. the root popup in an opening sequence calls // opened(). when a root menu closes it calls closed(). then // everything works. lovely. this.currentMenu = null; this.currentButton = null; // button that opened current menu (if any) this.currentFocusMenu = null; // the (sub)menu which receives key events this.focusNode = null; this.registeredWindows = []; this.registerWin = function(/*Window*/win){ // summary: register a window so that when clicks/scroll in it, the popup can be closed automatically if(!win.__PopupManagerRegistered) { dojo.event.connect(win.document, 'onmousedown', this, 'onClick'); dojo.event.connect(win, "onscroll", this, "onClick"); dojo.event.connect(win.document, "onkey", this, 'onKey'); win.__PopupManagerRegistered = true; this.registeredWindows.push(win); } }; /* */ this.registerAllWindows = function(/*Window*/targetWindow){ // summary: // This function register all the iframes and the top window, // so that whereever the user clicks in the page, the popup // menu will be closed // In case you add an iframe after onload event, please call // dojo.widget.PopupManager.registerWin manually //starting from window.top, clicking everywhere in this page //should close popup menus if(!targetWindow) { //see comment below targetWindow = dojo.html.getDocumentWindow(window.top && window.top.document || window.document); } this.registerWin(targetWindow); for (var i = 0; i < targetWindow.frames.length; i++){ try{ //do not remove dojo.html.getDocumentWindow, see comment in it var win = dojo.html.getDocumentWindow(targetWindow.frames[i].document); if(win){ this.registerAllWindows(win); } }catch(e){ /* squelch error for cross domain iframes */ } } }; this.unRegisterWin = function(/*Window*/win){ // summary: remove listeners on the registered window if(win.__PopupManagerRegistered) { dojo.event.disconnect(win.document, 'onmousedown', this, 'onClick'); dojo.event.disconnect(win, "onscroll", this, "onClick"); dojo.event.disconnect(win.document, "onkey", this, 'onKey'); win.__PopupManagerRegistered = false; } }; this.unRegisterAllWindows = function(){ // summary: remove listeners on all the registered windows for(var i=0;i