In rails controllers, how to prevent double submit (when user double-clic submit button or hit enter twice)?
Asked Answered
K

9

14

Well, everything's in the title but I'll explain a little more :-)

My rails app contain many forms (Ajaxified or not).

To prevent users to submit twice or more some forms, I use Javascript.

There's my scenario for a Ajaxified form :

  • the user submit the form (clic or enter)
  • the javascript disable the submit button
  • the rails controller do things (like a Soap request or an insert in a DB)
  • the rails controller update the page and enable the submit button if necessary (in case of errors)

Now I want to add server side code to keeps things really clean if the user bypass the javascript.

Any suggestions?

Katheryn answered 1/7, 2010 at 17:29 Comment(0)
P
8

Try using Redis locking and surround your block with something like

Redis.current.lock("#{current_user.id}.action_name") do
   # Some code
end

This is the gem I'm using https://github.com/mlanett/redis-lock

Presidentelect answered 19/2, 2013 at 20:30 Comment(1)
Hey, this is an old post but thank you for the answer anyway. I really like this approach with Redis locking.Katheryn
C
10

I use 4 method for 4 scenarios, please firstly prefer my awnser here: Prevent double submits in a Rails AJAX form

only click limitation for users:

use stopImmediatePropagation and add a click event to the target dom.

  /**
   * 防止按钮重复点击。
   * NOTICE: #1 需要在作用点之前调用此方法 #2 stopImmediatePropagation 会阻止后面的所有事件包括事件冒泡
   * @delay_duration 两次点击的间隔时间
   */
  $.fn.preventMultipleClick = function (delay_duration) {
    delay_duration = delay_duration || 3000;
    var last_click_time_stamp = 0;
    var time_duration = 0;
    $(this).bind('click', function (event) {
      time_duration = last_click_time_stamp ? event.timeStamp - last_click_time_stamp : 0;
      //console.debug("preventMultipleClick", last_click_time_stamp, time_duration);
      if (time_duration && time_duration < delay_duration) {
        event.stopImmediatePropagation();
      } else {
        //console.debug("skip preventMultipleClick~");
        last_click_time_stamp = event.timeStamp;
      }
    });
  };

limit the submit such as ajax:

use ajax's beforeSend attribut.

  /**
   * 使用:
   *   在jquery的ajax方法中加入参数:beforeSend
   *   例如:beforeSend: function(){return $.preventMultipleAjax(event, 5000)}
   *
   * @param event
   * @param delay_duration
   * @returns {boolean}
   */
  $.preventMultipleAjax = function (event, delay_duration) {
    delay_duration = delay_duration || 3000;
    var target = $(event.target);
    var last_click_time_stamp = target.attr("_ajax_send_time_stamp") || 0;
    var time_duration = last_click_time_stamp ? event.timeStamp - last_click_time_stamp : 0;
    //console.debug("preventMultipleAjax", last_click_time_stamp, time_duration);
    if (time_duration && time_duration < delay_duration) {
      return false;
    } else {
      //console.debug("skip preventMultipleAjax~");
      target.attr("_ajax_send_time_stamp", event.timeStamp);
      return true;
    }
  };

only for form:

<%= f.submit "Save annotation", :disable_with => "Saving...", :class => "btn btn-primary", :id => "annotation-submit-button" %>

or: disable:仅仅对表单元素,按钮等起作用,会阻止其上的事件触发

<input type="submit" value="submit" />
<input type="button" value="button" />
<input type="image" value="image" />

others:

This is the gem:https://github.com/mlanett/redis-lock

Redis.current.lock("#{current_user.id}.action_name") do
   # Some code
end
Colombia answered 23/11, 2013 at 11:57 Comment(1)
Good summary of the different solutions client and server side. Maybe the chinese parts may be translated. :-)Katheryn
T
9

You can add the option :disable_with => "Please Wait..." to the submit tag.

Tolbutamide answered 17/7, 2012 at 13:15 Comment(3)
Client side feature, not what I was looking for. Thx anyway, the tip is useful in other cases.Katheryn
Careful. Not impossible to prevent single submit with this. Still not sure what triggered this error for some users, only that something did.Blondie
I believe this is deprecated in Rails 4: https://mcmap.net/q/517518/-how-to-disable-a-form-submit-button-quot-a-l-224-ruby-on-rails-way-quot.Cq
P
8

Try using Redis locking and surround your block with something like

Redis.current.lock("#{current_user.id}.action_name") do
   # Some code
