Click here to Skip to main content
15,891,951 members
Articles / Web Development / ASP.NET
Article

ClickOnce Button Server Control

Rate me:
Please Sign up or sign in to vote.
4.75/5 (54 votes)
13 Apr 2004CPOL3 min read 303.9K   2.9K   100   86
An article that shows how to do what everyone said you shouldn't or couldn't do with the ASP.NET button control.

Introduction

Back in the days of classic ASP, when we wanted to prevent the end user from repeatedly mashing the submit button, we would disable the button with JavaScript. But with ASP.NET, it's not as simple as that anymore. When you disable any form element client side, you essentially disable it server side as well since its name value pair will not be sent with the form post. The drawback from this is that the button_click event is now rendered useless. It will never be raised!

I have searched the ASP.NET forums over for an elegant solution for this problem and I have seen a couple of nice work arounds but nothing that gives the true effect of rendering the button disabled after being clicked. So I came up with my own. Enter the ClickOnceButton server control.

The server control behaves exactly like the intrinsic button control with the exception of two new properties: DisableAfterClick and DisabledText.

When DisableAfterClick is true, the button will be disabled client side after click. If DisabledText is set, the text of the button will be set to the DisabledText after the button is disabled. Useful if you want to get a li'l visual cue (such as changing the buttons text to 'Processing..').

The main work happens in the AddAttributesToRender event:

VB
Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter)
    Dim strOnClick As String

    If IsNothing(MyBase.Page) Then
        MyBase.Page.VerifyRenderingInServerForm(Me)
    End If

    writer.AddAttribute(HtmlTextWriterAttribute.Type, "submit")
    writer.AddAttribute(HtmlTextWriterAttribute.Name, MyBase.UniqueID)
    writer.AddAttribute(HtmlTextWriterAttribute.Value, Me.Text)

    If Not IsNothing(MyBase.Page) And Me.CausesValidation_
                And MyBase.Page.Validators.Count > 0 Then
        If Me.DisableAfterClick Then
            strOnClick = Me.GetClickOnceClientValidateEvent()
        Else
            strOnClick = Me.GetClientValidateEvent()
        End If

        If MyBase.Attributes.Count > 0 And Not _
                IsNothing(MyBase.Attributes("onclick")) Then
          strOnClick = String.Concat(MyBase.Attributes("onclick"), strOnClick)
          MyBase.Attributes.Remove("onclick")
        End If

        writer.AddAttribute("language", "javascript")
        writer.AddAttribute(HtmlTextWriterAttribute.Onclick, strOnClick)
    ElseIf DisableAfterClick = True Then
        strOnClick = Me.GetOnceClickJavascript()

        If MyBase.Attributes.Count > 0 And Not _
               IsNothing(MyBase.Attributes("onclick")) Then
          strOnClick = String.Concat(MyBase.Attributes("onclick"), strOnClick)
          MyBase.Attributes.Remove("onclick")
        End If

        writer.AddAttribute("language", "javascript")
        writer.AddAttribute(HtmlTextWriterAttribute.Onclick, strOnClick)
    End If

    MyBase.AddAttributesToRender(writer)
End Sub

This creates the button programmatically as well as determines if the button causes validation and there are validators on the page or if the button just simply needs to be disabled. You'll notice it calls three distinct different properties. These are what makes the magic happen. They are as follows:

VB
Friend ReadOnly Property GetClientValidateEvent() As String
    Get
        Return "if (typeof(Page_ClientValidate) _
                  == 'function') Page_ClientValidate(); "
    End Get
End Property

