Turbolinks 5にすると、JavaScriptまわりで正しく動かすためにいろいろ工夫が必要となりました。今回は、MagicSuggestというウィジェットを例に、対応するためにしたことをまとめていきます。
なお、自分が以前書いた、Turbolinks 5に関する記事もご覧いただくと、よりわかりやすいかもしれません。
Turbolinks 5の場合に、注意すべき点
「ページ表示のタイミングでのイベントが違う」という点は以前にも触れましたが、それ以外にも、「ブラウザバックした場合」の挙動がふつうの場合と大きく違っています。以下に、ブラウザバックする場合の概ねの流れを示してみます。
- ページを表示する(
turbolinks:load
などのイベントが流れる) - ページ遷移しようとする(Turbolinksでの処理が起きる)
- 現在のページで
turbolinks:before-cache
イベントが走る - 現在のページのDOMを保存する
- 移動した先のDOMを描画する
- (移動先のページ)
- ブラウザバックを押すと、Turbolinksに制御が移る
- 保存したDOMをロードして、
turbolinks:load
イベントを実行する - ロードしたDOMを描画して、戻る処理が完了
ここで注意点としては、DOMの保存は.cloneNode(true)
で行われるので、ネイティブ・jQueryを問わず、イベントはすべて削除されます。とはいえ、一般的な場合、
- DOMノード…保存して復活するので、問題なく動く
- JavaScriptイベント…
turbolinks:load
でセットすれば、新規表示時にも発動し、ブラウザバック時にも全削除→再セットとなって、問題なし
となるので、正常に動く…ように思えます。
JavaScriptからウィジェットを追加する場合
ここで問題になるのが、JavaScriptからDOMまで変えてしまうようなウィジェットを追加するような場合です。そのままにしていると、
- 新規ロード時…DOMを書き換えてウィジェットを生成、それにイベントをセット
- ブラウザバック時…「書き換え後のDOM+イベントなし」の状態からウィジェット生成コードが走って、うまく動かない場合がある
作戦としては2つあって、自分でDOM変更やイベントのセットを書いている場合には、「すでにDOMが変更されていればバイパスする」という方法があります。ただ、プラグインで行う場合はそうも行きません。
MagicSuggestとは
今回テーマとして取り上げたMagicSuggestは、複数選択可能なselect
をドロップダウン形式にしてしまう、というもので、設定次第ではQiitaのタグ欄のような「フリー入力+既存リストから選択」ということもできます。
そこそこ長さのあるリストから複数選択させたいときに便利だったのでよく使っていたのですが、Turbolinks 5になったことで対応が必要となりました。
動くようにはなりました
「MagicSuggestのイベントだけセット」という器用な芸当はできないので、「もともとあった<select>
要素をバックアップしておいて、キャッシュ記録時に書き戻す」という方法で対応を行うことにしました。なお、jQuery側に手当をして、ページ遷移ごとにready
が発動するようにしてあります。
//= require magicsuggest
jQuery(function() {
'use strict';
// ページバック時の書き戻し用に、本来のドロップダウンをバックアップ
var params = {
/* 略 */
};
$('.magic-suggest').each(function(){
var $elem = $(this);
var cloned = this.cloneNode(true);
$elem.magicSuggest(params);
$('#' + this.id).data('original_select', cloned);
});
$(document).one('turbolinks:before-cache', function(){
$('.ms-ctn').each(function(){
var $this = $(this);
$this.replaceWith($this.data('original_select'));
});
});
});
バックアップからの書き戻しは一度でいいので、.one
で単発イベントとしてあります。