0

我正在使用 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}) } )

这个初始化函数的职责是建立一个淘汰视图模型并将其绑定到某个节点。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不存在的东西。
4

1 回答 1

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

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

您在设置和拆卸方面处于正确的位置,但我想知道您是否考虑为 setup on 实现一个初始化函数,而不是您的turbolinks_inand函数,然后在 on和 on 上实现一个单独的拆卸函数(这是您所追求的“卸载”事件)。您的设置/拆卸代码可能如下所示:turbolinks_outturbolinks:loadturbolinks: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)

因此,与其直接在内init联脚本中调用对象的函数,不如window.MyApp.init决定初始化哪些对象。

它是如何决定的?!

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

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

您可以按照这种方法来包含您的 JavaScript 配置:

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

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

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,因此您可能希望对其进行调整以使用更多。实现这一点超出了这个答案的范围,但您可能希望查看T3,它会为您处理所有这些!

于 2017-05-29T13:30:08.443 回答