Click here to Skip to main content
16,016,580 members
Articles / Web Development / ASP.NET

Click Once Button

Rate me:
Please Sign up or sign in to vote.
2.38/5 (14 votes)
4 Aug 20065 min read 73.8K   613   25   13
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

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

    and when PutDisableScriptAtEnd = true

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

Using the Code

Using CustomImageButton

HTML
<Tittle:CustomImageButton id=btnXLS onclick=btnExport_Click Runat="server" 
 ImageUrl="Images/btnExportToExcel.gif">

HTML of same would be:

HTML
<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

HTML
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

HTML
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

C#
..
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.


Written By
Team Leader Royal Bank of Scotland
India India
Total 11+ years of experience in Software Development. Expertise in .net (C#, asp.net, controls making, webservice, ado.net, xml/xsl, assemblies, rational XDE etc.) also UML, Design Patterns, ASP, Javascript, VbScript, Dhtml, CSS etc.

Member of Planet-Source-Code.com, www.brainbench.com and many other and have submitted several snippets/sample applications on the web.

Email:tittlejoseph@gmail.com

Comments and Discussions

 
Generalset attribute Pin
sydausguy14-Mar-07 7:35
sydausguy14-Mar-07 7:35 
GeneralRe: set attribute Pin
Tittle Joseph16-Mar-07 1:57
Tittle Joseph16-Mar-07 1:57 
QuestionCan only get it to work locally Pin
Lamebot8-Feb-07 23:18
Lamebot8-Feb-07 23:18 
AnswerRe: Can only get it to work locally Pin
Tittle Joseph9-Feb-07 4:24
Tittle Joseph9-Feb-07 4:24 
GeneralRe: Can only get it to work locally Pin
Lamebot19-Feb-07 3:17
Lamebot19-Feb-07 3:17 
Hi,

Thanks for your response, really appreciate it.

I'm using 2003, haven't used 2005 myself either. I've spent quite a while debugging and this is what i've found out:

1) There aren't any errors occurring. The code runs as it should do, it never trips into the "catch" block and i've tried removing that too, but no errors.

2) As i said, the code is running and the dummy buttons are being created. I've discovered that I only see them when I have an alert in the page after the button has been created. So if after the line in "CreateDuplicateButtonOverSame" where it sets the source of the button: "dummyBtn.src = ...", I add: "alert(dummyBtn.src);", I will see the buttons appear. With no alerts in, the code definitely runs, but I don't see the dummy buttons appear.

So it's some kind of display issue I think. The alert box forces the page to be redrawn and then the dummyBtn appears. Without the alert it never does.

As I said, it works for me locally, i've no idea why online would be different?

I'm using IE7, unfortunately I don't have 6 to test on.

Any help you give me would be greatly appreciated.

Thanks

Dan
GeneralCauses validation Pin
jportelas3-Oct-06 5:53
jportelas3-Oct-06 5:53 
GeneralRe: Causes validation Pin
Tittle Joseph3-Oct-06 23:13
Tittle Joseph3-Oct-06 23:13 
GeneralRating Pin
Tittle Joseph6-Aug-06 23:16
Tittle Joseph6-Aug-06 23:16 
GeneralDisabled Buttons in IE Pin
rjpaulsen4-Aug-06 6:47
rjpaulsen4-Aug-06 6:47 
GeneralRe: Disabled Buttons in IE Pin
Anton Afanasyev4-Aug-06 10:13
Anton Afanasyev4-Aug-06 10:13 
GeneralRe: Disabled Buttons in IE Pin
Tittle Joseph6-Aug-06 19:45
Tittle Joseph6-Aug-06 19:45 
GeneralRe: Disabled Buttons in IE Pin
Anton Afanasyev6-Aug-06 19:49
Anton Afanasyev6-Aug-06 19:49 
GeneralRe: Disabled Buttons in IE Pin
Tittle Joseph7-Aug-06 4:23
Tittle Joseph7-Aug-06 4:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.