With great thanks to stoneym, IDisposable,
Ricardo Francisco, and the Atlas Control Toolkit Team, I've reworked the ModalPopupExtender to fix many of the bugs.
Highlights:
- You can submit through an updatepanel without setting UseSubmitBehavior to false. (requires new property onOkSubmitNormal to True)
- I believe the doctype problem is fixed.
- Works better with updatepanels.
- Objects are being destroyed properly.
There is a new property called onOkNormalSubmit which is default False, the way the toolkit is normally setup as. To use this property you will have to modify the .cs files too.
Here's the Javascript code:
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.
Type.registerNamespace('AtlasControlToolkit');
AtlasControlToolkit.ModalPopupBehavior = function() {
AtlasControlToolkit.ModalPopupBehavior.initializeBase(this);
//
// Variables
//
// Custom properties
var _PopupControlID;
var _BackgroundCssClass;
var _DropShadow = false;
var _OkControlID;
var _CancelControlID;
var _OnOkScript;
var _OnCancelScript;
var _OnOkNormalSubmit;
// Member variables
var _showHandler;
var _okHandler;
var _cancelHandler;
var _scrollHandler;
var _resizeHandler;
// Only used when drop shadows are on
var _dropShadowBehavior;
var _foregroundElementControl;
// Disable/Restore Tab variables
var _saveTabIndexes = new Array();
var _saveDesableSelect = new Array();
var _tagWithTabIndex = new Array('A','BUTTON','TEXTAREA','INPUT','IFRAME');
//
// Overrides
//
this.initialize = function() {
AtlasControlToolkit.ModalPopupBehavior.callBaseMethod(this, 'initialize');
var foregroundElement = $(_PopupControlID);
foregroundElement.style.display = 'none';
foregroundElement.style.position = 'absolute';
// Note: Safari doesn't support cancellation of link clicks added by
// addEventListener, so we must add them with "onclick ="
_showHandler = Function.createDelegate(this, this._onShow);
this._attachOnClick(this.control.element, _showHandler);
if (_OkControlID) {
_okHandler = Function.createDelegate(this, this._onOk);
}
if (_CancelControlID) {
_cancelHandler = Function.createDelegate(this, this._onCancel);
}
_scrollHandler = Function.createDelegate(this, this._onLayout);
_resizeHandler = Function.createDelegate(this, this._onLayout);
}
this.dispose = function() {
this._hide();
_scrollHandler = null;
_resizeHandler = null;
this._detachOnClick($(_CancelControlID), _cancelHandler);
this._detachOnClick($(_OkControlID), _okHandler);
this._detachOnClick(this.control.element, _showHandler);
var applicationMarkupContext = Sys.Application.getMarkupContext();
if (applicationMarkupContext) {
applicationMarkupContext.removeObject(this);
}
AtlasControlToolkit.ModalPopupBehavior.callBaseMethod(this, 'dispose');
}
this.getDescriptor = function() {
var td = AtlasControlToolkit.ModalPopupBehavior.callBaseMethod(this, 'getDescriptor');
// Add property declarations
td.addProperty('PopupControlID', String);
td.addProperty('BackgroundCssClass', String);
td.addProperty('DropShadow', Boolean);
td.addProperty('OnOkNormalSubmit', Boolean);
td.addProperty('OkControlID', String);
td.addProperty('CancelControlID', String);
td.addProperty('OnOkScript', String);
td.addProperty('OnCancelScript', String);
return td;
}
//
// Custom methods
//
this._attachOnClick = function(element, handler) {
if (element) {
if (window.__safari) {
element.onclick = handler;
} else {
element.attachEvent('onclick', handler);
}
}
}
this._detachOnClick = function(element, handler) {
if (handler) {
if (element) {
element.detachEvent('onclick', handler);
}
handler = null;
}
}
this._attachPopup = function(foregroundElement) {
foregroundElement.style.display = 'none';
foregroundElement.style.position = 'absolute';
var backgroundElement = document.createElement('div');
backgroundElement.id = 'ModalBackgroundDIV';
backgroundElement.style.display = 'none';
backgroundElement.style.position = 'absolute';
if (_BackgroundCssClass) {
backgroundElement.className = _BackgroundCssClass;
}
foregroundElement.parentNode.appendChild(backgroundElement);
if (_OkControlID) {
this._attachOnClick($(_OkControlID), _okHandler);
}
if (_CancelControlID) {
this._attachOnClick($(_CancelControlID), _cancelHandler);
}
if (_DropShadow) {
_foregroundElementControl = new Sys.UI.Control(foregroundElement);
_dropShadowBehavior = new AtlasControlToolkit.DropShadowBehavior();
_foregroundElementControl.get_behaviors().add(_dropShadowBehavior);
_dropShadowBehavior.initialize();
}
window.attachEvent('onresize', _resizeHandler);
window.attachEvent('onscroll', _scrollHandler);
return backgroundElement;
}
this._detachPopup = function(foregroundElement, backgroundElement) {
if (_scrollHandler) {
window.detachEvent('onscroll', _scrollHandler);
}
if (_resizeHandler) {
window.detachEvent('onresize', _resizeHandler);
}
if (_dropShadowBehavior) {
_dropShadowBehavior.dispose();
if (_foregroundElementControl) {
_foregroundElementControl.get_behaviors().remove(_dropShadowBehavior);
_foregroundElementControl = null;
}
_dropShadowBehavior = null;
}
if (backgroundElement && foregroundElement) {
if (backgroundElement.id == 'ModalBackgroundDIV')
{
foregroundElement.parentNode.removeChild(backgroundElement);
}
}
}
this._onShow = function(e) {
this._show();
event.returnValue = false;
return false;
}
this._onOk = function() {
this._hide();
if (!_OnOkNormalSubmit) {
event.returnValue = false;
} else {
event.returnValue = true;
}
if (_OnOkScript) {
window.setTimeout(_OnOkScript, 0);
}
if (!_OnOkNormalSubmit) {
return false;
} else {
return true;
}
}
this._onCancel = function() {
this._hide();
event.returnValue = false;
if (_OnCancelScript) {
window.setTimeout(_OnCancelScript, 0);
}
return false;
}
this._onLayout = function() {
var foregroundElement = $(_PopupControlID);
var backgroundElement = foregroundElement.parentNode.lastChild;
this._layout(foregroundElement, backgroundElement);
}
this._show = function() {
var foregroundElement = $(_PopupControlID);
var backgroundElement = this._attachPopup(foregroundElement);
this._updateZIndex(foregroundElement, backgroundElement);
foregroundElement.style.display = 'block';
backgroundElement.style.display = 'block';
//Disable Tab
this.disableTab(foregroundElement);
this._layout(foregroundElement, backgroundElement);
// On pages that don't need scrollbars, Firefox and Safari act like
// one or both are present the first time the layout code runs which
// obviously leads to display issues - run the layout code a second
// time to work around this problem
this._layout(foregroundElement, backgroundElement);
}
this._hide = function() {
var foregroundElement = $(_PopupControlID);
var backgroundElement;
if (foregroundElement)
{
foregroundElement.style.display = 'none'
if (foregroundElement.parentNode.hasChildNodes())
{
if (foregroundElement.parentNode.lastChild.id == 'ModalBackgroundDIV')
{
backgroundElement = foregroundElement.parentNode.lastChild;
backgroundElement.style.display = 'none';
}
}
this.restoreTab();
}
this._detachPopup(foregroundElement, backgroundElement);
}
this._updateZIndex = function(foregroundElement, backgroundElement) {
if (!backgroundElement || !foregroundElement) return;
var foregroundZIndex = foregroundElement.style.zIndex;
var backgroundZIndex = backgroundElement.style.zIndex;
if (backgroundZIndex && foregroundZIndex && foregroundZIndex > backgroundZIndex) {
return;
}
else {
foregroundZIndex = Math.max(2, foregroundZIndex);
backgroundZIndex = foregroundZIndex - 1;
}
foregroundElement.style.zIndex = foregroundZIndex;
backgroundElement.style.zIndex = backgroundZIndex;
}
this._layout = function(foregroundElement, backgroundElement) {
if (!backgroundElement || !foregroundElement) return;
var scrollLeft = (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
var scrollTop = (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
var clientWidth;
if (window.innerWidth){
clientWidth = (window.__safari ? window.innerWidth : Math.min(window.innerWidth, document.documentElement.clientWidth));
} else if (document.documentElement && document.documentElement.clientWidth){
clientWidth = document.documentElement.clientWidth;
} else if (document.body){
clientWidth = document.body.clientWidth;
} else {
clientWidth = document.documentElement.clientWidth;
}
var clientHeight;
if (window.innerHeight) {
clientHeight = (window.__safari ? window.innerHeight : Math.min(window.innerHeight, document.documentElement.clientHeight));
} else if (document.documentElement && document.documentElement.clientHeight){
clientHeight = document.documentElement.clientHeight;
} else if (document.body){
clientHeight = document.body.clientHeight;
} else {
clientHeight = document.documentElement.clientHeight;
}
backgroundElement.style.left = scrollLeft+'px';
backgroundElement.style.top = scrollTop+'px';
backgroundElement.style.width = clientWidth+'px';
backgroundElement.style.height = clientHeight+'px';
foregroundElement.style.left = scrollLeft+((clientWidth-foregroundElement.offsetWidth)/2)+'px';
foregroundElement.style.top = scrollTop+((clientHeight-foregroundElement.offsetHeight)/2)+'px';
if (_dropShadowBehavior) {
_dropShadowBehavior.setShadow();
window.setTimeout(Function.createDelegate(this, this._fixupDropShadowBehavior), 0);
}
}
// Some browsers don't update the location values immediately, so
// the location of the drop shadow would always be a step behind
// without this method
this._fixupDropShadowBehavior = function() {
if (_dropShadowBehavior) {
_dropShadowBehavior.setShadow();
}
}
this.disableTab = function(foregroundElement) {
var i = 0;
var tagElements;
var tagElementsInPopUp = new Array();
_saveTabIndexes.clear();
//Save all popup's tag in tagElementsInPopUp
for (var j = 0; j < _tagWithTabIndex.length; j++) {
tagElements = foregroundElement.getElementsByTagName(_tagWithTabIndex[j]);
for (var k = 0 ; k < tagElements.length; k++) {
tagElementsInPopUp[i] = tagElements[k];
i++;
}
}
i = 0;
for (var j = 0; j < _tagWithTabIndex.length; j++) {
tagElements = document.getElementsByTagName(_tagWithTabIndex[j]);
for (var k = 0 ; k < tagElements.length; k++) {
if (tagElementsInPopUp.indexOf(tagElements[k]) == -1) {
_saveTabIndexes[i] = {tag: tagElements[k], index: tagElements[k].tabIndex};
tagElements[k].tabIndex="-1";
i++;
}
}
}
//IE6 Bug with SELECT
i = 0;
if (navigator.userAgent.indexOf('MSIE 6.0') != -1) {
//Save SELECT in PopUp
var tagSelectInPopUp = new Array();
for (var j = 0; j < _tagWithTabIndex.length; j++) {
tagElements = foregroundElement.getElementsByTagName('SELECT');
for (var k = 0 ; k < tagElements.length; k++) {
tagSelectInPopUp[i] = tagElements[k];
i++;
}
}
i = 0;
_saveDesableSelect.clear();
tagElements = document.getElementsByTagName('SELECT');
for (var k = 0 ; k < tagElements.length; k++) {
if (tagSelectInPopUp.indexOf(tagElements[k]) == -1) {
_saveDesableSelect[i] = {tag: tagElements[k], visib: tagElements[k].style.visibility} ;
tagElements[k].style.visibility = 'hidden';
i++;
}
}
}
}
this.restoreTab = function() {
for (var i = 0; i < _saveTabIndexes.length; i++) {
_saveTabIndexes[i].tag.tabIndex = _saveTabIndexes[i].index;
}
//IE6 Bug with SELECT
if (navigator.userAgent.indexOf('MSIE 6.0') != -1) {
for (var k = 0 ; k < _saveDesableSelect.length; k++) {
_saveDesableSelect[k].tag.style.visibility = _saveDesableSelect[k].visib;
}
}
}
//
// Property get/set methods
//
this.get_PopupControlID = function() {
return _PopupControlID;
}
this.set_PopupControlID = function(value) {
_PopupControlID = value;
}
this.get_BackgroundCssClass = function() {
return _BackgroundCssClass;
}
this.set_BackgroundCssClass = function(value) {
_BackgroundCssClass = value;
}
this.get_DropShadow = function() {
return _DropShadow;
}
this.set_DropShadow = function(value) {
_DropShadow = value;
}
this.get_OnOkNormalSubmit = function() {
return _OnOkNormalSubmit;
}
this.set_OnOkNormalSubmit = function(value) {
_OnOkNormalSubmit = value;
}
this.get_OkControlID = function() {
return _OkControlID;
}
this.set_OkControlID = function(value) {
_OkControlID = value;
}
this.get_CancelControlID = function() {
return _CancelControlID;
}
this.set_CancelControlID = function(value) {
_CancelControlID = value;
}
this.get_OnOkScript = function() {
return _OnOkScript;
}
this.set_OnOkScript = function(value) {
_OnOkScript = value;
}
this.get_OnCancelScript = function() {
return _OnCancelScript;
}
this.set_OnCancelScript = function(value) {
_OnCancelScript = value;
}
}
AtlasControlToolkit.ModalPopupBehavior.registerSealedClass('AtlasControlToolkit.ModalPopupBehavior', Microsoft.AtlasControlExtender.BehaviorBase);
Sys.TypeDescriptor.addType('atlascontroltoolkit', 'modalPopupBehavior', AtlasControlToolkit.ModalPopupBehavior);