新手问题 关于 Turbolinks 5 和 jQuery 的一些疑惑

renyuanz · March 07, 2016 · Last by renyuanz replied at March 08, 2016 · 4259 hits

最近在做一个新项目,于是尝试更新了 Turbolinks 5,说实话,坑的数量和之前一样多,而且坑的位置还不同了(因为 API 重写了),再加上我一直对 Turbolinks 似懂非懂的,几度让我抓狂到怀疑人生。

我来描述一下我遇到的问题:

一些故事背景

  • 我使用了一个购买的 bootstrap 主题,里面含有一些基本的 JS,比如折叠菜单按钮,我使用了压缩之后的版本
  • 一些 jQuery 的插件,geocomplete 和 dataTables
  • Turbolinks 5 + jQuery.turbolinks

Application.coffee 的内容如下

#= require jquery
#= require jquery_ujs
#= require jquery.turbolinks

#= require theme.min
#= require plugins/jquery.dataTables.min
#= require plugins/jquery.geocomplete
#= require cocoon
#= require data-tables
#= require maps

#= require turbolinks
#= 顺序应该是没问题的,我参考了ruby-china的源码和jQuery.turbolinks的文档

值得一提的是(可能是):theme.min.js 中已经打包进了 jQuery,我不确定在这里引用两次会不会有问题。

然后,比如说折叠菜单、dataTables 的初始化都是这样做的:

jQuery(function() {
  App.init(); // App 来自于 theme.min.js
});

jQuery(function() {
  BaseTableDatatables.init();
});

// 这些函数都是在各自的JS文件的结尾处

我遇到的问题是:

这些 JS 绑定的事件都只在页面读取的那一刻有用,当点击某个链接后 Turbolinks.visit() 或使用浏览器 Back / Forward Button 后,这些事件均不再运作。

尝试过和想到的解决方案:

  • javascript_include_tag 放到 body 标签结束之前,但是这个“土方法”似乎在 Turbolinks 5 行不通了,而且这也是官方不建议做的方法。
  • 把 jQuery.turbolinks 去掉,重新把这些事件绑定在 $(document).on("turbolinks:load", function(){ ... }) 上,但是很显然,有一些功能就被重复绑定了。
  • 应该有一个专门负责处理这些会被重复绑定事件的 API,比如 $(document).on("turbolinks:before-cache", function(){ ... })参考这里,但是说实话我没有理解怎么“解绑”事件。

说实话,JS 真的是一门怎么写都不爽的语言啊……但是现在的应用越来越依赖 JS 了,所以我还是想把这里面的门道弄清,希望各位前辈不吝赐教!

对于第一点,这样弄肯定是不行的,turbolinks 就是替换 body 里的内容,你把 js 放到 body,相对于没有 turbolinks 了; 对于第二点,没明白你重复绑定是什么意思,很多没有jquery.turbolinks的项目都是类似这样来做的: $(document).on("ready page:load", function(){ ... })

就这里的情况而言,看起来是jquery.turbolinks没生效,好好看看。

问题应该出在你购买的 theme 上面,关键看App.inti()后面绑定的写法。

如果写法类似$('.theme-specific-class').on('click', function(){...}), 那么你用在 Turbolink 上面就会有问题,因为on只会运行一次,把特定的 elements 加入到监听到 array 中。然后你转换页面后这些监听就会丢失,不再生效。我想通常这些 theme 的作者都会这么写,一方面是监听具体元素本身效率比较高,另一方面 Turbolink 应用也不是广泛的场景。

如果写法类似$(document).on('click', '.theme-specific-class', function(){...}),那么 Turbolink 就不会有问题。

假设是以上写法一,解决方法有两个,都涉及查找或修改库文件。

  1. 除了App.init(), 负责任的库应该还提供停止/解绑的 API, 比如App.stop/destroy/, 至少负责把 init 绑到全局的东西解除。如果有这个 API, 那么你可以在 Turbolink 转换前停止,转换后重新 init。如果没有你可以考虑自己增加。

  2. 把监听改到 document/body 上面

相比之下,选项 1 可能会容易和干净一点,但具体要看库是怎么写的。

@qinfanpeng 感谢回复 😄 但是应该是排除了 jquery.turbolinks 没生效的可能性,在加上它和去掉它还是有区别的。

@billy 感谢回复 😄 ,正如你所说,所有绑定都是写法一,而且没有提供解绑的 API。我昨天看了好多社区管理员 side project 里面的做法,对“解绑事件”有了一些认识。

也就是说我要做类似:

$(document).off('click', '.theme-specific-class', function(){})
$(document).on('click', '.theme-specific-class', function(){...})

这样的事情吗?

看起来我是不是该换一个 theme 了…

#3 楼 @renyuanz 和 theme 没关系,turbolink 5 是不刷新页面的,所以$(document).ready(function(){});里面的方法只会在首次加载页面的时候调用一次,所以切换页面后针对新的 DOM 应该生效的 js,必须写在$(document).on('turbolinks:load', function(){});里面,官网的文档其实都写了。。

问题解决了,谢谢楼上各位给的建议 😄

我的做法是统一在一个文件中(或者直接在 application.coffee 里面)绑定所有事件:

$(document).on
  'turbolinks:load': ->
    # 此处绑定全局事件
    App.init()

$(document).on
  'turbolinks:before-cache': ->
    # 此处解绑原先会出现多次的事件,比如dataTables
    $('[selector-dataTables]').dataTable().fnDestroy()

没有用 jquery.turbolinks ,目前感觉良好。

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