Rails Turbolinks 5:如何在特定页面上运行JS代码进入和退出?

时间:2017-05-25 14:58:45

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

我正在玩Turbolinks 5和Rails,试图弄清楚它是如何改变游戏的。

传统上,我的网页通过一组包含库(jQuery,Knockout等)和特定于应用程序的代码的javascript工作。视图模型 - 全部附加到某个有组织结构中的窗口对象。应用程序区域中的所有控制器,例如前端将共享相同的JavaScript(和CSS)包。

在每个使用JavaScript的页面上,都会有一个初始化程序来启动。例如:

$.ready(function() { window.users.update.init(#{@user_edit_view_model.javascript_configuration.to_json.html_safe}) } )

此init函数的职责是设置一个挖空视图模型并将其绑定到某个节点。 javascript_configuration可能包含当前用户的初始化状态。

无论如何,这种方法似乎根本不适用于Turbolinks。

我知道只有当用户访问users#edit作为第一页(或进行硬刷新)时才会触发,因此$.ready显然不在图片之外。

如果我附加到turbolinks:load事件,则上述代码不仅在用户输入users#edit时触发,而且还在用户随后导航到的任何页面上触发(直到他/她完全刷新为止)在某些时候)。

我已经能够通过定义一个在第一次执行后删除回调的函数来解决这个问题。

# For running code after the current page is ready
window.turbolinks_in = (callback) ->
  event_listener = ->
    callback()
    window.removeEventListener("turbolinks:load", event_listener)

  window.addEventListener "turbolinks:load", event_listener

此外,我还设计了:

# For running code when leaving the current page
window.turbolinks_out = (callback) ->
  event_listener = ->
    callback()
    window.removeEventListener("turbolinks:before-cache", event_listener)

  window.addEventListener "turbolinks:before-cache", event_listener

这两个函数似乎允许我在页面加载和“卸载”时运行代码。

我的问题是:

  1. 看到我必须提出自己的包装函数,我怀疑Turbolinks是故意开发的,以阻止我使用的初始化流程。这是真的吗?
  2. 如果这是真的,那么惯用的方法是什么?我应该如何设置淘汰视图模型或者运行任何与特定页面相关的代码?
  3. 如果不是这样,我该如何可靠地为页面设置卸载/离开功能。当缓存页面被新页面替换时,不会发出turbolinks:before-cache,那么在简要显示缓存页面后如何清理?我确实理解在替换(已经)缓存页面时不应该触发turbolinks:before-cache在语义上是有意义的,那么它的位置是什么呢?像turbolinks:unload这样的东西不存在。

1 个答案:

答案 0 :(得分:2)

  
      
  1. 看到我必须提出自己的包装函数,我怀疑Turbolinks是故意开发的,以阻止我使用的初始化流程。这是真的吗?
  2.   
  3. 如果这是真的,那么惯用的方法是什么?我应该如何设置淘汰视图模型或者运行任何与特定页面相关的代码?
  4.   

就在特定网页上运行代码而言,首先,我通常会避免为该网页添加内联script。这通常适用于非Turbolinks应用程序,但在启用Turbolinks的应用程序中,状态和事件处理程序在页面加载之间保持不变,事情可能会变得有些混乱。理想情况下,您应该将所有JS包含在一个文件中。 (如果您想知道如何注入服务器生成的内容,我会稍后介绍一下。)

您在设置和拆除方面处于正确的界限,但我想知道您是否考虑实施“turbolinks_inturbolinks_out功能”而不是turbolinks:loadturbolinks:before-cache功能。单个初始化函数用于turbolinks:before-render上的设置,随后是document.addEventListener('turbolinks:load', window.MyApp.init) document.addEventListener('turbolinks:before-cache', window.MyApp.destroy) document.addEventListener('turbolinks:before-render', window.MyApp.destroy) init上的单个拆解功能(这是"卸载"事件之后)。您的设置/拆解代码可能如下所示:

window.MyApp.init

因此,meta不是直接在内联脚本中调用对象的head函数,而是决定要初始化哪些对象。

如何决定?!

您似乎正在镜像JS对象的控制器名称和操作名称。这是一个很好的起点,我们只需要将这些名称放到客户端。一种常见的方法(我相信Basecamp使用)是使用<meta name="controller_name" content="<%= controller_name %>"> <meta name="action_name" content="<%= action_name %>"> 中的;(function () { window.MyApp = { init: function () { var controller = window[getControllerName()] var action if (controller) action = controller[getActionName()] if (action && typeof action.init === 'function') action.init() } } function getControllerName () { return getMeta('controller_name') } function getActionName () { return getMeta('action_name') } function getMeta (name) { var meta = document.querySelector('[name=' + name + ']') if (meta) return meta.getAttribute('content') } })() 元素来输出一些服务器生成的属性:

<meta name="javascript_config" content="<%= @view_model.javascript_configuration.to_json %>">

只要您的对象遵循控制器/操作命名模式,您的应用程序的初始化功能可能类似于:

window.MyApp = {
  init: function () {
    var controller = window[getControllerName()]
    var action
    var config = getConfig()
    if (controller) action = controller[getActionName()]
    if (action && typeof action.init === 'function') action.init(config)
  }
}

// …

function getConfig () {
  return JSON.parse(getMeta('javascript_config') || null)
}

您可以按照此方法添加JavaScript配置:

init

然后你的应用程序的初始化函数可以使用配置调用相关的初始化程序,如下所示:

destroy

最后,我们需要拆除初始化的结果。为此,我们会存储对象;(function () { var result window.MyApp = { init: function () { var controller = window[getControllerName()] var action var config = getConfig() if (controller) action = controller[getActionName()] if (action && typeof action.init === 'function') { result = action.init(config) } }, // … destroy: function () { if (result && typeof result.destroy === 'function') result.destroy() result = undefined } } })() 的返回值,如果它包含;(function () { var result window.MyApp = { init: function () { var controller = window[getControllerName()] var action var config = getConfig() if (controller) action = controller[getActionName()] if (action && typeof action.init === 'function') { result = action.init(config) } }, destroy: function () { if (result && typeof result.destroy === 'function') result.destroy() result = undefined } } function getControllerName () { return getMeta('controller_name') } function getActionName () { return getMeta('action_name') } function getConfig () { return JSON.parse(getMeta('javascript_config') || null) } function getMeta (name) { var meta = document.querySelector('[name=' + name + ']') if (meta) return meta.getAttribute('content') } })() 函数,我们会调用它:

SELECT DATE_FORMAT(DATE_TIME_FIELD, '%Y-%m-%d %H') AS DATE_TIME_HOUR
FROM sensor
GROUP BY DATE_FORMAT(DATE_TIME_FIELD, '%Y-%m-%d %H')
HAVING AVG(T)>4

把它们放在一起:

var devicePixelRatio = window.devicePixelRatio || 1;
var renderer = new THREE.WebGLRenderer({antialias: devicePixelRatio === 1});

显然,这种方法只适用于每页一个init,因此您可能希望对其进行调整以使其更多。实现这一点超出了这个答案的范围,但您可能希望查看T3,它会为您处理所有这些!