Friend ReadOnly Property GetClickOnceClientValidateEvent() As String
    Get
        Return "if (typeof(Page_ClientValidate) == 'function') _
                { if(Page_ClientValidate()) { " + _
                GetOnceClickJavascript + " }} else { " + _
                GetOnceClickJavascript + " }"
    End Get
End Property

Friend ReadOnly Property GetOnceClickJavascript() As String
    Get
        Return "document.getElementsByName('" + _
          Me.OnceClickBtnName + "').item(0).setAttribute('name'," + _
          "this.getAttribute('name')); this.disabled = true; " + _
          IIf(DisabledText = String.Empty, String.Empty, _
          "this.value = '" + DisabledText + "';") + _
          "this.form.submit();"
    End Get
End Property

The property, GetClientValidateEvent(), returns the same JavaScript that the framework normally appends if your button control causes validation and there are validators on the page. This is to handle the scenario where you have the button on a page, set to not be disabled after click, and you have validators.

The property, GetClickOnceClientValidateEvent(), returns a slightly modified version of the above script. This handles the scenario when there are validators on the page and you want the button to be disabled after click. But you want to make sure it only gets disabled if the Page_ClientValidate() returns true or in the case of non IE browsers it'll just disable the button and submit.

The property, GetOnceClickJavascript(), returns the base set of JavaScript that disables handles disabling the button. It also makes sure the button gets sent over in the post so that the button click will be raised. It does this by exchanging the name attribute of a hidden field with the name of the button. The framework just needs to see the name/ID of the control sent over to raise its click event. The hidden field gets registered in the control's OnInit(EventArgs) event.

VB
Protected Overrides Sub OnInit(ByVal e As EventArgs)
    If Me.DisableAfterClick And Not Me.IsHiddenFieldRegistered Then
        MyBase.Page.RegisterHiddenField(Me.OnceClickBtnName, "")
    End If

    MyBase.OnInit(e)
End Sub

Private Function IsHiddenFieldRegistered() As Boolean
    For Each ctl As Control In MyBase.Page.Controls
        If TypeOf ctl Is HtmlControls.HtmlInputHidden Then
            If ctl.ID = Me.OnceClickBtnName Then
                Return True
            End If
        End If
    Next

    Return False
End Function

The default constant value of OnceClickBtnValue is __onceClickBtn. The IsHiddenFieldRegistered function just makes sure that the field is registered only once. This allows us to possibly have more than one button on the form that disables after click.

You may be wondering why I didn't just inherit from the System.Web.UI.WebControls.Button class. Well, the reason was the AddAttributesToRender event. I could have just hijacked that event and added the same code I have here and not have had to go through the entire process of creating the properties, functions, etc. The problem was I still needed to call MyBase.AddAttributesToRender which would then finally call the AddAttributesToRender in the System.Web.UI.WebControls.WebControl class. If I inherited from the Button class, I would end up with two onclicks and no control over the JavaScript outputted in the event of validation. This gives me complete control.

To use this in your own projects, simply add the clickoncebutton assembly to your toolbox and drag and drop it onto your webform and you're set! Enjoy!

Note: Tested on IE 6.0 and NS 7.0 with version 1.1 of the .NET framework.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
28 year old developer currently working for Sapient, Inc.

Specialities include (but not limited to): C# And VB.Net, VB6 & 5, VBScript(Classic asp), JavaScript guru, web services, xml/xsl, C++ (Win32/COM/DCOM)

In my spare time I try to continue my pursuit of music (when i have time!).

Comments and Discussions

 
QuestionDisable Reload Pin
Nick Loewecke9-May-17 23:00
Nick Loewecke9-May-17 23:00 
Questionenabling/ disabiling button Pin
Member 28350564-Nov-08 23:18
Member 28350564-Nov-08 23:18 
GeneralThanks Pin
hido262826-Aug-08 23:22
hido262826-Aug-08 23:22 
GeneralThanks a lot works for aspnet1.1 Pin
yeya8a25-Jun-08 5:53
yeya8a25-Jun-08 5:53 
GeneralCommandButton ClickOnceButton Server Control Pin
bruze16-Jan-08 20:47
bruze16-Jan-08 20:47 
GeneralThis button saved my *SS Pin
robtathome@charter.net24-Apr-07 7:24
robtathome@charter.net24-Apr-07 7:24 
GeneralRe: This button saved my *SS Pin
Eric Plowe3-Aug-07 5:03
Eric Plowe3-Aug-07 5:03 
NewsA solution for asp.net 2.0 Pin
simplicityc26-Jan-07 12:24
simplicityc26-Jan-07 12:24 
Questiondoesnt seem to trigger the ajax UpdateProgress control Pin
pdenomme4-Jan-07 4:52
pdenomme4-Jan-07 4:52 
Questionsupport ValidationGroup functionality? Pin
limkek17-Dec-06 15:42
limkek17-Dec-06 15:42 
GeneralToo much trouble Pin
Robotovich25-Oct-06 12:16
Robotovich25-Oct-06 12:16 
GeneralClient scripts on .NET 2.0 Pin
jportelas3-Oct-06 5:48
jportelas3-Oct-06 5:48 
NewsOne more click once button article Pin
Tittle Joseph6-Aug-06 23:43
Tittle Joseph6-Aug-06 23:43 
GeneralJust what I needed Pin
Nathan Wheeler28-Mar-06 4:03
Nathan Wheeler28-Mar-06 4:03 
GeneralRe: Just what I needed Pin
Eric Plowe28-Mar-06 5:09
Eric Plowe28-Mar-06 5:09 
GeneralPrompt ClickOnce Button Server Control Pin
qherb13-Mar-06 8:52
qherb13-Mar-06 8:52 
I have developed a new server control - PromptClickOnce button based on ClickOnce button. Besides the features of validation and ClickOnce, it integrates a new feature: prompt a confirmation dialog box. When it is clicked, it first validates the page, then popup a dialog to ask user's confirmation before performing ClickOnce. If you need this button, simply add it to ClickOnceButton project, and build new ClickOnceButton assemly. After you add the new assemly to your .NET toolbox, you will see this new server control. Have fun to play with it!

Imports System.ComponentModel
Imports System.Web.UI
Imports System.Drawing

<br />
<ToolboxBitmap(GetType(System.Web.UI.WebControls.Button)), DefaultEvent("Click")> _<br />
Public Class PromptClickOnceButton<br />
    Inherits System.Web.UI.WebControls.WebControl<br />
    Implements IPostBackEventHandler<br />
<br />
    Public Event Click As EventHandler<br />
    Public Event Command As System.Web.UI.WebControls.CommandEventHandler<br />
<br />
    Private Const OnceClickBtnName As String = "__onceClickBtn"<br />
<br />
<br />
    <Browsable(True), Category("Behavior")> _<br />
    Public Property DisableAfterClick() As Boolean<br />
        Get<br />
            Dim o As Object = MyBase.ViewState("DisableAfterClick")<br />
            Return IIf(IsNothing(o), False, CBool(o))<br />
        End Get<br />
        Set(ByVal Value As Boolean)<br />
            MyBase.ViewState("DisableAfterClick") = Value<br />
        End Set<br />
    End Property<br />
<br />
    <Browsable(True), Category("Behavior")> _<br />
    Public Property CommandName() As String<br />
        Get<br />
            Dim o As Object = MyBase.ViewState("CommandName")<br />
            Return IIf(IsNothing(o), String.Empty, CStr(o))<br />
        End Get<br />
        Set(ByVal Value As String)<br />
            MyBase.ViewState("CommandName") = Value<br />
        End Set<br />
    End Property<br />
<br />
    <Browsable(True), Category("Behavior")> _<br />
    Public Property CommandArgument() As String<br />
        Get<br />
            Dim o As Object = MyBase.ViewState("CommandArgument")<br />
            Return IIf(IsNothing(o), String.Empty, CStr(o))<br />
        End Get<br />
        Set(ByVal Value As String)<br />
            MyBase.ViewState("CommandArgument") = Value<br />
        End Set<br />
    End Property<br />
<br />
    <Browsable(True), Category("Appearance")> _<br />
    Public Property DisabledText() As String<br />
        Get<br />
            Dim o As Object = MyBase.ViewState("DisabledText")<br />
            Return IIf(IsNothing(o), String.Empty, CStr(o))<br />
        End Get<br />
        Set(ByVal Value As String)<br />
            MyBase.ViewState("DisabledText") = Value<br />
        End Set<br />
    End Property<br />
<br />
    <Browsable(True), Category("Appearance")> _<br />
    Public Property Text() As String<br />
        Get<br />
            Dim o As Object = MyBase.ViewState("Text")<br />
            Return IIf(IsNothing(o), "Button", CStr(o))<br />
        End Get<br />
        Set(ByVal Value As String)<br />
            MyBase.ViewState("Text") = Value<br />
        End Set<br />
    End Property<br />
<br />
    <Browsable(True), Category("Behavior")> _<br />
    Public Property CausesValidation() As Boolean<br />
        Get<br />
            Dim o As Object = MyBase.ViewState("CausesValidation")<br />
            Return IIf(IsNothing(o), True, CBool(o))<br />
        End Get<br />
        Set(ByVal Value As Boolean)<br />
            MyBase.ViewState("CausesValidation") = Value<br />
        End Set<br />
    End Property<br />
<br />
    <Browsable(True), Category("Behavior")> _<br />
Public Property ConfirmBeforeSubmit() As Boolean<br />
        Get<br />
            Dim o As Object = MyBase.ViewState("ConfirmBeforeSubmit")<br />
            Return IIf(IsNothing(o), True, CBool(o))<br />
        End Get<br />
        Set(ByVal Value As Boolean)<br />
            MyBase.ViewState("ConfirmBeforeSubmit") = Value<br />
        End Set<br />
    End Property<br />
<br />
    <Browsable(True), Category("Appearance")> _<br />
   Public Property ConfirmationMessage() As String<br />
        Get<br />
            Dim o As Object = MyBase.ViewState("ConfirmationMessage")<br />
            Return IIf(IsNothing(o), String.Empty, CStr(o))<br />
        End Get<br />
        Set(ByVal Value As String)<br />
            MyBase.ViewState("ConfirmationMessage") = Value<br />
        End Set<br />
    End Property<br />
<br />
    Public Sub New()<br />
        MyBase.New(HtmlTextWriterTag.Input)<br />
    End Sub<br />
<br />
<br />
    <Browsable(False)> _<br />
Friend ReadOnly Property GetClientValidate() As String<br />
        Get<br />
            Return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); "<br />
        End Get<br />
    End Property<br />
<br />
    <Browsable(False)> _<br />
    Friend ReadOnly Property GetClickOnceClientValidate() As String<br />
        Get<br />
            Return "if (typeof(Page_ClientValidate) == 'function') { if(Page_ClientValidate()) { " + _<br />
                    GetClickOnceJavascript + " }} else { " + _<br />
                    GetClickOnceJavascript + " }"<br />
        End Get<br />
    End Property<br />
<br />
    <Browsable(False)> _<br />
Friend ReadOnly Property GetClickOnceJavascript() As String<br />
        Get<br />
            Return "document.getElementsByName('" + Me.OnceClickBtnName + "').item(0).setAttribute('name'," + _<br />
                   "this.getAttribute('name')); this.disabled = true; " + _<br />
                   IIf(DisabledText = String.Empty, String.Empty, "this.value = '" + DisabledText + "';") + _<br />
                   "this.form.submit();"<br />
        End Get<br />
    End Property<br />
<br />
    <Browsable(False)> _<br />
Friend ReadOnly Property GetConfirmJavascript() As String<br />
        Get<br />
            Return "return confirm('" + EscapedMessage(ConfirmationMessage) + "');"<br />
<br />
        End Get<br />
    End Property<br />
<br />
    <Browsable(False)> _<br />
    Friend ReadOnly Property GetConfirm() As String<br />
        Get<br />
<br />
            Return "confirm('" + EscapedMessage(ConfirmationMessage) + "')"<br />
<br />
        End Get<br />
    End Property<br />
<br />
    <Browsable(False)> _<br />
   Friend ReadOnly Property GetConfirmReturn() As String<br />
        Get<br />
<br />
            Return "if (confirm('" + EscapedMessage(ConfirmationMessage) + "')) " + _<br />
             "{ return true;} else {return false;} "<br />
<br />
        End Get<br />
    End Property<br />
<br />
    <Browsable(False)> _<br />
Friend ReadOnly Property GetConfirmClientValidate() As String<br />
        Get<br />
            Return "if (typeof(Page_ClientValidate) == 'function') { if(Page_ClientValidate()) { " + _<br />
                    GetConfirmReturn + " }} else { " + _<br />
                    GetConfirmReturn + " }"<br />
        End Get<br />
<br />
    End Property<br />
<br />
    <Browsable(False)> _<br />
    Friend ReadOnly Property GetConfirmClickOnce() As String<br />
        Get<br />
            Return " if( " + GetConfirm + ") { " + _<br />
                    GetClickOnceJavascript + " } else {return false;}"<br />
        End Get<br />
<br />
    End Property<br />
<br />
    <Browsable(False)> _<br />
      Friend ReadOnly Property GetConfirmClickOnceClientValidate() As String<br />
        Get<br />
            Return "if (typeof(Page_ClientValidate) == 'function') { if(Page_ClientValidate()) { " + _<br />
                    GetConfirmClickOnce + " }} else { " + _<br />
                    GetConfirmClickOnce + " }"<br />
        End Get<br />
    End Property<br />
<br />
    Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)<br />
    End Sub<br />
