You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails 6 form_with remote UJS:绑定额外处理程序遇阻

Rails远程投票表单自定义处理方案

问题核心

需要在rails-ujs远程提交投票表单前执行自定义DOM操作(禁用其他投票表单、显示加载模态框、降级旧的"最爱"投票),但遇到两个问题:

  • 绑定select的change事件触发提交时,浏览器弹出离开页面提示
  • 绑定表单的submit事件时,表单无响应且无报错

解决思路&方案

1. 正确触发rails-ujs远程提交

直接调用form.submit()会绕过rails-ujs的事件拦截,改用Rails官方提供的Rails.fire方法触发提交,确保rails-ujs的远程逻辑生效,同时避免浏览器的beforeunload误判。

2. 利用rails-ujs的AJAX事件钩子处理状态

通过绑定表单的ajax:beforeSend、ajax:complete等事件,在提交前后执行自定义逻辑,同时避免直接拦截submit事件导致的无响应问题。

3. 修正beforeunload的状态判断

在AJAX请求完成(无论成功失败)后更新保存状态,避免浏览器误提示。

修改后的代码示例

表单部分(无需修改)

# partial appears on PostsController#show
# there are several of these on the page, for voting on post titles

<%= form_with(model: @vote, url: naming_vote_path(naming_id: naming.id),
              method: :patch, local: false, id: "cast_vote_#{naming.id}",
              class: "naming-vote-form") \
              do |f| %>
  <%= f.select(:value, menu, {},
               { class: "form-control w-100",
                 data: { role: "change_vote", id: naming.id } }) %>
<% end %>

JavaScript部分

$(document).ready(function () {
  let _haveVotesChanged = false;
  let _haveVotesBeenSaved = false;

  const change_vote_selects = () => $("[data-role='change_vote']");
  const vote_forms = () => $(".naming-vote-form");

  const resetVoteState = () => {
    // 恢复所有select可用状态
    change_vote_selects().removeAttr("disabled");
    // 关闭模态框
    $("#naming_ajax_progress").modal('hide');
    // 更新保存状态
    _haveVotesBeenSaved = true;
  };

  const attachBindings = () => {
    // 绑定select的change事件,执行前置逻辑后触发提交
    change_vote_selects().on("change", function (event) {
      const $select = $(this);
      const value = $select.val();
      const naming_id = $select.data("id");
      _haveVotesChanged = true;
      _haveVotesBeenSaved = false;

      // 处理"最爱"投票降级逻辑
      if (value === "3.0") {
        change_vote_selects().each(function () {
          const $otherSelect = $(this);
          if ($otherSelect.data("id") !== naming_id && $otherSelect.val() === "3.0") {
            $otherSelect.val("2.0");
          }
          $otherSelect.attr("disabled", "disabled");
        });
      } else {
        // 非最爱投票时,仅禁用当前表单的select
        change_vote_selects().not($select).removeAttr("disabled");
        $select.attr("disabled", "disabled");
      }

      // 显示加载模态框
      $('#naming_ajax_progress_caption').empty().append(
        $("<span>").text(translations.show_namings_saving + "... "),
        $("<span class='spinner-right mx-2'></span>")
      );
      $("#naming_ajax_progress").modal('show');

      // 用Rails.fire触发rails-ujs远程提交
      Rails.fire($select.closest('form')[0], 'submit');
    });

    // 绑定表单的AJAX完成事件,重置状态
    vote_forms().on("ajax:complete", function () {
      resetVoteState();
    });

    // 绑定表单AJAX失败事件,回滚投票值
    vote_forms().on("ajax:error", function () {
      const $form = $(this);
      const $select = $form.find("[data-role='change_vote']");
      // 回滚到之前的投票值
      $select.val($select.data("old_value"));
    });

    // 初始化保存初始投票值
    change_vote_selects().each(function () {
      const $select = $(this);
      $select.data("old_value", $select.val());
    });
  };

  // 页面离开提示逻辑
  window.onbeforeunload = function () {
    if (_haveVotesChanged && !_haveVotesBeenSaved) {
      return translations.show_namings_lose_changes;
    }
  };

  attachBindings();
});

关键调整说明

  • 使用Rails.fire(formElement, 'submit')替代直接form.submit(),确保rails-ujs的远程提交逻辑被触发,避免绕过事件钩子。
  • 新增ajax:complete事件处理,在请求完成后统一恢复表单状态、关闭模态框,无论请求成功或失败。
  • 新增ajax:error事件处理,在请求失败时回滚投票值到初始状态,提升用户体验。
  • 调整禁用逻辑:非"最爱"投票时仅禁用当前表单的select,而非全部,更符合交互逻辑。

内容的提问来源于stack exchange,提问作者nimmolo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 16:20:38