DisableControlExtender

I recently wrote a post showing how the contents of an UpdatePanel can be disabled using a technique similar to what the ModalPopupExtender control does.  The ModalPopup control adds a div onto the page that covers the all other elements by setting the width and height to the windows width and height and then sets the div's z-order to a very high number (like 1,000) ensuring that is it the top most element.  This div covers all of the other elements on the screen which provides the modal behavior.

I received two comments from this post

  1. It doesn't work with FireFox 
  2. It would be nice if this was an ASP.NET AJAX extender control

I have not created an AjaxControlToolkit extender control before so I figured this would be a good opportunity to learn something new.  Because I had already developed the code for the previous entry, I figured it would come down to copying and pasting the code into the appropriate methods of the AjaxControlToolkit framework.  Sure enough, that's really all it took.  Here are the steps I took, and what I learned along the way ...

Live Demo 1 | Live Demo 2 | Download

DisableControlExtender Design

When creating a new control, the first step I usually take is to start keying in the markup so I can decide what properties the control needs to support and so I can determine how I will interact with the control on the page.  At a minimum, I need the DisableControlExtender to support providing a style that I will use for the disabled div and a mechanism for programmatically enabling and disable the control the extender is targeting.  For specifying the disabled style, I create a DisabledCssClass and set it to the css class the extender will attach to the div.  Below is the markup I plan on using for doing this.     

<asp:UpdatePanel ID="updatePanel" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
    </ContentTemplate>
</asp:UpdatePanel>
<mb:DisableControlExtender 
    ID="disableControlExtender" runat="server" 
    TargetControlID="updatePanel" DisableCssClass="disabled" 
/> 

Next, I added the javascript code for enabling and disabling the control the extender is targeting.  To do this, I handle the PageRequestManagers beginRequest and endRequest methods.  beginRequest is where I will need to disable the UpdatePanel and endRequest is where I need to re-enable it.  Here is the javascript I am using for this ...

function pageLoad(sender, args){  
    if(!args.get_isPartialLoad()){  
        //  register for our events
        Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(beginRequest);
        Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);    
    }
}        

function beginRequest(sender, args){
    $find('disableControlExtender').disable();
}

function endRequest(sender, args) {
    $find('disableControlExtender').enable();
}

Download and Install the AjaxControlToolkit

Now that the control's properties and javascript interface have been defined, we can start working on creating the DisableControlExtender.  If you haven't already, you will need to download and install the AjaxControlToolkit.  The toolkit consists of a collection of community developed controls that provide extensions (mostly client side) to the existing controls. From what I understand, the project was started by a group of Microsoft developers, but has since moved to codeplex and is now community owned. The download is just a zip file so you can unpack it to whatever location you want. Part of the toolkit includes a showcase website that contains interactive pages that you can use to play with the different controls - it is identical to the 'live' demo of the controls that is available at http://ajax.asp.net/ajaxtoolkit/.  Part of the toolkit is a Visual Studio project template that you can use to create new AjaxControlToolkit extender controls.

Create a new ASP.NET AJAX Control Project

Next, I opened Visual Studio and created a new blank solution to which I added a new project based on the 'ASP.NET AJAX Control Project' template.  This project template adds three files to your project, the Extender and Designer .Net classes and a javascript behavior.  I also added a website to the solution as well that I am using for testing.  Here is a snap shot of what my Solution Explorer looks like ...

Implement the ExtenderControlBase class

Now that the projects are all setup and we have a sample test web site, we can start working on the actual implementation.  I will first start with the DisableControlExtender class.  This class inherits from the AjaxControlToolkit.ExtenderControlBase class and this is where we specify the custom properties our control provides.  For my control there is only a single customer property, DisableCssClass, so that is all I have added.  

In addition my new custom property, we also need to include some additional metadata to the class definition.  The ClientScriptResource attribute contains a reference to the javascript file as well as the full type name of the javascript object that provides the client side behavior (the template will auto-generate these for you). 

using System;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.ComponentModel;
using System.ComponentModel.Design;
using AjaxControlToolkit;

[assembly: System.Web.UI.WebResource(
    "MattBerseth.WebControls.AJAX.DisableControl.DisableControlBehavior.js", 
    "text/javascript")]

