Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Click Once Button

0.00/5 (No votes)
4 Aug 2006 1  
A Button/Image Button when clicked; disables all buttons on the page, so this makes sure that button is clicked only once, not twice by mistake.

Sample Image - ClickOnceButton.gif

Introduction

It is a Custom Button/ImgBtn which disables all other buttons (Image Button, <button>, input type as Submit, Reset, Button ) on the page, and once buttons are disabled, they cannot be clicked until they are enabled back by the control itself. This feature makes sure that button is clicked only once, not multiple times consecutively, also since it disables all other buttons on the page, there are no chances of clicking other buttons while processing of one button is going on server.

Concept Behind

Whenever this Custom Button/ImgBtn is clicked, I search all buttons on the page, create a clone of those buttons, disable them and place them over the original ones, and this gives an illusion that buttons are disabled. The reason behind why I avoided modifying existing buttons is that I did not want to interrupt processing of buttons disabling them, also I wanted to keep features like document.activeElement and btn.Click(), etc.

In case of ImageButton, while creating clone of ImageButton, I change the URL of the image to disable one, so one must have disable image in same image path.

Features

  • You need not write any extra code, just include this control on page, in case you are using ImageButton.
  • Disabling of button happens in JavaScript, that's why buttons are disabled even before any long processing of the server.
  • Any type of buttons could be on page, all will be disabled.
  • Buttons are disabled for a specific interval, this interval can be increased or decreased for different custom buttons.
  • Before control enables back all the button, if page is submitted to the server and re-loaded back, you could see all buttons enabled again as everything is done on client side therefore state is not maintained.
  • JavaScript functions are (EnableCustomButton()) exposed using which all buttons can be enabled before the enable interval has reached, e.g., if validations fail, you can alert the end user and can enable buttons immediately.
  • There exist JavaScript function using which you can disable all buttons, without even using custom button supplied, this JS method could be useful while using your own control or HTML buttons but you still want to disable the page when button is clicked.

Prerequisites

  • For showing disabled image, there must exist "XXX_disable.XXX" in same image path, i.e., If you have Image button URLs as "add.gif" or "btnXLS.jpg", you must also have "add_disable.gif" and "btnXLS_disable.jpg" in the same image path.
  • Every button input type (submit, reset, button), BUTTON tags and Image Button must have Ids defined.
  • If you don't use this CustomImageButton but still want other normal buttons to disable whole page when clicked, you need to call JavaScript function "CustomButtonClicked(this)" on "onclick" event of the button.
    Example, in the above example, "Final Save" button is normal ASP Button but when clicked, it also disables the complete page, code of same is given below:
    btnFinalSave.Attributes.Add("onclick","CustomButtonClicked(this)");

Attributes

  • DisableTimeOut: How long all buttons will be disabled on the page, when CustomImageButton is clicked. Default: 8000.
  • PutDisableScriptAtEnd: Disabling of buttons happens after the "JavaScript onclick events" already written on that button. Default: false.

    Example, when PutDisableScriptAtEnd = false

    <input onclick="CustomButtonClicked(this);
     if ( !SomeClientSideValidation() ) return false;" ../>

    and when PutDisableScriptAtEnd = true

    <input onclick="if ( !SomeClientSideValidation() ) return false; 
     CustomButtonClicked(this);" ../>

Using the Code

Using CustomImageButton

<Tittle:CustomImageButton id=btnXLS onclick=btnExport_Click Runat="server" 

 ImageUrl="Images/btnExportToExcel.gif">

HTML of same would be:

<input onclick="CustomButtonClicked(this);" type="image" name="btnXLS" id="btnXLS" 

        onclick src="Images/btnExportToExcel.gif" alt="" border="0" />

The above would be rendered as, and when clicked, disables all buttons on the page, even though nothing is written on any other button and not necessarily other buttons need to be of the same type CustomImageButton :

Image 2

Disabling All Buttons When Clicked on a Normal Button

X.aspx
<asp:button id="btnFinalSave" Text="Final Save" Runat="server" onclick="btnFinalSave_Click" />

X.aspx.cs
btnFinalSave.Attributes.Add("onclick","CustomButtonClicked(this)");

The above would be rendered as below and when clicked, disables all buttons on the page:

Image 3

Making Buttons Enable Even Before the Actual Interval, e.g. When Validation Fails; Re-Enable the Button Immediately

X.aspx
<Tittle:CustomImageButton id="btnSave" ImageUrl="Images/btSave.gif" 

 Runat="server" onclick="btnSave_Click" />
