Turbolinks 不友好? [英] Turbolinks unfriendly?

查看:37
本文介绍了Turbolinks 不友好?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我完全明白为什么 Turbolinks 5 很棒,如果你正在阅读它,你可能也读过它,但我对它与块上其他脚本的糟糕表现感到非常沮丧.

I totally get why Turbolinks 5 is awesome and if you're reading it, you probably do as well, but I am very frustrated with how badly it plays with the other scripts on the block.

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

To date, there is no simple explanation (human readable) that shows how to wrap existing jQuery scripts in a way that would allow them to function. Take for example this one: https://github.com/Bttstrp/bootstrap-switch. It's well written, simple to understand. You load the js and css to your assets pipeline and instantiate it on some page.

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

您转到 view.html,单击另一个页面,然后单击返回,您会看到两个按钮.

you go to view.html, click another page, click back and you see two buttons.

接下来,您花了 5 个小时寻找一种方法,让 Turbolinks 在之前未加载的情况下只加载一次 bootstrapSwitch 实例.好吧,即使你这样做了,功能也会消失.点击它不起作用.

Next, you spend 5 hours looking for a way to have Turbolinks load the instance of bootstrapSwitch only once if not loaded before. Well, even if you do, the functionality will be gone. Clicking it will not work.

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

$(document).on("turbolinks:load", function()... will load it on every Turbolink visit, and for now, the only way I could make it work and not create duplicates was to disable cache on view.html with

<%= content_for :head do %>
    <meta name="turbolinks-cache-control" content="no-cache">
<% end %>

感觉有点傻.

我认为这一切都与使用幂等有关 - https://github.com/turbolinks/turbolinks#making-transformations-idempotent 但你实际上是如何做到这一点的?

I think it all has something to do with using idempotent - https://github.com/turbolinks/turbolinks#making-transformations-idempotent but how do you practically do this?

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

Could someone please take this simple plugin as an example and share a simple, elegant solution for making it work which we can then reproduce with other scripts?

推荐答案

使用 Turbolinks 开发应用程序确实需要特定的方法才能使事情顺利运行.由于页面加载和缓存方式的差异,某些运行脚本的模式在使用 Turbolinks 时与不使用 Turbolinks 时的行为方式不同.起初这可能看起来不友好,陷阱"可能令人沮丧,但我发现只要稍微了解,它就会鼓励更有条理、更健壮的代码:)

Developing apps with Turbolinks does require a particular approach in order to get things running smoothly. Due to differences the way pages are loaded and cached, some patterns of running scripts won't behave in the same way with Turbolinks vs. without. This may seem unfriendly at first, and the "gotchas" can be frustrating, but I've found that with a little understanding, it encourages more organised, robust code :)

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

As you have figured out, the problem with duplicate switches is that the plugin is being called more than once on the same element. This is because Turbolinks caches a page just before navigating away from it, and so the cached version includes any dynamically added HTML[1] e.g. stuff added via plugins. When navigating back/forward, the cached version is restored, and the behaviour is duplicated :/

那么如何解决这个问题呢?在处理添加 HTML 或事件侦听器的代码时,通常最好在缓存页面之前拆除行为.Turbolinks 事件是 turbolinks:before-cache.因此,您的设置/拆卸可能是:

So how to fix this? When working with code which adds HTML or event listeners, it is generally a good idea to teardown behaviours before the page is cached. The Turbolinks event for that is turbolinks:before-cache. So your setup/teardown might be:

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

这有点难以测试,因为所有的设置和拆卸都是在事件处理程序中完成的.更重要的是,这样的情况可能还有很多,所以为了防止重复,您可能需要引入自己的迷你框架"来设置和拆除功能.下面将介绍如何创建一个基本框架.

This is a bit difficult to test since all the setup and teardown is done in event handlers. What's more, there maybe many more cases like this, so to prevent repitition, you may want to introduce your own "mini-framework" for setting up and tearing down functionality. The following walks through creating a basic framework.

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

Here's is what we'll aim for: calling window.App.addFunction with a name and a function registers a function to call. That function gets the elements and calls the plugin. It returns an object with a destroy function for teardown:

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

下面实现了addFunction,将添加的函数存储在functions属性中:

The following implements addFunction, storing added functions in the functions property:

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

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

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

We'll call each function when the app initializes, and store the result of each function call in the results array if it exists:

// 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(如果存在):

Tearing down the app involves destroying calling destroy (if it exists) on any results:

// 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 = []
  }
}

最后我们初始化和拆卸应用程序:

Finally we initialise and teardown the app:

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

所以把这一切放在一起:

So to put this all together:

;(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 时.

Functions are now independent of the event handler that calls them. This decoupling has a couple of benefits. First it's more testable: functions are available in window.App.functions. You can also choose when to call your functions. For example, say you decide not to use Turbolinks, the only part you'd need to change would be when window.App.init is called.

[1] 我认为这比默认的浏览器行为更好(按返回"将用户返回到第一次加载时的页面).Turbolinks返回"将用户返回到他们离开的页面,这可能是用户所期望的.

[1] I think this is better than the default browser behaviour (where pressing "Back" returns the user back to the page as it was when it was first loaded). A Turbolinks "Back" returns the user back to the page as they left it, which is probably what a user expects.

这篇关于Turbolinks 不友好?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