有一个需求,需要为 Rails 上所有的异步请求添加加载动画(其中 Rails: 5.1.4,jQuery: 3.3.1 )。具体设计是发出请求后在页面展示正在请求的提示动画,请求结束时隐藏或清除该动画。下面是分情况的实现:

禁用 rails-ujs 的情况

如果没有使用 rails-ujs

// application.js
// 确定项目中下面这行已经被移除
//= require rails-ujs

那么实现只要添加 JS 事件就行了

$(document).on("ajaxSend", function(){
  // 加载异步请求动画
  $("#loading").show();
})
.on("ajaxComplete", function(){
  // 移除异步请求动画
  $("#loading").hide();
})

其中 #loading 是布局层面的 html 加载动画模板,每个页面都需要加载,初始化时先隐藏起来。

使用 rails-ujs 的情况(仅使用于非动态元素)

如果是按默认的 Rails 配置,项目中需要使用 rails-ujs 时,ujs 提供的异步请求(a 或 button 添加 remote: true 这种)绑定的事件不是原生事件,这时候需要绑定另外一种事件实现

$(document).on("ajax:send", function(){
  // 加载异步请求动画
  $("#loading").show();
})
.on("ajax:complete", function(){
  // 移除异步请求动画
  $("#loading").hide();
})

这么处理在一般的情况下没有问题。但是当绑定 send 事件的元素在后端处理中被移除了的时候,后续的事件就无效了。举个例子:

  • tests/index.html.erb
<%= link_to "我会异步删除自己", tests_path, remote: true, method: "delete", id: "test" %>
  • tests/destroy.js.erb
$("#test").remove();

然后点击测试中的链接之后,加载异步请求动画成功,但是请求结束后,动画不会移除。查询资料,说这种异步请求未完成就改变元素的操作不是好实践。但是这种需求在 Rails 还是很普遍啊,例如大家用的 kaminari 分页库的异步加载,就会重新渲染整个列表,也就是触发换页操作的按钮也被移除过了,所以也会出现上述的问题。于是这个问题还是要解决的。

使用 rails-ujs 的情况(完整)

具体要解决 Rails 异步请求加载动画的问题,需要使用到 ajaxSend 事件的 jqXHR 参数,用这个参数值来绑定后续事件,而不再使用 ajaxComplete 事件。

首先,因为当前的 Rails 版本自带的 rails-ujs 不再依赖 jQuery,因此 ajax:send 事件的参数中,不是 jqXHR,所以首先需要替换回原来的 jquery_ujs 库

  • Gemfile
gem 'jquery-rails'
  • application.js
//= require rails-ujs 替换为
//= require jquery_ujs

然后用以下的方式绑定事件

$(function() {
  $(document).on("ajax:send", function(event, jqXHR) {
    // 加载异步请求动画
    $("#loading").show();
    jqXHR.always(function() {
      // 移除异步请求动画
      $("#loading").hide();
    });
  });
});

还有一个多请求干扰的问题,如:异步删除两个资源,A 的删除动作触发了动画显示,随后 A 的任务完成后动画消失,但 B 如果还在进行中的话,动画实际上已经消失了。

因此还要添加一个全局计数器

$(function() {
  var ajaxSendCount = 0;
  $(document).on('ajax:send', function(event, jqXHR) {
    if (!$("#loading").is(":visible")) {
      ajaxSendCount = 0;
      // 加载异步请求动画
      $("#loading").show();
    }
    ajaxSendCount++;
    jqXHR.always(function() {
      ajaxSendCount--;
      if (count <= 0) {
        // 清除异步请求动画
        $("#loading").hide();
      }
    });
  });
});

现在可以正常的加载异步请求的动画了。