<br />
    Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter)<br />
        Dim strOnClick As String<br />
<br />
        If IsNothing(MyBase.Page) Then<br />
            MyBase.Page.VerifyRenderingInServerForm(Me)<br />
        End If<br />
<br />
        writer.AddAttribute(HtmlTextWriterAttribute.Type, "submit")<br />
        writer.AddAttribute(HtmlTextWriterAttribute.Name, MyBase.UniqueID)<br />
        writer.AddAttribute(HtmlTextWriterAttribute.Value, Me.Text)<br />
<br />
        If Not IsNothing(MyBase.Page) And Me.CausesValidation And MyBase.Page.Validators.Count > 0 Then<br />
            If Me.DisableAfterClick Then<br />
                If Me.ConfirmBeforeSubmit Then<br />
                    strOnClick = Me.GetConfirmClickOnceClientValidate<br />
                Else<br />
                    strOnClick = Me.GetClickOnceClientValidate<br />
                End If<br />
            Else<br />
                If Me.ConfirmBeforeSubmit Then<br />
                    strOnClick = Me.GetConfirmClientValidate<br />
                Else<br />
                    strOnClick = Me.GetClientValidate<br />
                End If<br />
            End If<br />
            If MyBase.Attributes.Count > 0 And Not IsNothing(MyBase.Attributes("onclick")) Then<br />
                strOnClick = String.Concat(MyBase.Attributes("onclick"), strOnClick)<br />
                MyBase.Attributes.Remove("onclick")<br />
            End If<br />
