Turbolinks不友好?

时间:2017-04-05 03:22:02

标签: ruby-on-rails turbolinks turbolinks-5

我完全明白为什么Turbolinks 5很棒,如果你正在阅读它,你可能也会这么做,但是我对它与块上其他脚本的播放效果非常沮丧。

到目前为止,没有简单的解释(人类可读),它说明了如何以允许它们运行的​​方式包装现有的jQuery脚本。 以此为例:https://github.com/Bttstrp/bootstrap-switch。它写得很好,易于理解。您将js和css加载到资产管道并在某个页面上实例化它。

# view.html.erb
<input type="checkbox" class="switch"> switch button
<script type="text/javascript">
    $(".switch").bootstrapSwitch();
</script>

你去view.html,点击另一个页面,点击后面,你会看到两个按钮。

接下来,你花了5个小时寻找一种让Turbolinks加载bootstrapSwitch实例的方法,如果之前没有加载的话。好吧,即使你这样做,功能也会消失。点击它将无效。

$(document).on("turbolinks:load", function()...会在每次Turbolink访问时加载它,而目前,我唯一可以使其工作而不是创建重复项的方法是使用

禁用view.html上的缓存
<%= content_for :head do %>
    <meta name="turbolinks-cache-control" content="no-cache">
<% end %>

哪种感觉有点愚蠢。

我认为这一切都与使用idempotent有关 - https://github.com/turbolinks/turbolinks#making-transformations-idempotent但你怎么做到这一点?

有人可以把这个简单的插件作为一个例子,并分享一个简单,优雅的解决方案,使其工作,然后我们可以用其他脚本重现吗?

1 个答案:

答案 0 :(得分:2)

使用Turbolinks开发应用程序确实需要特定的方法才能让事情顺利进行。由于页面加载和缓存的方式不同,运行脚本的某些模式在Turbolinks与不使用Turbolinks时的行为方式不同。这起初看起来似乎不友好,并且&#34;陷阱&#34;可能令人沮丧,但我发现只需一点点理解,它就会鼓励更有条理,更健壮的代码:)

正如您所知,重复切换的问题是插件在同一元素上被多次调用。这是因为Turbolinks在导航远离它之前缓存页面 ,因此缓存版本包括任何动态添加的HTML [1],例如通过插件添加的东西。向后/向前导航时,将恢复缓存版本,并且行为将重复:/

那么如何解决这个问题呢?使用添加HTML或事件侦听器的代码时,通常在缓存页面之前拆除行为是个好主意。 Turbolinks事件是turbolinks:before-cache。所以你的设置/拆解可能是:

// app/assets/javascripts/switches.js
$(document)
  .on('turbolinks:load', function () {
    $('.switch').bootstrapSwitch()
  })
  .on('turbolinks:before-cache', function () {
    $('.switch').bootstrapSwitch('destroy')
  })

这有点难以测试,因为所有设置和拆除都是在事件处理程序中完成的。更重要的是,可能会有更多这样的案例,所以为了防止重复,你可能想要引入自己的&#34;迷你框架&#34;用于设置和拆除功能。以下介绍创建基本框架。

以下是我们的目标:使用名称调用window.App.addFunction并使用函数注册要调用的函数。该函数获取元素并调用插件。它返回一个具有destroy功能的对象,用于拆卸:

// app/assets/javascripts/switches.js
window.App.addFunction('switches', function () {
  var $switches = $('.switch').bootstrapSwitch()
  return {
    destroy: function () {
      $switches.bootstrapSwitch('destroy')
    }
  }
})

以下实现addFunction,在functions属性中存储已添加的函数:

// app/assets/javascripts/application.js
// …
window.App = {
  functions: {},

  addFunction: function (name, fn) {
    this.functions[name] = fn
  }
}

我们将在应用初始化时调用每个函数,并将每个函数调用的结果存储在results数组中(如果存在):

// app/assets/javascripts/application.js
// …
var results = []

window.App = {
  // …
  init: function () {
    for (var name in this.functions) {
      var result = this.functions[name]()
      if (result) results.push(result)
    }
  }
}

拆除应用程序涉及销毁对任何结果的调用destroy(如果存在):

// app/assets/javascripts/application.js
// …
window.App = {
  // …
  destroy: function () {
    for (var i = 0; i < results.length; i++) {
      var result = results[i]
      if (typeof result.destroy === 'function') result.destroy()
    }
    results = []
  }
}

最后,我们初始化并拆除应用程序:

$(document)
  .on('turbolinks:load', function () {
    window.App.init.call(window.App)
  })
  .on('turbolinks:before-cache', window.App.destroy)

所以把这一切放在一起:

;(function () {
  var results = []

  window.App = {
    functions: {},

    addFunction: function (name, fn) {
      this.functions[name] = fn
    },

    init: function () {
      for (var name in this.functions) {
        var result = this.functions[name]()
        if (result) results.push(result)
      }
    },

    destroy: function () {
      for (var i = 0; i < results.length; i++) {
        var result = results[i]
        if (typeof result.destroy === 'function') result.destroy()
      }
      results = []
    }
  }

  $(document)
    .on('turbolinks:load', function () {
      window.App.init.call(window.App)
    })
    .on('turbolinks:before-cache', window.App.destroy)
})()

现在,函数独立于调用它们的事件处理程序。这种脱钩有几个好处。首先,它是更可测试的:window.App.functions中提供了功能。您还可以选择何时调用您的功能。例如,假设您决定不使用Turbolinks,那么您需要更改的唯一部分就是调用window.App.init时。

[1]我认为这比默认浏览器行为更好(按下&#34;返回&#34;将用户返回到首次加载时的页面)。 Turbolinks&#34; Back&#34;在用户离开时将用户返回到页面,这可能是用户期望的。