namespace MattBerseth.WebControls.AJAX.DisableControl
{
    /// <summary>
    /// 
    /// </summary>
    [Designer(typeof(DisableControlDesigner))]
    [ClientScriptResource(
        "MattBerseth.WebControls.AJAX.DisableControl.DisableControlBehavior", 
        "MattBerseth.WebControls.AJAX.DisableControl.DisableControlBehavior.js")]
    [TargetControlType(typeof(Control))]
    public class DisableControlExtender : ExtenderControlBase
    {
        /// <summary>
        /// 
        /// </summary>
        [ExtenderControlProperty]
        [DefaultValue("")]
        public string DisableCssClass
        {
            get
            {
                return this.GetPropertyValue("DisableCssClass", "");
            }
            set
            {
                this.SetPropertyValue("DisableCssClass", value);
            }
        }
    }
}

* By default the javascript behavior file is setup with the build property as an embedded resource.  The second parameter provided to the ClientScriptResource attribute is the path to this resource.  

Implement the ExtenderControlDesigner class

This one is pretty easy, the default version of this file should contain everything you need.  Here is what DisableControlDesigner looks like ...

using System.Web.UI.WebControls;
using System.Web.UI;

namespace MattBerseth.WebControls.AJAX.DisableControl
{
    /// <summary>
    /// 
    /// </summary>
    class DisableControlDesigner : 
        AjaxControlToolkit.Design.ExtenderControlBaseDesigner<DisableControlExtender>
    {
    }
}

Implement the JavaScript Behavior

Now that we have the .Net classes created, we can start moving over the javascript logic from my original post into the client side framework.  First, I created a new property for storing and retrieving the css class that will be applied to the disabled div.  When the DisableControlExtender is rendered onto the page, script is added that creates our behavior and passes through the values of our custom properties.

get_DisableCssClass : function() {
    return this._disableCssClass;
},

set_DisableCssClass : function(value) {
    this._disableCssClass = value;
}

Here is the script that the AjaxControlToolkit automatically adds to the page that creates our custom behavior.  You can view source on one of my demos, scroll all the way to the bottom to check it out.         

Sys.Application.initialize();
Sys.Application.add_init(function() {
    $create(
        MattBerseth.WebControls.AJAX.DisableControl.DisableControlBehavior, 
        {"DisableCssClass":"disabled","id":"disableControlExtender"}, 
        null, null, $get("updatePanel"));
});

After we created the property, we can now focus on adding the logic initializing our behavior.  To do this we extend the prototype to include an initialize function where we will create our div element and add it to the DOM.  In addition to this, we also add a handler for the window's resize event so we can resize our div as the window is resized.  Here is the initialization code for this behavior ...

initialize : function() {
    MattBerseth.WebControls.AJAX.DisableControl.DisableControlBehavior.callBaseMethod(this, 'initialize');

    if(!this._backgroundElement){
        //  create the div that we will position over the disabled element
        this._backgroundElement = document.createElement('div');
        this._backgroundElement.className = this.get_DisableCssClass();    
        //  make the zIndex very large so we can be sure this div
        //  will cover the element
        this._backgroundElement.style.zIndex = 10000;  
        this._backgroundElement.style.display = 'none';  
        
        //  let us know when the window is resized so we can reposition the div
        this._resizeHandler = Function.createDelegate(this, this._onLayout);
        $addHandler(window, 'resize', this._resizeHandler);
        
        //  add the element to the DOM
        this.get_element().parentNode.appendChild(this._backgroundElement);                      
    }
},

Now we can add logic for actually enabling and disabling the control.  Like I mentioned earlier, I would like to have an enable() and disable() method exposed that I can invoke to handle enabling and disabling the control.  Disabling the control consists of the following:

  1. Locating the bounds of the element we are disabling
  2. Setting the width and height properties of our div to these same values
  3. Placing our div over the control
  4. Removing the display style, making sure this control is visible

Here is the javascript I am using for doing this.  _onLayout is virtually identical to the javascript I used in my original posting ...

disable : function() {
    if(this._isEnabled){
        //  position the div
        this._onLayout();  
        //  update the enabled state
        this._isEnabled = false;
    } 
},

enable : function() {
    if(!this._isEnabled){
        //  hide the element
        this._backgroundElement.style.display = 'none';
        //  update the enabled state
        this._isEnabled = true;
    }
},

_onResize : function() {
    if(!this._isEnabled){
        //  position the div
        this._onLayout();             
    }
}, 

_onLayout : function() {
    //  determine the boundary of the element
    var elementBounds = Sys.UI.DomElement.getBounds(this.get_element());                
    
    //  set the div's height/width to that of the element
    this._backgroundElement.style.width = elementBounds.width + 'px';
    this._backgroundElement.style.height = elementBounds.height + 'px';     
    
    //  place the div over the element
    Sys.UI.DomElement.setLocation(this._backgroundElement, elementBounds.x, elementBounds.y); 
            
    //  show the div
    this._backgroundElement.style.display = '';      
}, 