<br />
            writer.AddAttribute("language", "javascript")<br />
            writer.AddAttribute(HtmlTextWriterAttribute.Onclick, strOnClick)<br />
<br />
        ElseIf Me.DisableAfterClick Or Me.ConfirmBeforeSubmit Then<br />
            If Me.DisableAfterClick Then<br />
               If Me.ConfirmBeforeSubmit Then<br />
                   strOnClick = Me.GetConfirmClickOnce<br />
                Else<br />
                    strOnClick = Me.GetClickOnceJavascript<br />
                End If<br />
            Else<br />
                If Me.ConfirmBeforeSubmit Then<br />
                    strOnClick = Me.GetConfirmJavascript<br />
                End If<br />
            End If<br />
<br />
            If MyBase.Attributes.Count > 0 And Not IsNothing(MyBase.Attributes("onclick")) Then<br />
                strOnClick = String.Concat(MyBase.Attributes("onclick"), strOnClick)<br />
                MyBase.Attributes.Remove("onclick")<br />
            End If<br />
<br />
            writer.AddAttribute("language", "javascript")<br />
            writer.AddAttribute(HtmlTextWriterAttribute.Onclick, strOnClick)<br />
        End If<br />
<br />
        MyBase.AddAttributesToRender(writer)<br />
<br />
    End Sub<br />