end

This is the gem I'm using https://github.com/mlanett/redis-lock

Presidentelect answered 19/2, 2013 at 20:30 Comment(1)
Hey, this is an old post but thank you for the answer anyway. I really like this approach with Redis locking.Katheryn
G
2

Here's what I'd do:

  1. Add a "token" field to an object in db that's about to be changed.
  2. Put that token into a form that modifies said object.
  3. Right after modification save that object with NEW token.
  4. Use that token in other page views.

This will not prevent double submission, but at least will prevent the changes from second commit. I.e. when user submits the form second time the code will check the submitted token against the one in database and if they do not match - do not do an update to the object.

This also has a draw back for newly created objects (i.e. if user want's to create a comment or smth like that). But in this case you may want to check the creation time interval from same user and if it's less than, say, 5 seconds - you'd prevent the object from being "created".

Gyre answered 5/7, 2010 at 8:29 Comment(1)
This approach of tagging requests is analogous to that of tagging messages in RMI/RPC, which is essentially the same thing. It's also possible to tag at a finer-grain, such as per model or object, and look at how the controller reacts to duplicates (e.g. skip the database operation but still show the new page -- the user doesn't n eed to know something went wrong).Cognomen
G
1

Not a real suggestion (I won't be surprised of downvotes), but will your site still be usable without JS? How about showing him appropriate message that for normal operation he needs to enable the JS, otherwise you won't show him lots and lots of forms on the page.

Gyre answered 1/7, 2010 at 17:32 Comment(1)
True and you made me edit my question :-) The user case I'm talking about is when the user bypass the javascript. Consider the javascript is always active. My question is mostly about the server-side part..Katheryn
C
0

1) It's nice to also show some moving indicator to let user know that something's going on, that their request is being processed. It should eliminate lots of double submits.

2) If user has disabled javascript, how're you gonna submit 'ajaxified' forms? If site becomes not functional without javascript, then it's probably best to just notify user (like Eimantas suggests).

edit One example of such indicator, just to be clear what I mean in 1.
http://www.netzgesta.de/busy/

Cello answered 1/7, 2010 at 17:39 Comment(1)
1) Sure, I'll implement one 2) I've edited my question: the real problem is if the user bypass the javascript. (if the user disable the javascript, the form submitting is processed as usual.. there's no need to prevent double submit)Katheryn
K
0

Not sure if this is helpful:

ON SERVERSIDE:

  1. On fresh load of the form, set a session['variable']=false //meaning the form isn't submitted yet.

  2. On form submit, check:

    if session['variable'] == true
    {
       do nothing...
    }
    else
    {
       set session['variable'] = true;
      //do submit logic here
    }
    
Kealey answered 5/7, 2010 at 9:1 Comment(1)
How does this prevent double submit? If a user clicks submit button twice quickly (so the first request hasn't reached the back-end yet), the second submit will still have session['variable'] = false.Judie
H
0

I happen to face the same problem as well, and I have solved it with a very simple way(maybe it is not proper or it exists something buggy I didn't notice, please inform me if you found out)

Here is my answer:

$submitButton.on('click', function(e) {
  setTimeout(() => {
    $(this).attr('disabled', '');
  }, 0);
});

The main issue I was facing was that if I double clicked my original button, it would submit request twice and caused something unpredictable, so I tried to block the button with "disabled"attribute right after clicking it. Here is what I wrote.

// !!this is an incorrect example!!
$submitButton.on('click', function(e) {
    $(this).attr('disabled', '');
});
// !!don't copy this!!

The biggest problem I was facing is that if I just disable the submit button right after clicking it, the submit request won't submit and the page will just hang there, as nothing ever happens.

I think the cause is that the "disabled" attribute prevents the submit request from submitting.(Although I have no clue what the relationship between these two...)

So, I think the disable event should be executed after submit event.

As far as I understand, form submitting is a Javascript event, and setTimeout is an async method. As Javascript executes based on event loop, the ajax event will be put in the end of the event quene, and will be executed only after all the sync events finish.

In my code, after the first click, the submit button will be disabled after 0 millisecond, which is impossible for human beings to click the second time, problem solved!

Hypogene answered 15/5, 2018 at 14:37 Comment(0)
T
0

I my case (Rails 7+), I had

= form_with model:, url:, data: { turbo: false } do |form|`

Removing data: { turbo: false } solved the issue.

Trainee answered 4/7 at 12:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.