为了使我们网站中的所有内容都可以翻译(包括验证的错误消息),我们将几乎所有的表单都切换到了远程表单。虽然这有助于翻译错误消息,但我们遇到了其他问题,例如:
- 如果用户多次单击提交按钮,则该操作会被多次调用。如果我们有一个用于在数据库中创建新记录的远程表单,并假设用户的数据是有效的,那么每次点击都会添加一个新对象(内容完全相同)。有什么方法可以确保这样的事情不会发生吗?
有什么地方可以读到有关远程表单最佳实践的信息吗?我该如何处理多次点击问题?将所有表格切换到远程表格是一个很大的错误吗?
为了使我们网站中的所有内容都可以翻译(包括验证的错误消息),我们将几乎所有的表单都切换到了远程表单。虽然这有助于翻译错误消息,但我们遇到了其他问题,例如:
有什么地方可以读到有关远程表单最佳实践的信息吗?我该如何处理多次点击问题?将所有表格切换到远程表格是一个很大的错误吗?
有一个名为 .rails 3 的选项:disable_with
。将其放在输入元素上以在提交远程表单时禁用并重新标记它们。它为这些输入添加了一个 data-disable-with 标签,并且 rails.js 可以选择和绑定此功能。
submit_tag "Complete sale", :disable_with => "Please wait..."
更多信息可以在这里找到
很容易,您可以根据自己的喜好以多种方式实现这一目标:
只需使用 ajax 请求手动发布表单,并在等待响应时禁用/隐藏(或任何您需要的)表单,以确保用户不会继续发帖疯狂。一旦您从服务器获得响应,您就可以再次允许用户再次发布(首先清理表单),或显示其他内容或将其重定向到另一个页面或再次您需要的任何内容。
使用 link_to :remote=>true 提交表单并添加回调函数来处理响应,并在提交时禁用/隐藏(或任何您需要的)表单
向表单添加一个 js 侦听器以检测何时提交,然后禁用/隐藏/无论表单
如您所见,有很多不同的方法可以实现您所需要的。
编辑:如果您需要有关绑定或处理从 js 提交的表单的信息,您会发现非常简单有趣的示例,可以帮助您执行我建议的操作!jQuery 提交
我自己有很多远程表单,在大多数情况下我会避免使用它们。但有时您的布局或 UX 需要动态下拉表单,而无需重新加载或刷新整个页面。
所以,让我逐步解决这个问题。
即使使用普通表单,用户也可以双击您的按钮,或者单击多次,如果用户没有得到明确的指示,表明单击已注册并且操作已开始。
有很多方法(例如 javascript)可以使其可见,但在 rails 中最简单的是:
= f.button :submit, :disable_with => "Please wait..."
这将在第一次单击后禁用按钮,清楚地表明单击已注册并且操作已开始。
对于远程形式,它并没有太大的不同,但最有可能的区别是:之后会发生什么?
使用远程表单,您有几个选项:
让我处理这些案件。请理解,这三种情况在做正常形式时是完全标准的。但不是在进行远程呼叫时。
2.1 出错时要使远程表单正确更新,您必须做更多的事情。不是很多,而是一点点。
使用 haml 时,您将有一个名为的视图edit.js.haml
,它看起来像
:plain
$('#your-form-id').replaceWith('#{j render(:partial => '_form') }');
这是做什么的:只用表单替换完整的haml。您将必须相应地构建您的视图,以使其工作。这并不难,但只是必要的。
2.2 清除表格您有两个选择: * 完全重新呈现表单,与错误一样。只需确保您从新元素呈现表单,而不是刚刚发布的!* 只需发送以下 javascript:
$('#your-form-id').reset();
这将使表单空白,通常情况下,这将有效地使任何后续点击无效(某些客户端验证可能会阻止发布,直到某些字段被填写)。
2.3 重定向由于您使用的是远程表单,因此您不能只是重定向。这必须发生在客户端,所以这有点复杂。
再次使用haml,这将类似于
:plain
document.location.href = '#{@redirect_uri}';
为了防止使用远程表单的双重(三重、四重、更多)帖子,您必须
:disable_with
)希望这可以帮助。
You can try something like that for ajax requests.
Set block variable true for ajax requests
before_filter :xhr_blocker
def xhr_blocker
if request.xhr?
if session[:xhr_blocker]
respond_to do |format|
format.json, status: :unprocessable_entity
end
else
session[:xhr_blocker] = true
end
end
end
Clear xhr_blocker variable with an after filter method
after_filter :clear_xhr_blocker
def clear_xhr_blocker
session[:xhr_blocker] = nil
end
我在鼠标悬停时使用弹出窗口也遇到过类似的问题,并且不想将多个请求排队。为了获得更多控制权,您可能会发现直接使用 javascript/coffeescript 而不是 UJS(就像我一样)更容易。
我解决它的方法是将 Ajax 调用分配给一个变量并检查该变量是否已分配。在我的情况下,我会中止 ajax 调用,但是一旦 ajax 调用成功完成,您可能希望从函数返回并将变量设置为 null。
这个咖啡脚本示例来自我使用“GET”的弹出窗口,但理论上它应该与“POST”或“PUT”相同。
例如
jQuery ->
ajaxCall = null
$("#popupContent").html " "
$("#popup").live "mouseover", ->
if ajaxCall
return
ajaxCall = $.ajax(
type: "GET"
url: "/whatever_url"
beforeSend: ->
$("#popupContent").prepend "<p class=\"loading-text\">Loading..please wait...</p>"
success: (data) ->
$("#popupContent").empty().append(data)
complete: ->
$"(.loading-text").remove()
ajaxCall = null
)
为了简洁起见,我省略了鼠标悬停和计时器处理。
我不想这么说,但听起来你想出了比疾病更糟糕的治疗方法。
为什么不使用 i18n 进行翻译?那肯定是'Rails方式'......
如果你必须继续沿着这条路线走,你将不得不开始使用 Javascript。远程表单通常用于投票或评论等小型“AJAXy 事物”。在不离开页面的情况下创建整个对象对于人们可能想要连续创建大量对象(您要解决的确切问题)很有用。
一旦你开始使用 AJAX,你就必须处理你必须开始做一些 JS 的事实。这是客户端的东西,因此不是 Rail 的专长。
如果您觉得您在这条路上走得太远以至于无法回头,我建议 AJAX 响应至少应该重置表单。然后,这将阻止人们多次错误地创建相同的东西。
从 UI/UX 的角度来看,它还应该显示一条消息,让用户知道他们成功创建了对象。
总而言之 - 如果你有时间,git reset 并开始使用 i18n,如果你不能,让 ajax 回调重置表单并设置一个 flash 消息。
编辑:我突然想到你甚至可以让 AJAX 为你重定向页面(但你必须自己处理 Flash 消息)。但是,使用然后通过 javascript 重定向的远程表单是 FUGLY...
最简单的解决方案是为每个表单生成一个令牌。然后您的create
操作可以确保它尚未被使用并确定是否应该创建记录。
以下是我将如何编写此功能。请注意,我实际上并没有对此进行测试,但这个概念应该有效。
1. 在new
动作内部创建一个哈希来识别表单请求。
def new
@product = Product.new
@form_token = session["form_token"] = SecureRandom.hex(15)
end
2. 在表单中添加一个隐藏字段来存储表单令牌。这将在create
操作中被捕获,以确保之前没有提交过表单。
<%= hidden_field_tag :form_token, @form_token %>
3. 在create
操作中,您可以确保表单标记在session
和params
变量之间匹配。这将使您有机会查看这是第一次提交还是第二次提交。
def create
# delete the form token if it matches
if session[:form_token] == params[:form_token]
session[:form_token] = nil
else
# if it doesn't match then check if a record was created recently
product = Product.where('created_at > ?', 3.minutes.ago).where(title: params[:product][:title]).last
# if the product exists then show it
# or just return because it is a remote form
redirect_to product and return if product.present?
end
# normal create action here ...
end
更新:我上面描述的有一个名字,它被称为同步器(或似曾相识)令牌。如本文所述,是防止重复提交的正确方法。
该策略解决了重复表单提交的问题。同步器令牌在用户会话中设置,并包含在返回给客户端的每个表单中。提交该表单时,会将表单中的同步器令牌与会话中的同步器令牌进行比较。令牌应在第一次提交表单时匹配。如果令牌不匹配,则可能不允许提交表单并将错误返回给用户。当用户提交表单,然后单击浏览器中的“后退”按钮并尝试重新提交相同的表单时,可能会发生令牌不匹配。
另一方面,如果两个令牌值匹配,那么我们确信控制流完全符合预期。至此,会话中的token值被修改为新值,接受表单提交。
我会绑定到 ajax:complete,(或 ajax:success 和 ajax:error)来重定向或更新 DOM,以便在请求完成时根据需要删除/更改表单。