Bootstrap 本机不适用于 Turbo Links

Bootstrap Native does not work with Turbo Links

我正在尝试在 Rails 5 应用程序中获取 Bootstrap Native to work with Turbolinks 5。当我第一次加载页面时,Bootstrap 下拉菜单工作正常,但导航到另一个页面后,Bootstrap 下拉菜单不再工作。就好像 Bootstrap 的事件监听器断开了连接。

我已经看到几个关于 Bootstrap 的 jQuery 实现的问题,但是,我对使用 Bootstrap Native 和消除 jQuery 感兴趣来自我的 JS 堆栈。

这里有一些细节:

application.js

# app/assets/javascript/application.js
//= require turbolinks
//= require rails-ujs
//= require polyfill
//= require bootstrap-native

应用布局

# views/layout/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
    </div>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload', 'data-turbolinks-eval': 'false' %>
  </body>
</html>

bootstrap-原生 这是 Bootstrap Native package.

从我的 Javascript 标签中删除 'data-turbolinks-eval': 'false',从而在每个 Turbolink 导航上重新评估应用程序的所有 Javascript,确实解决了 Bootstrap 本机问题,但它却导致 rails-ujs 抛出异常。

有什么解决办法吗?

使用 Turbolinks 时,您很可能希望将应用程序 JavaScript 文件放在 <head>:

# views/layout/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

虽然这似乎违背了传统的最佳实践,但 Turbolinks 的性能优势在于脚本仅在初始页面加载时加载。

如果 bootstrap 本机库在 DOMContentLoaded 上初始化其 plugins/functions,您可能还需要在 turbolinks:load 上手动调用这些函数,然后将它们拆除(如有必要,通常在 turbolinks:before-cache)。

主要修订

在 Bootstrap 本机开发团队的一名成员接受了答案之后,我修改了我在我的应用程序中实施的解决方案。我的解决方案与公认的解决方案略有不同,因为我的侦听器将在整个 DOM 中搜索 Bootstrap 组件。相比之下,公认的解决方案只会在具有 id="myContainer 的 HTML 标签内搜索。接受的解决方案将执行得更快,因为它只搜索 DOM 的一个子集。但是,它需要开发人员将相关的 Bootstrap 组件包装在带有 myContainer id 的标签中。

两种解决方案都有效。我的解决方案会 运行 稍微慢一点,但会导致编码更容易,并且不太容易出现开发人员引起的错误。详情如下:

app/assets/javascript/application.js

//= require turbolinks
//= require rails-ujs
//= require polyfill
//= require bootstrap-native
//= require bootstrap-native-turbolinks

app/assets/javascript/bootstrap-native-turbolinks.js

document.addEventListener('turbolinks:load', function(){
  Array.prototype.forEach.call(document.querySelectorAll('[data-spy="affix"]'), function(element){ new Affix(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-dismiss="alert"]'), function(element){ new Alert(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="buttons"]'), function(element){ new Button(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-ride="carousel"]'), function(element){ new Carousel(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="collapse"]'), function(element){ new Collapse(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="dropdown"]'), function(element){ new Dropdown(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="modal"]'), function(element){ new Modal(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="popover"]'), function(element){ new Popover(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-spy="scroll"]'), function(element){ new ScrollSpy(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="tab"]'), function(element){ new Tab(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="tooltip"]'), function(element){ new Tooltip(element) });
},false);

vendor/assets/javascripts/

最后,我将我的应用程序 javascript 标记移动到 <head> 并在没有 tuborlinks-eval 和 turbolinks-track 的情况下异步加载它。这会在初始页面加载时将 Javascript 配置为 运行。 turbolinks:load 侦听器在每次 turbolinks 页面访问时调用,并将 Bootstrap 本机事件侦听器附加到 DOM.

中的适当组件

app/views/layouts/application.html.erb

...
<head>
  ...
  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload', 'data-turbolinks-eval': 'false', async: true %>
</head>
...

截至撰写本文时,此解决方案已用于生产 Rails 5.1 应用程序。

顺便说一下,application.js 中列出的所有 Javascript,在连接和缩小后,小于 20KB。相比之下,jQuery by itself is 86KB minified. Bootstrap Native 可以显着缩短应用的下载时间。

BSN 开发者在想为什么不在 BSN 库之外添加这个自定义代码,以便于维护?

var container = document.getElementById('myContainer');
document.addEventListener('turbolinks:load', function(){
  container = container || document.getElementById('myContainer');
  Array.prototype.forEach.call(container.querySelectorAll('[data-spy="affix"]'), function(element){ new Affix(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-dismiss="alert"]'), function(element){ new Alert(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="buttons"]'), function(element){ new Button(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-ride="carousel"]'), function(element){ new Carousel(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="collapse"]'), function(element){ new Collapse(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="dropdown"]'), function(element){ new Dropdown(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="modal"]'), function(element){ new Modal(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="popover"]'), function(element){ new Popover(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-spy="scroll"]'), function(element){ new ScrollSpy(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="tab"]'), function(element){ new Tab(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="tooltip"]'), function(element){ new Tooltip(element) });
},false);

您应该只查看特定的容器 turbolinks 更新,因为您不会更新任何其他内容,并且您需要它尽可能快。

我还更新了 wiki page 以包含一个通用示例。

更新 从 BSN 版本 2.0.20 开始,您可以在您的站点 <head> 中使用该库,而无需任何额外的脚本,并且您可以 turbolinks 更轻松地完成此操作:

var container = document.getElementById('myContainer');
document.addEventListener('turbolinks:load', function(){
  container = container || document.getElementById('myContainer');
  BSN.initCallback(container);
}, false);

如果您在控制台中输入 BSN 按 Enter,您将获得以下对象:

BSN = {
  version: '2.0.20',
  initCallback: function(lookup){},
  supports: [ /* an array with supported components */ ]
}

请记住,您不必使用 myContainer 作为 turbolinks 目标的 ID 属性,您可以使用任何东西使该元素唯一,我建议使用 data-function="turbolinks-autoload" 这样的选择器.

下载最新的母版并试一试。

var bsn = require('bootstrap.native/dist/bootstrap-native-v4');

document.addEventListener('turbolinks:load', () => {
    BSN.initCallback();
});