Conclusion

That's it.  All of the code for the sample website as well as the DisableControlExtender are provided at the top of this page.  Also, if you are creating your own custom extender, I found the source code that comes with AjaxControlToolkit is a great resource for learning about how to create custom extender controls. 

I tested the control in IE and FireFox.  The only issue I ran into was that resizing the window in IE is a lot smoother that resizing it in FireFox.  It seems like the resize event doesn't fire for FireFox until the user has let go of the window.  If anyone knows how to fix this, I would be interested in hearing the solution.

I am still tagging this as 'prototype' because I am not using this in our production environment.

Thats it.  Enjoy!


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/52

Comments


Posted by: Jaweed on August 6, 2007 12:00 AM

Awesome post. Where were you all these months? ;-)

YES!!! FANTASTIC!

The only thing I would suggest at this point is to look into the this.registerPartialUpdateEvents(); of the AjaxControlToolkit.BehaviorBase. The ACTK is an awesome framework. They have done most the dirty work but you have to search for the meat and potatoes ATM. :)

GREAT work Matt!

Posted by: Ales on August 22, 2007 12:00 AM

Great control. Thanks. I am having problems using it insde a website with a master page.
Any suggestions?
Problem is, the panel does not become disabled. Without master page it works just fine.

Thanks Matt. Great stuff. However, I had an issue where the div being placed over the control (update panel in this case) was not positioned correctly b/c the div that was holding my update panel was offset. I changed the _onLayout function as follows to account for the parent elements offset values.

_onLayout : function() {
// determine the boundary of the element

var elementBounds = Sys.UI.DomElement.getBounds(this.get_element());
var parentElement = this.get_element().parentNode;

var offsetLeft = 0, offsetTop = 0;
if (parentElement != null)
{
offsetLeft = parentElement.offsetLeft + this.get_element().offsetLeft;
offsetTop = parentElement.offsetTop + this.get_element().offsetTop;
if (offsetTop
if (offsetLeft
}
// set the divs height/width to that of the element
this._backgroundElement.style.width = elementBounds.width + px;
this._backgroundElement.style.height = elementBounds.height + px;
// place the div over the element
Sys.UI.DomElement.setLocation(this._backgroundElement, elementBounds.x - offsetLeft, elementBounds.y - offsetTop);

// show the div
this._backgroundElement.style.display = ;
},

There might be a better way to do this, but for now this is working in my case.

Thanks again!

To use within a master page, use the clientID (which will include the contentplaceholder id) of the disableControlExtender in the beginRequest and endRequest functions. In my case I had my updatepanel inside a formview followed by the disableControlExtender so the client id was "ctl00_cphContent_fvMember_disableControlExtender"

Thanks Matt!

Posted by: George Polevoy on September 30, 2007 12:00 AM

Space button on the keyboard is a shortcut for pressing a focused button in Windows, so the button you just pressed is not quite disabled actually, its only disabled for the mouse clicks. Especially if you have submitted the form just this way for the first time - without using the mouse.

Posted by: Joe on November 5, 2008 05:19 PM

updated client id but no working when in a user control...any ideas?

Thanks!

Posted by: Joe on November 5, 2008 05:21 PM

I put this in beginRequest

if (typeof window.event != 'undefined') // IE
document.onkeydown = function() // IE
{
return false;
}
else
document.onkeypress = function(e) // FireFox/Others
{
return false;
}


and this in endRequest

if (typeof window.event != 'undefined') // IE
document.onkeydown = function() // IE
{
return true;
}
else
document.onkeypress = function(e) // FireFox/Others
{
return true;
}


which disabled all keyboard input during the postback ( I had to block tabs and arrows)

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • Joe wrote: I put this in beginRequest if (typeof window.event != 'undefined') // IE document.onkeydown...
  • Joe wrote: updated client id but no working when in a user control...any ideas? Thanks!...
  • George Polevoy wrote: Space button on the keyboard is a shortcut for pressing a focused button in Windows, so the button y...
  • Claire wrote: To use within a master page, use the clientID (which will include the contentplaceholder id) of the ...
  • Paige Cook wrote: Thanks Matt. Great stuff. However, I had an issue where the div being placed over the control (updat...
  • Ales wrote: Great control. Thanks. I am having problems using it insde a website with a master page. Any suggest...
  • MichaelD! wrote: YES!!! FANTASTIC! The only thing I would suggest at this point is to look into the this.registerPar...
  • Jaweed wrote: Awesome post. Where were you all these months? ;-) ...