<br />
    Protected Overrides Sub OnInit(ByVal e As EventArgs)<br />
        If Me.DisableAfterClick And Not Me.IsHiddenFieldRegistered Then<br />
            MyBase.Page.RegisterHiddenField(Me.OnceClickBtnName, "")<br />
        End If<br />
        MyBase.OnInit(e)<br />
    End Sub<br />
<br />
    Private Function IsHiddenFieldRegistered() As Boolean<br />
        For Each ctl As Control In MyBase.Page.Controls<br />
            If TypeOf ctl Is HtmlControls.HtmlInputHidden Then<br />
                If ctl.ID = Me.OnceClickBtnName Then<br />
                    Return True<br />
                End If<br />
            End If<br />
        Next<br />
        Return False<br />
    End Function<br />
<br />
    Protected Overridable Sub OnClick(ByVal e As EventArgs)<br />
        RaiseEvent Click(Me, e)<br />
    End Sub<br />
<br />
    Protected Overridable Sub OnCommand(ByVal e As System.Web.UI.WebControls.CommandEventArgs)<br />
        RaiseEvent Command(Me, e)<br />
    End Sub<br />
<br />
    Private Sub RaisePostBackEvent(ByVal eventArgument As String) Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent<br />
        If Me.CausesValidation Then<br />
            MyBase.Page.Validate()<br />
        End If<br />
        Me.OnClick(New EventArgs)<br />
        Me.OnCommand(New System.Web.UI.WebControls.CommandEventArgs(Me.CommandName, Me.CommandArgument))<br />
    End Sub<br />
