Turbolinks недружелюбно?
Я полностью понимаю, почему Turbolinks 5 потрясающий, и если вы читаете его, вы, вероятно, тоже, но я очень разочарован тем, насколько плохо он играет с другими скриптами в блоке.
На сегодняшний день не существует простого объяснения (удобочитаемого для человека), которое показывает, как обернуть существующие сценарии jQuery таким образом, чтобы они могли функционировать. Возьмем, к примеру, этот: https://github.com/Bttstrp/bootstrap-switch. Это хорошо написано, просто для понимания. Вы загружаете js и css в свой конвейер ресурсов и создаете его экземпляр на какой-то странице.
# view.html.erb
<input type="checkbox" class="switch"> switch button
<script type="text/javascript">
$(".switch").bootstrapSwitch();
</script>
Вы идете в view.html, нажмите на другую страницу, нажмите назад, и вы увидите две кнопки.
Затем вы потратите 5 часов на поиск способа заставить Turbolinks загружать экземпляр bootstrapSwitch только один раз, если он не был загружен ранее. Ну, даже если вы это сделаете, функциональность исчезнет. Нажав на это не получится.
$(document).on("turbolinks:load", function()...
будет загружать его при каждом посещении Turbolink, и пока единственным способом, которым я мог заставить его работать, а не создавать дубликаты, было отключение кэша в view.html с помощью
<%= content_for :head do %>
<meta name="turbolinks-cache-control" content="no-cache">
<% end %>
Который чувствует себя немного глупо.
Я думаю, что все это как-то связано с использованием идемпотента - https://github.com/turbolinks/turbolinks, но как вы это практически делаете?
Может ли кто-нибудь взять этот простой плагин в качестве примера и поделиться простым, элегантным решением для его работы, которое мы затем можем воспроизвести с помощью других сценариев?
1 ответ
Разработка приложений с помощью Turbolinks требует особого подхода для обеспечения бесперебойной работы. Из-за различий в способах загрузки и кэширования страниц некоторые шаблоны запуска скриптов не будут вести себя одинаково с Turbolinks и без. Поначалу это может показаться недружелюбным, и "ошибки" могут быть разочаровывающими, но я обнаружил, что при небольшом понимании это поощряет более организованный и надежный код:)
Как вы уже поняли, проблема с дублирующимися переключателями заключается в том, что плагин вызывается более одного раза для одного и того же элемента. Это связано с тем, что Turbolinks кэширует страницу непосредственно перед ее удалением, и поэтому кэшированная версия включает в себя любой динамически добавляемый HTML[1], например, материал, добавляемый через плагины. При перемещении назад / вперед кэшированная версия восстанавливается, и поведение дублируется:/
Так как это исправить? При работе с кодом, который добавляет HTML или прослушиватели событий, обычно рекомендуется отключить поведение до кэширования страницы. Событие Turbolinks для этого turbolinks:before-cache
, Таким образом, ваши настройки / демонтаж может быть:
// app/assets/javascripts/switches.js
$(document)
.on('turbolinks:load', function () {
$('.switch').bootstrapSwitch()
})
.on('turbolinks:before-cache', function () {
$('.switch').bootstrapSwitch('destroy')
})
Это немного сложно проверить, так как все настройки и разборки выполняются в обработчиках событий. Более того, может быть, таких случаев гораздо больше, поэтому, чтобы предотвратить повторение, вы можете захотеть представить свою собственную "мини-платформу" для настройки и разрушения функциональности. Далее описывается создание базовой структуры.
Вот к чему мы стремимся: звонить window.App.addFunction
с именем и функцией регистрирует функцию для вызова. Эта функция получает элементы и вызывает плагин. Возвращает объект с destroy
функция для разрыва:
// app/assets/javascripts/switches.js
window.App.addFunction('switches', function () {
var $switches = $('.switch').bootstrapSwitch()
return {
destroy: function () {
$switches.bootstrapSwitch('destroy')
}
}
})
Следующие инструменты addFunction
, сохраняя добавленные функции в functions
имущество:
// app/assets/javascripts/application.js
// …
window.App = {
functions: {},
addFunction: function (name, fn) {
this.functions[name] = fn
}
}
Мы будем вызывать каждую функцию при инициализации приложения и сохранять результат каждого вызова функции в results
массив, если он существует:
// 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
(если он существует) по любым результатам:
// 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 = []
}
}
Наконец, мы инициализируем и разрываем приложение:
$(document)
.on('turbolinks:load', function () {
window.App.init.call(window.App)
})
.on('turbolinks:before-cache', window.App.destroy)
Итак, чтобы собрать все это вместе:
;(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
называется.
[1] Я думаю, что это лучше, чем поведение браузера по умолчанию (когда нажатие кнопки "Назад" возвращает пользователя обратно на страницу, как это было при первой загрузке). Turbolinks "Назад" возвращает пользователя обратно на страницу, когда он покинул ее, что, вероятно, и ожидает пользователь.