繁体   English   中英

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

[英]Rails Turbolinks 5 : How do I run JS code on a particular page enter and exit?

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

传统上,我的页面使用一堆JavaScript来工作,其中包含库(jQuery,Knockout等)和特定于应用程序的代码,例如视图模型-所有这些都以某种有组织的结构附加到窗口对象。 应用程序区域中的所有控制器(例如前端)将共享相同的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:before-cache ,但是那又是什么呢? 诸如turbolinks:unload类的东西不存在。
  1. 鉴于我不得不提出自己的包装器功能,我怀疑Turbolinks的开发目的是为了阻止我使用的初始化流程。 真的吗?
  2. 如果是真的,那么惯用的方法是什么? 我应该如何设置基因剔除视图模型,或者运行与特定页面相关的任何代码?

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

您在设置和拆除方面处于正确的位置,但我想知道是否要考虑在turbolinks:load上实现用于安装的单个初始化函数,而不是您的turbolinks_inturbolinks_out函数?在turbolinks:before-cacheturbolinks: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)

因此, window.MyApp.init决定要初始化的对象,而不是直接window.MyApp.init联脚本中调用对象的init函数。

它如何决定?

似乎您正在镜像JS对象的控制器名称和动作名称。 这是一个很好的起点,我们只需要将这些名称添加到客户端即可。 一种常见的方法(我相信由Basecamp使用)是使用head meta元素来输出一些服务器生成的属性:

<meta name="controller_name" content="<%= controller_name %>">
<meta name="action_name" content="<%= action_name %>">

只要您的对象遵循控制器/动作命名模式,您的应用程序的initialize函数可能类似于:

;(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')
  }
})()

您可以按照以下方法来包括您的JavaScript配置:

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

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

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)
}

最后,我们需要拆除初始化结果。 为此,我们将存储对象的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')
  }
})()

显然,这种方法仅适用于每页一个init,因此您可能需要对其进行调整以使其适用于更多的init。 实现这一点超出了此答案的范围,但是您不妨考虑一下T3 ,它可以为您处理所有这一切!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM