在rails控制器中,如何防止双重提交(当用户双击提交按钮或按两次回车键时)?

发布于 2024-09-07 17:01:28 字数 388 浏览 3 评论 0原文

好吧,一切都在标题中,但我会解释更多一点:-)

我的 Rails 应用程序包含许多表单(Ajax 化或非 Ajax 化)。

为了防止用户提交两次或多次某些表单,我使用了 Javascript。

我的 Ajax 化表单场景如下:

  • 用户提交表单(clic 或 Enter),
  • javascript 禁用提交按钮,
  • rails 控制器执行操作(例如 Soap 请求或插入数据库),
  • rails 控制器更新页面并启用如有必要,请提交按钮(以防出现错误)

现在我想添加服务器端代码,以便在用户绕过 javascript 时保持内容真正干净。

有什么建议吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

只想待在家 2024-09-14 17:01:45

我的情况(Rails 7+),我

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

删除 data: { Turbo: false } 解决了这个问题。

I my case (Rails 7+), I had

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

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

清欢 2024-09-14 17:01:44

我碰巧也遇到了同样的问题,我用一个非常简单的方法解决了它(也许它不正确或者存在我没有注意到的错误,如果您发现请通知我)

这是我的答案:

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

我面临的主要问题是,如果我双击原来的按钮,它会提交两次请求并导致不可预测的情况,因此我尝试在单击按钮后立即使用“禁用”属性来阻止该按钮。这是我写的。

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

我面临的最大问题是,如果我在单击提交按钮后立即禁用它,则提交请求将不会提交,并且页面将挂在那里,因为什么也没有发生。

我认为原因是“disabled”属性阻止了提交请求的提交。(虽然我不知道这两者之间有什么关系......)

所以,我认为禁用事件应该在提交事件之后执行.

据我了解,表单提交是一个Javascript事件,而setTimeout是一个异步方法。由于Javascript是基于事件循环执行的,ajax事件会被放在事件队列的末尾,并且只有在所有同步事件完成后才会执行。

在我的代码中,第一次点击后,提交按钮将在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!

天涯沦落人 2024-09-14 17:01:43

不确定这是否有帮助:

在服务器端:

  1. 在表单的新加载上,
    set a session['variable']=false //表示表单尚未提交。

  2. 提交表单时,检查:

    if session['variable'] == true
    {
       什么都不做...
    }
    别的
    {
       设置会话['变量'] = true;
      //这里提交逻辑
    }
    

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
    }
    
江城子 2024-09-14 17:01:41

1)最好还显示一些移动指示器,让用户知道正在发生某些事情,他们的请求正在处理。它应该消除大量的双重提交。

2)如果用户禁用了javascript,你将如何提交“ajaxified”表单?如果网站在没有 JavaScript 的情况下无法正常工作,那么最好只是通知用户(就像 Eimantas 建议的那样)。

编辑
此类指标的一个示例,只是为了清楚我在 1 中的意思。
http://www.netzgesta.de/busy/

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/

迷爱 2024-09-14 17:01:40

这不是一个真正的建议(我不会对反对票感到惊讶),但是如果没有 JS,你的网站仍然可以使用吗?向他显示适当的消息如何,为了正常操作,他需要启用 JS,否则你不会在页面上向他显示大量的表单。

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.

不羁少年 2024-09-14 17:01:39

这就是我要做的:

  1. 将“令牌”字段添加到数据库中即将更改的对象。
  2. 将该标记放入修改所述对象的表单中。
  3. 修改后立即使用新令牌保存该对象。
  4. 在其他页面视图中使用该标记。

这不会阻止重复提交,但至少会阻止第二次提交的更改。即,当用户第二次提交表单时,代码将根据数据库中的令牌检查提交的令牌,如果它们不匹配,则不对对象进行更新。

这对于新创建的对象也有一个缺点(即,如果用户想要创建评论或类似的东西)。但在这种情况下,您可能想要检查同一用户的创建时间间隔,如果小于 5 秒,您将阻止“创建”对象。

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".

残花月 2024-09-14 17:01:38

尝试使用 Redis 锁定并用类似

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

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

带上头具痛哭 2024-09-14 17:01:36

您可以添加选项:disable_with => “请稍候...”到提交标签。

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

奶茶白久 2024-09-14 17:01:35

我对 4 个场景使用 4 种方法,请首先在这里选择我的 awnser:防止Rails AJAX表单中的双重提交

仅对用户进行点击限制:

使用stopImmediatePropagation并向目标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;
      }
    });
  };

限制ajax等提交:

使用ajax的beforeSend属性。

  /**
   * 使用:
   *   在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;
    }
  };

仅适用于形式:

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

或:
禁用:表单对表单元素,按钮等作业,会阻止其上的事件触发

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

其他:

这是gem:https://github.com/mlanett/redis-lock

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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文