<br />
    Private Function EscapedMessage(ByVal strMsg As String) As String<br />
        Return Replace(ConfirmationMessage, "'", "\'")<br />
    End Function<br />
<br />
End Class


-- modified at 10:49 Tuesday 2nd May, 2006
GeneralRe: Prompt ClickOnce Button Server Control Pin
Anjum Rizwi1-May-06 19:32
Anjum Rizwi1-May-06 19:32 
GeneralRe: Prompt ClickOnce Button Server Control Pin
qherb2-May-06 4:39
qherb2-May-06 4:39 
GeneralRe: Prompt ClickOnce Button Server Control Pin
Anjum Rizwi2-May-06 19:47
Anjum Rizwi2-May-06 19:47 
Generalthank you Pin
earthdance30-Nov-05 7:42
earthdance30-Nov-05 7:42 
GeneralClick and Disable Pin
25-Oct-05 22:39
suss25-Oct-05 22:39 
Generalsubmit and onsubmit Pin
Kirk Quinbar19-Sep-05 6:18
Kirk Quinbar19-Sep-05 6:18 
GeneralMy C# version - with some limitations Pin
trondborg18-Aug-05 13:59
trondborg18-Aug-05 13:59 
GeneralRe: My C# version - with some limitations Pin
trondborg18-Aug-05 18:24
trondborg18-Aug-05 18:24 
GeneralRe: My C# version - with some limitations Pin
trondborg19-Aug-05 6:28
trondborg19-Aug-05 6:28 

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.