views:

2033

answers:

8

Hi there, having a slight problem with an ASP.net page of mine. If a user were to double click on a "submit" button it will write to the database twice (i.e. carry out the 'onclick' method on the imagebutton twice)

How can I make it so that if a user clicks on the imagebutton, just the imagebutton is disabled?

I've tried:

<asp:ImageButton 
     runat="server" 
     ID="VerifyStepContinue" 
     ImageUrl=image src 
     ToolTip="Go" 
     TabIndex="98" 
     CausesValidation="true" 
     OnClick="methodName" 
     OnClientClick="this.disabled = true;" />

But this OnClientClick property completely stops the page from being submitted! Any help?

Sorry, yes, I do have Validation controls... hence the icky problem.

+7  A: 

The general approach is twofold.

Serverside:

  1. On load of the page, generate a token (using System.Random), save it in the session, and write it to a hidden form field
  2. On submit, check that the hidden form field equals the session variable (before setting it again)
  3. Do work

Clientside:

Similar to what you have, but probably just hide the button, and replace it with some text like 'submitting'.

The important thing to note, client side, is that the user may cancel the post by hitting 'escape', so you should consider what to do here (depending on how far along they are the token won't be used, so you'll need to bring the button back from being disabled/hidden).

Complete example follows:

C# (includes code to see it in action):

<html>
<head runat="server">
    <title>double-click test</title>
    <script language="c#" runat="server">
    private Random
        random = new Random();

    private static int
        TEST = 0;

    public void Page_Load (object sender, EventArgs ea)
    {
        SetToken();
    }

    private void btnTest_Click (object sender, EventArgs ea)
    {
        if( IsTokenValid() ){
            DoWork();
        } else {
            // double click
            ltlResult.Text = "double click!";
        }
    }

    private bool IsTokenValid ()
    {
        bool result = double.Parse(hidToken.Value) == ((double) Session["NextToken"]);
        SetToken();
        return result;
    }

    private void SetToken ()
    {
        double next = random.Next();

        hidToken.Value       = next + "";
        Session["NextToken"] = next;
    }


    private void DoWork ()
    {
        TEST++;
        ltlResult.Text = "DoWork(): " + TEST + ".";
    }
    </script>
</head>
<body>
    <script language="javascript">
        var last = null;
        function f (obj)
        {
            obj.src = "http://www.gravatar.com/avatar/4659883ec420f39723c3df6ed99971b9?s=32&amp;d=identicon&amp;r=PG";
            // Note: Disabling it here produced strange results. More investigation required.
            last = obj;
            setTimeout("reset()", 1 * 1000);
            return true;
        }

        function reset ()
        {
            last.src = "http://www.gravatar.com/avatar/495ce8981a5127a9fd24bd72e7e3664a?s=32&amp;d=identicon&amp;r=PG";
            last.disabled = "false";
        }
    </script>
    <form id="form1" runat="server">
        <asp:HiddenField runat="server" ID="hidToken" />
        <asp:ImageButton runat="server" ID="btnTest"
            OnClientClick="return f(this);"
            ImageUrl="http://www.gravatar.com/avatar/495ce8981a5127a9fd24bd72e7e3664a?s=32&amp;d=identicon&amp;r=PG" OnClick="btnTest_Click" />
        <pre>Result: <asp:Literal runat="server" ID="ltlResult" /></pre>
    </form>
