0

Consider this scenario:

  1. form.html has a link to send_email.php.
  2. A user clicks the link twice very quickly.
  3. The server receives two requests for send_email.php and processes them both.
  4. send_email.php sent two emails, but only one should have been sent.

How can we use jQuery to disable the link after the first page request has been made by the browser?

I have built you an elegant MWE. This webpage links to itself and increments a counter with every page request. If you quickly double-click the link (before the current DOM is destroyed to load the page request), it will make two page requests, and you'll see the counter increment by two. Your task is to write jQuery that will allow the first page request to process as usual, but block any subsequent clicks from creating additional page requests.


Many other people ask questions that seem similar but are actually entirely different use cases. Often they want this:

  1. Display a link or a button on a webpage
  2. Always block its default event (making a page request or submitting a form)
  3. Run some arbitrary Javascript exactly once
  4. Subsequently disable the link or button Here are solutions to this different question.

Notice how my use case is different. I have no Javascript to run. I do want the link or button to work as usual! I want it to work exactly once! All I want is to prevent a user from creating a bunch of page requests by mashing a link or a button. I want to allow the normal link or button function, but only once!


I should be able to drop some jQuery into this PHP script and accomplish exactly what the page describes as the expected behavior.

<?php

sleep(1);

session_name('have-a-cookie');
session_start(['cookie_lifetime' => 3600]);

$_SESSION['counter'] = isset($_SESSION['counter']) ? $_SESSION['counter']+1 : 0;

?>
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <link rel="stylesheet" href="bootstrap.min.css" type="text/css" />
        <script src="jquery-3.6.1.min.js"></script>
        <script src="bootstrap.bundle.min.js"></script>
        <script>
// this does not work
// https://stackoverflow.com/questions/41693832/disable-a-link-after-click-using-jquery
$('.btn').on('click', function(e) {
    $(this).prop('disabled',true);
});
        </script>
    </head>
    <body>
        <div class="m-3 card text-dark bg-light" style="max-width: 20em;">
            <div class="card-body">
                <h2>Count: <?php echo $_SESSION['counter']; ?></h2>
                <p>The goal is to prevent multiple requests.</p>
                <p>Double-clicking the button should make <b>one</b> request and increment the counter by one.</p>
                <p><a class="btn btn-primary" href="mwe.php">Count!</a></p>
                <p class="mb-0"><a href="mwe.php.txt">Source code</a></p>
            </div>
        </div>
    </body>
