新手问题 为何使用了 Turbolinks 以后,jquery_ujs 里的 Event 被重复 bind 了多次?

adamshen · January 10, 2016 · Last by qinfanpeng replied at January 11, 2016 · 4013 hits

是这样,我写了一个简单的 form,使用 remote: true 改成了用 ajax 进行提交。提交以后查看日志,发现 Rails 收到了数次重复的 ajax 提交。

接着我打开 Firebug 查看提交页面,发现与提交有关的 click 事件被重复绑定了四次。虽然 Turbolinks 不会重新引入 jquery_ujs,但是每次切换页面,似乎 jquery_ujs 都会重新被执行一遍。

要如何才能避免这种现象?

这是含有 form 的 new.html.erb 的代码

<div class="container">
  <h2 class="col-md-offset-5">新增电脑</h2>

  <div class="col-md-offset-3">
    <%= form_for @computer, url: computers_path, remote: true,
                 html: {class: "form-horizontal"} do |f| %>
        <%= render partial: "form", locals: {f: f} %>
        <div class="form-group">
          <div class="col-md-offset-3">
            <%= f.submit("创建", class: "btn btn-default") %>
          </div>
        </div>
    <% end %>
  </div>
</div>

layout 代码

<!DOCTYPE html>
<html>
<head>
  <title>Manage</title>
  <nav class="navbar navbar-default navbar-fixed-top">
      <%= top_nav class: "nav navbar-nav" do |li|
        li << link_to("首页", root_path)
        li << link_to("通讯录", contacts_path)
        li << link_to("考勤记录", attends_path)
        li << link_to("固定资产", assets_path(default_company))
        li << link_to("个人借款", loans_path(default_company))
        li << link_to("人事系统", employees_path)
        li << link_to("电脑管理", computers_path)
        li << link_to("项目清单", projects_path)
      end %>

    <ul class="nav navbar-nav navbar-right">
      <% if user_signed_in? %>
          <li><%= link_to "退出登录", logout_path %></a></li>
      <% else %>
          <li><%= link_to "登录系统", new_user_session_path %></a></li>
      <% end %>
    </ul>
  </nav>
  <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

这是 application.js 里的

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree .
//= require bootstrap-sprockets

除了上面这些以外,没有写任何其他的前端代码。

看看你的代码里是否这样的代码 $(document).on "page:load", ->,其中的page:load每次因 Turbolinks引起的切换页面都会执行里面的逻辑。我猜想你需要的是这样的代码 $(document).on "ready", ->,这个只在第一次加载后 dom ready 才执行,后面因 Turbolinks引起的切换页面都不会再执行了。

#1 楼 @qinfanpeng 恩,感谢解答。我再补充和追问一下,这里我并没有自己写任何 js 代码,只是利用 remote: true 来完成 ajax 提交。这是没有使用 Turbolinks 之前所有的 click 事件,都是 jquery_ujs 里绑定的,很正常。

下面是使用了 Turbolinks 之后的 click 事件,每个相同的事件都被重复绑定了三次,导致我在网页上点下提交,会触发三次 ajax 提交。

我不解的是为何相同的事件会重复绑定三次,在我未切换页面前,是正常的,但一切换页面,似乎 click 事件就会被多绑定一次。

贴代码。

#3 楼 @rei 好的,已更新。

app/asserts/javascripts 目录还有什么文件?

将 application.js 删到剩下:

//= require jquery
//= require jquery_ujs
//= require turbolinks

再试试。

#5 楼 @rei 试了下,还是不行。

app/asserts/javascripts目录下剩下的是每个controller对应的coffee脚本,都是空白的,没有在其中增加代码。

我怀疑是某个 Gem 和 turbolinks 冲突,于是 new 了一个新的 rails demo,只加载 rails 基本的 gem。然后在 layout 里只写一个导航栏,发现也依然有这个问题。

看了看 Trubolinks 的这段切页代码

changePage = (title, body, csrfToken, runScripts) ->
  triggerEvent EVENTS.BEFORE_UNLOAD
  document.title = title
  document.documentElement.replaceChild body, document.body
  CSRFToken.update csrfToken if csrfToken?
  setAutofocusElement()
  executeScriptTags() if runScripts
  currentState = window.history.state
  progressBar?.done()
  triggerEvent EVENTS.CHANGE
  triggerEvent EVENTS.UPDATE

executeScriptTags = ->
  scripts = Array::slice.call document.body.querySelectorAll 'script:not([data-turbolinks-eval="false"])'
  for script in scripts when script.type in ['', 'text/javascript']
    copy = document.createElement 'script'
    copy.setAttribute attr.name, attr.value for attr in script.attributes
    copy.async = false unless script.hasAttribute 'async'
    copy.appendChild document.createTextNode script.innerHTML
    { parentNode, nextSibling } = script
    parentNode.removeChild script
    parentNode.insertBefore copy, nextSibling
  return

根据我的理解,Turbolinks 不会重新加载 head 里已载入过的 js 脚本,只会再运行一遍 body 里 js 语句。但是我打开 firebug 发现,每次我切换页面,浏览器就重新从本地的 bfcache 里 get 了一次 head 里的 js 脚本。另外我又看了 jquery_ujs 的代码

if ( $.rails !== undefined ) {
  $.error('jquery-ujs has already been loaded!');
}

jquery_ujs 里的既然有防止重复载入的语句,那么为何里面的事件还是会被重复 bind?是否和浏览器的 cache 机制有关?

把 head 里面那段 nav 删掉看看,我怀疑是不合语法浏览器自动修正把 script tag 归入 body 了。

果然

<html>
  <head>
    <nav>nav</nav>
    <script></script>
  </head>
  <body>
  </body>
</html>

浏览器修正

可显示元素要放 body 里。

#8 楼 @rei 厉害,他这个问题我看了半天没眉目,只是感觉把 nav 标签放到 head 里很奇怪,没想到会是这样。还是你们这些前辈有经验。:plus1:

#8 楼 @rei 哇。哈哈哈哈。移到 body 里,真的正常了。你太帅了!

感谢大神相助,前端好多我的知识盲点,看来要恶补才行。

#9 楼 @qinfanpeng 你也很有经验哒,只是我这样的乌龙太少人会犯了。 😪

You need to Sign in before reply, if you don't have an account, please Sign up first.