</body>
</html>
Noon Silk
+1. nice .......
Mitch Wheat
There's no built-in, simple way of handling this problem in ASP.Net?
MusiGenesis
MusiGenesis: All I know is I now have 7777 rep and I never want a single bit more (http://www.ffonline.com/media/guides/ff7/ffvii_faq_lucky7.txt) (And no).
Noon Silk
So like:int token = 0;Page_Load(){System.Random rand = new Random();int token = rand.Next(1,1000000000000);Session["Token"] = token;//Anything else?}methodName(){//What would go here first?submissionMethod();}And then anything on the actual ASP.net as well?
David Archer
@silky: one more reason ASP.Net sucks Cheez Whiz through a flex straw. Sigh. They could have just designed ASP.Net as a sensible web development platform instead trying to let developers pretend they were still coding Windows application, but nooooooo ...
MusiGenesis
Also, +1 to mess up your 7777 perfection. See you at 8888.
MusiGenesis
David: Added more info, MusiGenesis: Damn you!, Mitch: Thanks :)
Noon Silk
@silky: A small nitpick - you shouldn't use a static `Random` like this in an `.aspx` page without some locking around it.
LukeH
Luke: Why? Is there potentially some issue with two threads being inside `.Next` at the same time? Hmm, probably there is. Alright, I'll change it to not be static.
Noon Silk
@silky: Yep, that's exactly it!
LukeH
Please see latest addition, still having problems...
David Archer
Alright. Out of embarassment of getting it slightly wrong, I've written a complete example. I think it should work (javascript to set disable may be slightly wrong). Please test and let me know. For some reason I can't rest until it works ... :P
Noon Silk
Wow, awesome, this has really helped! +1!
David Archer
Downvoted because the code doesn't work. If you disable the button the server side event does not fire. And reset() complains about obj not being defined.
Jan Aagaard
I don't suppose there are any options that don't rely on the Session for the token? If a user has two data entry forms open side by side filling them both in then the second submit will not work.
Robin Day
+3  A: 

I have solved this by setting a hidden field on the client click before hitting the server.

Then in the server I check the hidden field and if the value is for example something 'FALSE' that might mean I can or cannot of the action.

David Basarab
+4  A: 

If you have validation on the page, disabling the button client side gets a little tricky. If validation fails, you don't want to disable the button. Here's a snippet that adds the client side event handler:

private void BuildClickOnceButton(WebControl ctl)

{

System.Text.StringBuilder sbValid = new System.Text.StringBuilder();

sbValid.Append("if (typeof(Page_ClientValidate) == 'function') { ");

sbValid.Append("if (Page_ClientValidate() == false) { return false; }} ");

sbValid.Append(ctl.ClientID + ".value = 'Please wait...';");

sbValid.Append(ctl.ClientID + ".disabled = true;");

// GetPostBackEventReference obtains a reference to a client-side script 
// function that causes the server to post back to the page.

sbValid.Append(ClientScript.GetPostBackEventReference(ctl, ""));

sbValid.Append(";");

ctl.Attributes.Add("onclick", sbValid.ToString());

}

See this asp.net thread for more info.

Update: the above code would be used to add the OnClientClick handler in code behind. You could also write the javascript in your aspx markup like so:

<script type="text/javascript">
function disableButton(button)
{
    // if there are client validators on the page
    if (typeof(Page_ClientValidate) == 'function') 
    {
        // if validation failed return false
        // this will cancel the click event
        if (Page_ClientValidate() == false) 
        { 
            return false; 
        }
    }

    // change the button text (does not apply to an ImageButton)
    //button.value = "Please wait ...";
    // disable the button
    button.disabled = true;

    // fire postback
    __doPostBack(button.id, '');
}
</script>

<asp:ImageButton runat="server" ID="VerifyStepContinue" ImageUrl="button.png" 
    ToolTip="Go" TabIndex="98" CausesValidation="true" OnClick="methodName" 
    OnClientClick="return disableButton(this);" />
jrummell
This looks like what I'm after, but not quite sure how to work with this... which variables would I need to change for mine? I've looked at the forum post and it seems to be right, but I can't quite get at it.
David Archer
I updated my answer with an example. Hope this helps!
jrummell
Again, doesn't work... when I try to use this, it still disables everything.
David Archer
Do you get a javascript error? Is the script disabling more than just the image button?
jrummell
Disabling more than just the image button, yes.
David Archer
I can't see how disableButton() could disable anything other that the button element passed to it as a parameter. But it looks like you found your answer. If you still want to try this, let me know exactly what's being disabled and I'll offer any help that I can.
jrummell
A: 

Similar to Silky's client-side response, I usually make two buttons that look alike except that the second button is disabled and hidden. OnClientClick of the normal button swaps the display styles of the two buttons so that the normal button is hidden and the disabled button is shown.

Matt Hamsmith
A: 

Ouch, bad response to this one. Going to keep working on it then :)

David Archer
Sorry, but I have to downvote this; you should never be sleeping like this to solve this problem; if my posted code isn't working for you, let me know and I'll fix it.
Noon Silk
Are you putting system in sleep ? Dude it's not a good practice, it may annoy you in huge data-driven applications.
Braveyard
Aye, I thought as much. Trying out Silky's solution, still quite new to ASP.net so trying to adapt it :)
David Archer
A: 

The double-click feature is a server-side implementation to prevent processing that same request which can be implemented on the client side through JavaScript. The main purpose of the feature is to prevent processing the same request twice. The server-side implementation does this by identifying the repeated request; however, the ideal solution is to prevent this from occurring on the client side.

In the HTML content sent to the client that allows them to submit requests, a small validation JavaScript can be used to check whether the request has already been submitted and if so, prevent the online shopper from submitting the request again. This JavaScript validation function will check the global flag to see if the request has been submitted and, if so; does not resubmit the request. If the double-click feature is disabled on the server, it is highly recommended that the JSP and HTML pages implement this JavaScript prevention.

The following example prevents the form from being submitted more then once by using the onSubmit() action of the form object:

...
<script>
var requestSubmitted = false;
       function submitRequest() {
              if (!requestSubmitted ) {
                     requestSubmitted  = true;
                     return true;
              }
              return false;
       }
</script>
...

       <FORM method="POST" action="Logon" onSubmit="javascript:submitRequest()">
              ......
       </FORM>
Braveyard
The onsubmit code should look like this: onsubmit="return submitRequest()".
Jan Aagaard
A: 

Working on this still, up to this point now:

ASP code:

 <asp:TextBox ID="hidToken" runat="server" Visible="False" Enabled="False"></asp:TextBox>
 ...
 <asp:ImageButton runat="server" ID="InputStepContinue" Name="InputStepContinue" ImageUrl="imagesrc" ToolTip="Go" TabIndex="98" CausesValidation="true" OnClick="SubmitMethod" OnClientClick="document.getElementById('InputStepContinue').style.visibility='hidden';" />

C# code:

         private Random
    random = new Random();


    protected void Page_Load(object sender, EventArgs e)
    {
        //Use a Token to make sure it has only been clicked once.
        if (Page.IsPostBack)
        {
            if (double.Parse(hidToken.Text) == ((double)Session["NextToken"]))
            {
                InputMethod();
            }
            else
            {
                // double click
            }
        }

        double next = random.Next();

        hidToken.Text = next + "";
        Session["NextToken"] = next;

Actually... this nearly works. The double click problem is pretty much fixed (yay!) The image still isn't hidden though.

David Archer