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
相关产品推荐
相关产品推荐