<script language="javascript">
function SomeClientSideValidation()
{
    if ( !confirm('Validation not met.\n\nYes - will still save 
         the page\nNo - will not save the page and re-Enable all buttons') )
    {
        EnableCustomButton('btnSave');
        return false;
    }
    
    return true;
}                
</script>
X.aspx.cs
btnSave.Attributes.Add("onclick","if ( !SomeClientSideValidation() ) return false;");

Technical Implementation Insight

CustomImageButton.cs

..
public class CustomImageButton : ImageButton
{
    public int DisableTimeOut ..
    public int PutDisableScriptAtEnd ..
    
    ..
    protected override void OnPreRender(EventArgs e)
    {
        #region On Pre Render Javascript
        //CustomButtonJavascript
        if (!Page.IsClientScriptBlockRegistered("CustomButtonScript") )
        {
            string CustomButtonScript = @"
            <script language="javascript">
            //    TittleJoseph@yahoo.com
            //  http://www.codeproject.com/script/articles/list_articles.asp?userid=1654009
            var idOfImageButtonsToDisable = '';
            var idOfButtonsToDisable = '';

            //Custom Image Button Clicked.
            function CustomButtonClicked(obj)
            {
                idOfImageButtonsToDisable = '';

                if ( obj.disabled == false )
                {
                    var pgForm = obj.form;
                    
                    //note: unfortunately type=image never comes up in 
                    //Form.Elements therefore searching them by tag name INPUT
                    //search all type=image, button, submit, reset and disabling them.
                    var inputs = pgForm.getElementsByTagName('input');
                    
                    for ( var i=inputs.length-1; i>=0; i--)
                    {
                    try
                    {
                var currentButton = inputs[i];
            if ( (currentButton.type == 'submit' || currentButton.type == 'button' 
            || currentButton.type == 'reset') && currentButton.disabled == false )
            {
                //storing ids of all buttons, will require when enabled back later.
                idOfButtonsToDisable += (idOfButtonsToDisable==''?'':',') + currentButton.id
                
                CreateDuplicateButtonOverSame(currentButton);
            }

            //Image Buttons and not if already disabled 
            //(duplicate button will always be disabled.)
            if ( currentButton.type == 'image' && currentButton.disabled == false )
            {
                //If image button is already disabled, do not touch it.
                if ( currentButton.src.toLowerCase().indexOf('_disable.') >= 0 )
                    continue;

                //storing ids of all image buttons, will require when enabled back later.
                idOfImageButtonsToDisable += (idOfImageButtonsToDisable==''?'':',') + 
                                              currentButton.id ;

                CreateDuplicateButtonOverSame(currentButton);
            }
                    }
                    catch(e)
                        {
                            //alert(e.description);
                        }
                    }

                    //Searching all <BUTTON> tags and disabling them as well.
                    var buttons = pgForm.getElementsByTagName('button');
                    
                for ( var i=buttons.length-1; i>=0; i--)
                {
                    try
                    {
                        var currentButton = buttons[i];
                        
                        //storing ids of all buttons, will require when enabled back later.
                        idOfButtonsToDisable += (idOfButtonsToDisable==''?'':',') + 
                                                currentButton.id

                        CreateDuplicateButtonOverSame(currentButton);
                    }
                    catch(e)
                    {
                        //alert(e.description);
                    }
                }

                    setTimeout('ReEnableAllButtons(\''+obj.id+'\')',"+
                                this.DisableTimeOut.ToString() +@");
                }
            }
            function CreateDuplicateButtonOverSame(currentButton)
            {
                //Create a duplicate button same as this 1 and disabling that.
                var dummyBtn = currentButton.cloneNode();
                dummyBtn.id = dummyBtn.id+'_clone';
                dummyBtn.disabled = true;
                //ERROR: don't know why, but name attribute is not resetting here, 
                //still removing 'name' as it is conflicting
                var nm = dummyBtn.getAttribute('name');
                dummyBtn.removeAttribute('name');
                dummyBtn.setAttribute('name',nm+'_clone');
                dummyBtn.setAttribute('onclick','');
                dummyBtn.style.display = 'none';

                //Inserting duplicate buttons before end of body
                document.body.insertAdjacentElement('beforeEnd',dummyBtn);
                
                //Getting co-ordinate of original image buttons.
                var mouseY = document.body.scrollTop + 
                             parseFloat(currentButton.getBoundingClientRect().top);
                var mouseX = document.body.scrollLeft + 
                             parseFloat(currentButton.getBoundingClientRect().left);

                //co-ordinates which are received are 2 pixel extra and 
                //do not fully cover the actual object
                mouseX = (mouseX >= 2)?mouseX-2:mouseX;
                mouseY = (mouseY >= 2)?mouseY-2:mouseY;
                
                //I'm not hiding actual button because document.activeElement 
                //will not be available then,
                //just placing disable image/button on top of actual button/image button.
                currentButton.style.position = 'static';
                dummyBtn.style.position = 'absolute';
                dummyBtn.style.left = mouseX;
                dummyBtn.style.top = mouseY;

            if ( dummyBtn.tagName == 'BUTTON' )
            {
                dummyBtn.innerHTML = currentButton.innerHTML;
            }
            else //INPUT
            {
                if ( dummyBtn.type == 'image' )
                {
                    //Changing Image to Disable image.
                    //must have file 'add_disable.gif','deleteImage_disable.jpg' 
                    //in directory structure 
                    //if actual image button url is 'add.gif', 'deleteImage.jpg' respectively.
                    var extensiondot = currentButton.src.lastIndexOf('.');
                    dummyBtn.src = currentButton.src.substr(0,extensiondot) + '_disable'+
                    currentButton.src.substr(extensiondot);
                }
            }
            dummyBtn.style.display = '';
            }

            function ReEnableAllButtons(objId)
            {
                //Image Button Object which fired the event.
                var obj = document.getElementById(objId);
                
                if ( idOfButtonsToDisable != '' )
                {
                    var buttonIds = idOfButtonsToDisable.split(',');
                    
                    for (var i=0; i< buttonIds.length; i++)
                    {
                        var btnClone = document.getElementById(buttonIds[i]+'_clone');
                        if ( btnClone != null )
                            btnClone.removeNode(true);
                        document.getElementById(buttonIds[i]).style.position='';
                    }
                    idOfButtonsToDisable = '';
                }

                if ( idOfImageButtonsToDisable != '' )
                {
                    var imageButtonIds = idOfImageButtonsToDisable.split(',');
                    
                    for (var i=0; i< imageButtonIds.length; i++)
                    {
                        var imgbtnClone = document.getElementById(imageButtonIds[i]+'_clone');
                        if ( imgbtnClone != null )
                            imgbtnClone.removeNode(true);
                        document.getElementById(imageButtonIds[i]).style.position='';
                    }
                    idOfImageButtonsToDisable = '';
                }
            }
            
            //Call this function, if you want Enable the image before the time.
            function EnableCustomButton(objId)
            {
                ReEnableAllButtons(objId);
            }                
            </script>
            ";
            Page.RegisterClientScriptBlock("CustomButtonScript",CustomButtonScript);
        }
        
        base.OnPreRender (e);
        #endregion
    }

    protected override void AddAttributesToRender(HtmlTextWriter writer) 
    {
        #region Add disabling script on onclick event
        string existingOnClick = "";
        if ( this.Attributes["onclick"] != null )
            existingOnClick = this.Attributes["onclick"];

        //script which disables all buttons on the page.
        string disableScript = "CustomButtonClicked(this);";
        string concatwith = (existingOnClick.TrimEnd(' ').EndsWith
        (";")==true||existingOnClick=="")?"":";";
        if ( existingOnClick != "" )
        {
            existingOnClick = existingOnClick.Trim();
            existingOnClick = existingOnClick.Replace("javascript:","");

            if ( PutDisableScriptAtEnd == true ) 
                disableScript = existingOnClick + concatwith + disableScript;
            else
                disableScript = disableScript + existingOnClick;
        }

        //Remove already being used "onclick", otherwise this attribute comes twice.
        this.Attributes["onclick"]=null;

        writer.AddAttribute(HtmlTextWriterAttribute.Onclick, disableScript);

        base.AddAttributesToRender(writer);
        #endregion
    }
}

FAQs

Do you force me to use just this control everywhere on my page, and I cannot use any other Custom or HTML Control?

Not at all. It is finally the JavaScript which is doing the disable magic. So you can even copy it in your Global.js and call its method for disabling thing. But I'll still say that it is better to keep everything inside the control, you may be required to create similar ImageButton where you can copy the code as instructed in Future Enhancements below, and you need not specifically call JavaScript method everywhere then, like CustomImageButton, use CustomButton as well then.

Can you advise where it is useful for me?

  • Where you just want user to click button only once, clicking button twice may lead to problem.
  • When you think some long logic is done on server, and it look likes system is slow or hang, and you don't want user to impatiently click twice on the same button.

Will this code work in .NET 2.0 or above?

I don't know because I don't have .NET 2.0 to test it. BTW, it is JavaScript which is playing the role, so you can copy JavaScript code.

Future Enhancements Possible

  • The current custom control is for ImageButton. Same code could be used for custom Button also, i.e., CustomButton which is inherited from System.Web.UI.WebControls.Button. You need to copy all exposed attributes from here to there and override methods OnPreRender and AddAttributesToRender also.
  • We can have extra attribute as StatusMessage which could show some message in status bar while the processing is going on after the button is clicked and disabled.

References

No references, this entire code is written from scratch by me only :).

Conclusion

I would really be interested in knowing if this code has helped you by any means, please do not hesitate or be lazy in dropping your comments telling what you have felt about this submission and how much it has helped you. I would appreciate it if you keep the URL of this page inside the control's code while using it.

History

  • 4th August, 2006: Initial version

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below. A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here