</html>
Lucas
  • 139
  • 6
  • 1
    Does this answer your question? [How to prevent a double-click using jQuery?](https://stackoverflow.com/questions/11621652/how-to-prevent-a-double-click-using-jquery) – Juan Oct 19 '22 at 04:33
  • @Juan no, it doesn't. those answer block the link from working like a link using ` event.preventDefault();`, allow for some arbitrary Javascript to run once, and block subsequent clicks from running the arbitrary Javascript. Notice that I want to allow the link to work like a normal link exactly once. I do not want to run arbitrary Javascript. Immediately after the user clicks the link, the browser should make the page request as usual. I want to block a second click from making a second page request. – Lucas Oct 27 '22 at 22:21
  • Does this answer your question? [Disabling links to stop double-clicks in JQuery](https://stackoverflow.com/questions/1681679/disabling-links-to-stop-double-clicks-in-jquery) – Don't Panic Oct 28 '22 at 07:00

2 Answers2

2

Update

With further clarification in the comments, all OP needs is to disable further clicks on the link while the browser is in the process of navigating to the target page. As you suggested, there are indeed many duplicate questions, here's one where the accepted answer should work fine for your use case.

The idea is that the very first click on the link will fire this event handler, which will only run once, and will not stop the actual navigation. The event handler creates a new event handler that disables further clicks.

I voted to close this question as a duplicate of (also linked above):


Original Answer

Searching for how to disable a link turns up this article, which gives some very good reasons about why not to do it. A link without a, well, link, is meaningless - links are for navigating, and if you don't intend to navigate somewhere, maybe it shouldn't be a link.

The article suggets removing the href, but in your case you are not actually navigating, so that would not help. Your link is styled as a button, and for good reason - making it a button is not only more semantically correct, it would make disabling it a whole lot easier.

If you really want to stick with a link:

  • removing href is useless because you are not actually navigating anyway;

  • .attr('disabled', 'disabled'), or .prop() variants are useless because a link cannot be disabled;

  • .preventDefault() is actually required, because the default action when clicking a link is to navigate, and you don't want to do that, but it won't stop future clicks;

  • The nuclear option - removing the entire click handler so no more clicks are processed by JS - won't work either, because then you lose your .preventDefault(), and a click will act like a plain, vanilla, no-JS click and naviate away to the href target!

One option is to track whether or not the link has been clicked with a variable. Set it to true first time through, and only allow the processing if it is false. Working example:

// Track whether or not the link has been clicked
let clicked = false;

$('.btn').on('click', function(e) {
    // The default action for a link is to navigate - you don't
    // want to do that, so you do need to prevent it
    e.preventDefault();
    
    // Check if we have clicked, and bail out if so
    if (clicked) {
        return;
    }
    
    // Track that we have clicked
    clicked = true;
    
    // Some visual indicator so we can see what is happening
    console.log('click!');    
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<a class="btn" href="somewhere.html">Count!</a>

If you want to switch to a button, things are considerably simpler, as well as being semantically correct and elegant:

$('.btn').on('click', function(e) {
    // The default action for a button is to submit - you don't
    // want to do that, so you do need to prevent it
    e.preventDefault();
    
    // Disable button
    $(this).attr('disabled', 'disabled');
    
    // Some visual indicator so we can see what is happening
    console.log('click!');    
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<button class="btn" href="somewhere.html">Count!</button>
Don't Panic
  • 13,965
  • 5
  • 32
  • 51
  • Thanks for the article. I agree, disabling links is weird and doesn't really make sense. Thanks for the rest of your discussion as well. I am perfectly happy to use a button. [Here is your solution using a button.](https://www.wingedleopard.net/disable_button/solution_dont-panic.php) The button doesn't work at all. It should allow exactly one click. It should disable subsequent clicks. – Lucas Oct 19 '22 at 01:12
  • I don't really understand why you say "in your case you are not actually navigating." I do want the link to work. I want it to make exactly one page request. I want all subsequent click-requests disabled. I'm not sure how to further clarify my question. I don't want a user double-clicking the button and creating two page requests. – Lucas Oct 19 '22 at 01:22
  • RE: the copy of code on the remote site - I expect it isn't working bcs your code isn't wrapped in [`$(document).ready()`](https://api.jquery.com/ready/). The code in my answer works - it is a runnable snippet, click the "run" button to confirm. As to navigating - I guess I don't understand. If you want to allow normal navigation, clicking the link would mean your browser will end up on the `mwe.php` page, and your button won't be visible anymore, so you couldn't click it a 2nd time anyway, unless/until you navigate back to the page where the button is. – Don't Panic Oct 19 '22 at 05:41
  • Maybe it is a terminology thing - by "*navigate*" do you mean "click"? Are you really trying to browse *to* the `mwe.php` page? Load it with AJAX or something like that? – Don't Panic Oct 19 '22 at 23:17
  • Look, my problem and question are all very simple. Imagine a webpage parent.html with a link to child.html. If the user clicks it once, it starts to load child.html. But if the user double-clicks the link, TWO page requests are made! I want to block the second page request. I want the link (or button or whatever element you want to use) to be disabled *after* the first click. The first click of the link should work as usual. – Lucas Oct 27 '22 at 22:24
  • I created an MWE that shows if you managed to make multiple page requests before the next page loads. So all you need to do is add some jQuery to that page so that if I double-click the button, it will *never* increment the counter by two (this happens because two page requests are made). – Lucas Oct 27 '22 at 22:32
  • I don't know why you're talking about AJAX. The page is exactly what it is. There's no AJAX present or desired. Yes, I really want to link to mwe.php, which is why I linked to it. It seems that you're assuming that I miswrote something and you're looking for a different problem to solve. My problem is *extremely* simple... Please read my re-written question above! – Lucas Oct 27 '22 at 22:54
  • You can click the button twice if you do it quickly. Most double-clicks are in quick succession. You can usually get two or three clicks in before the browser destroys the DOM. Try to click the MWE button twice quickly before it reloads the page! – Lucas Oct 27 '22 at 22:56
0

One solution is to convert the button-styled link into a submit button within a form.

Use this script:

(function () {
    var allowSubmit = true;
    $('form').onsubmit = function () {
       if (allowSubmit)
           allowSubmit = false;
       else
           return false;
    }
})();

Credit to Andy E for this component.

Substitute the <button> with a form:

<form action="solution_lucas.php" method="get">
    <input type="submit" class="btn btn-primary" value="Count!" />
</form>

The live working solution is available here (at least temporarily).

Complete source code for the working solution is available here.

I will wait for other better or more elegant solutions! This one puts a question mark at the end of the URL. It is highly flexible though, in case you want send data with the request.

Lucas
  • 139
  • 6