/* 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.Dialog"); dojo.require("dojo.widget.*"); dojo.require("dojo.widget.ContentPane"); dojo.require("dojo.event.*"); dojo.require("dojo.gfx.color"); dojo.require("dojo.html.layout"); dojo.require("dojo.html.display"); dojo.require("dojo.html.iframe"); dojo.declare( "dojo.widget.ModalDialogBase", null, { // summary // Mixin for widgets implementing a modal dialog isContainer: true, // focusElement: String // provide a focusable element or element id if you need to // work around FF's tendency to send focus into outer space on hide focusElement: "", // bgColor: String // color of viewport when displaying a dialog bgColor: "black", // bgOpacity: Number // opacity (0~1) of viewport color (see bgColor attribute) bgOpacity: 0.4, // followScroll: Boolean // if true, readjusts the dialog (and dialog background) when the user moves the scrollbar followScroll: true, // closeOnBackgroundClick: Boolean // clicking anywhere on the background will close the dialog closeOnBackgroundClick: false, trapTabs: function(/*Event*/ e){ // summary // callback on focus if(e.target == this.tabStartOuter) { if(this._fromTrap) { this.tabStart.focus(); this._fromTrap = false; } else { this._fromTrap = true; this.tabEnd.focus(); } } else if (e.target == this.tabStart) { if(this._fromTrap) { this._fromTrap = false; } else { this._fromTrap = true; this.tabEnd.focus(); } } else if(e.target == this.tabEndOuter) { if(this._fromTrap) { this.tabEnd.focus(); this._fromTrap = false; } else { this._fromTrap = true; this.tabStart.focus(); } } else if(e.target == this.tabEnd) { if(this._fromTrap) { this._fromTrap = false; } else { this._fromTrap = true; this.tabStart.focus(); } } }, clearTrap: function(/*Event*/ e) { // summary // callback on blur var _this = this; setTimeout(function() { _this._fromTrap = false; }, 100); }, postCreate: function() { // summary // if the target mixin class already defined postCreate, // dojo.widget.ModalDialogBase.prototype.postCreate.call(this) // should be called in its postCreate() with(this.domNode.style){ position = "absolute"; zIndex = 999; display = "none"; overflow = "visible"; } var b = dojo.body(); b.appendChild(this.domNode); // make background (which sits behind the dialog but above the normal text) this.bg = document.createElement("div"); this.bg.className = "dialogUnderlay"; with(this.bg.style){ position = "absolute"; left = top = "0px"; zIndex = 998; display = "none"; } b.appendChild(this.bg); this.setBackgroundColor(this.bgColor); this.bgIframe = new dojo.html.BackgroundIframe(); if(this.bgIframe.iframe){ with(this.bgIframe.iframe.style){ position = "absolute"; left = top = "0px"; zIndex = 90; display = "none"; } } if(this.closeOnBackgroundClick){ dojo.event.kwConnect({srcObj: this.bg, srcFunc: "onclick", adviceObj: this, adviceFunc: "onBackgroundClick", once: true}); } }, uninitialize: function(){ this.bgIframe.remove(); dojo.html.removeNode(this.bg, true); }, setBackgroundColor: function(/*String*/ color) { // summary // changes background color specified by "bgColor" parameter // usage: // setBackgroundColor("black"); // setBackgroundColor(0xff, 0xff, 0xff); if(arguments.length >= 3) { color = new dojo.gfx.color.Color(arguments[0], arguments[1], arguments[2]); } else { color = new dojo.gfx.color.Color(color); } this.bg.style.backgroundColor = color.toString(); return this.bgColor = color; // String: the color }, setBackgroundOpacity: function(/*Number*/ op) { // summary // changes background opacity set by "bgOpacity" parameter if(arguments.length == 0) { op = this.bgOpacity; } dojo.html.setOpacity(this.bg, op); try { this.bgOpacity = dojo.html.getOpacity(this.bg); } catch (e) { this.bgOpacity = op; } return this.bgOpacity; // Number: the opacity }, _sizeBackground: function() { if(this.bgOpacity > 0) { var viewport = dojo.html.getViewport(); var h = viewport.height; var w = viewport.width; with(this.bg.style){ width = w + "px"; height = h + "px"; } var scroll_offset = dojo.html.getScroll().offset; this.bg.style.top = scroll_offset.y + "px"; this.bg.style.left = scroll_offset.x + "px"; // process twice since the scroll bar may have been removed // by the previous resizing var viewport = dojo.html.getViewport(); if (viewport.width != w) { this.bg.style.width = viewport.width + "px"; } if (viewport.height != h) { this.bg.style.height = viewport.height + "px"; } } this.bgIframe.size(this.bg); }, _showBackground: function() { if(this.bgOpacity > 0) { this.bg.style.display = "block"; } if(this.bgIframe.iframe){ this.bgIframe.iframe.style.display = "block"; } }, placeModalDialog: function() { // summary: position modal dialog in center of screen var scroll_offset = dojo.html.getScroll().offset; var viewport_size = dojo.html.getViewport(); // find the size of the dialog (dialog needs to be showing to get the size) var mb; if(this.isShowing()){ mb = dojo.html.getMarginBox(this.domNode); }else{ dojo.html.setVisibility(this.domNode, false); dojo.html.show(this.domNode); mb = dojo.html.getMarginBox(this.domNode); dojo.html.hide(this.domNode); dojo.html.setVisibility(this.domNode, true); } var x = scroll_offset.x + (viewport_size.width - mb.width)/2; var y = scroll_offset.y + (viewport_size.height - mb.height)/2; with(this.domNode.style){ left = x + "px"; top = y + "px"; } }, _onKey: function(/*Event*/ evt){ if (evt.key){ // see if the key is for the dialog var node = evt.target; while (node != null){ if (node == this.domNode){ return; // yes, so just let it go } node = node.parentNode; } // this key is for the disabled document window if (evt.key != evt.KEY_TAB){ // allow tabbing into the dialog for a11y dojo.event.browser.stopEvent(evt); // opera won't tab to a div }else if (!dojo.render.html.opera){ try { this.tabStart.focus(); } catch(e){} } } }, showModalDialog: function() { // summary // call this function in show() of subclass before calling superclass.show() if (this.followScroll && !this._scrollConnected){ this._scrollConnected = true; dojo.event.connect(window, "onscroll", this, "_onScroll"); } dojo.event.connect(document.documentElement, "onkey", this, "_onKey"); this.placeModalDialog(); this.setBackgroundOpacity(); this._sizeBackground(); this._showBackground(); this._fromTrap = true; // set timeout to allow the browser to render dialog setTimeout(dojo.lang.hitch(this, function(){ try{ this.tabStart.focus(); }catch(e){} }), 50); }, hideModalDialog: function(){ // summary // call this function in hide() of subclass // workaround for FF focus going into outer space if (this.focusElement) { dojo.byId(this.focusElement).focus(); dojo.byId(this.focusElement).blur(); } this.bg.style.display = "none"; this.bg.style.width = this.bg.style.height = "1px"; if(this.bgIframe.iframe){ this.bgIframe.iframe.style.display = "none"; } dojo.event.disconnect(document.documentElement, "onkey", this, "_onKey"); if (this._scrollConnected){ this._scrollConnected = false; dojo.event.disconnect(window, "onscroll", this, "_onScroll"); } }, _onScroll: function(){ var scroll_offset = dojo.html.getScroll().offset; this.bg.style.top = scroll_offset.y + "px"; this.bg.style.left = scroll_offset.x + "px"; this.placeModalDialog(); }, checkSize: function() { if(this.isShowing()){ this._sizeBackground(); this.placeModalDialog(); this.onResized(); } }, onBackgroundClick: function(){ // summary // Callback on background click. // Clicking anywhere on the background will close the dialog, but only // if the dialog doesn't have an explicit close button, and only if // the dialog doesn't have a blockDuration. if(this.lifetime - this.timeRemaining >= this.blockDuration){ return; } this.hide(); } }); dojo.widget.defineWidget( "dojo.widget.Dialog", [dojo.widget.ContentPane, dojo.widget.ModalDialogBase], { // summary // Pops up a modal dialog window, blocking access to the screen and also graying out the screen // Dialog is extended from ContentPane so it supports all the same parameters (href, etc.) templatePath: dojo.uri.dojoUri("src/widget/templates/Dialog.html"), // blockDuration: Integer // number of seconds for which the user cannot dismiss the dialog blockDuration: 0, // lifetime: Integer // if set, this controls the number of seconds the dialog will be displayed before automatically disappearing lifetime: 0, // closeNode: String // Id of button or other dom node to click to close this dialog closeNode: "", postMixInProperties: function(){ dojo.widget.Dialog.superclass.postMixInProperties.apply(this, arguments); if(this.closeNode){ this.setCloseControl(this.closeNode); } }, postCreate: function(){ dojo.widget.Dialog.superclass.postCreate.apply(this, arguments); dojo.widget.ModalDialogBase.prototype.postCreate.apply(this, arguments); }, show: function() { if(this.lifetime){ this.timeRemaining = this.lifetime; if(this.timerNode){ this.timerNode.innerHTML = Math.ceil(this.timeRemaining/1000); } if(this.blockDuration && this.closeNode){ if(this.lifetime > this.blockDuration){ this.closeNode.style.visibility = "hidden"; }else{ this.closeNode.style.display = "none"; } } if (this.timer) { clearInterval(this.timer); } this.timer = setInterval(dojo.lang.hitch(this, "_onTick"), 100); } this.showModalDialog(); dojo.widget.Dialog.superclass.show.call(this); }, onLoad: function(){ // when href is specified we need to reposition // the dialog after the data is loaded this.placeModalDialog(); dojo.widget.Dialog.superclass.onLoad.call(this); }, fillInTemplate: function(){ // dojo.event.connect(this.domNode, "onclick", this, "killEvent"); }, hide: function(){ this.hideModalDialog(); dojo.widget.Dialog.superclass.hide.call(this); if(this.timer){ clearInterval(this.timer); } }, setTimerNode: function(node){ // summary // specify into which node to write the remaining # of seconds // TODO: make this a parameter too this.timerNode = node; }, setCloseControl: function(/*String|DomNode*/ node) { // summary // Specify which node is the close button for this dialog. // If no close node is specified then clicking anywhere on the screen will close the dialog. this.closeNode = dojo.byId(node); dojo.event.connect(this.closeNode, "onclick", this, "hide"); }, setShowControl: function(/*String|DomNode*/ node) { // summary // when specified node is clicked, show this dialog // TODO: make this a parameter too node = dojo.byId(node); dojo.event.connect(node, "onclick", this, "show"); }, _onTick: function(){ // summary // callback every second that the timer clicks if(this.timer){ this.timeRemaining -= 100; if(this.lifetime - this.timeRemaining >= this.blockDuration){ // TODO: this block of code is executing over and over again, rather than just once if(this.closeNode){ this.closeNode.style.visibility = "visible"; } } if(!this.timeRemaining){ clearInterval(this.timer); this.hide(); }else if(this.timerNode){ this.timerNode.innerHTML = Math.ceil(this.timeRemaining/1000); } } } } );