ページの一部を指定して変更できるTurbo Frames
ここまで、Driveの基本的な動作を見てきました。このように、Scaffoldingによって生成されたページやフォームに特に手を入れなくても、ページ遷移のない高速なアプリケーションを開発できます。また、上記の通りTurboを設定で無効にすれば、従来どおりのページ遷移を伴うアプリケーションに簡単に切り替えられます。
しかしながら、やはりページのほとんどの部分を書き換えるのは無駄だとか、SPAらしくページの一部だけを書き換えたいということはあります。このような場合には、次のステップとしてTurbo Frames(以降、Frames)の機能を使ってページに「フレーム」という領域を設けて、それを必要に応じて更新していきます。
ここ以降は再びTurboの機能を使っていきますので、app/javascript/application.jsのTurboを無効にする設定は、削除するかコメントアウトしておいてください。
[NOTE]Turbolinks
Turboは、Rails 4から導入されたTurbolinksというライブラリの後継版です。Turbolinksではfetchの対象がリンク(link_toメソッド)のみでしたが、Turboではフォーム(form_withメソッド)まで拡張されました。これにより、アプリケーションの多くの局面でTurboの恩恵を受けられるようになっています。
置き換え対象のフレームを作成する
Framesの機能を使って、一覧ページ(/words)の[New word]リンクを、新規作成フォームで置き換えてみます。リンクをクリックすると、その部分がそのままフォームになるので、引き続き作業できるようになるというわけです。フレームの作成は、app/views/words/index.html.erbをリストのように変更します。
…略… <%# [New word]リンクをフレームで囲む %> <%= turbo_frame_tag 'new_word' do %> <%= link_to "New word", new_word_path %> <% end %>
リンクであるlink_toメソッドを、turbo_frame_tagメソッドのブロックで囲んでいます。turbo_frame_tagメソッドは、turbo-railsライブラリによるTurboのためのヘルパーメソッドで、Framesのためのタグ(turbo-frameタグ)を生成してくれます。引数はturbo-frameタグのid属性の値になります。これで、link_toメソッド(リンク)はフレームで囲まれますので、この部分だけの更新が可能になります。
[NOTE]turbo_frame_tagメソッドのレンダリング
turbo_frame_tagメソッドはturbo-frameタグを生成しますが、具体的なレンダリングのイメージは以下の通りです。turbo_frame_tagメソッドの替わりに直接タグを記述してもよいですが、モデルオブジェクトによるid属性の自動的な生成などのメリットがなくなるので、できるだけヘルパーメソッドを使うようにしましょう。
<turbo-frame id="new_word"> <a href="/words/new">New word</a> </turbo-frame>
置き換わるフレームを作成する
リンクのクリックで、リンクを新規作成フォームで置き換えるので、新規作成フォームであるapp/views/words/new.html.erbをリストのように書き換えます。
<%# フォームとリンクをフレームで囲む %> <%= turbo_frame_tag @word do %> <%= render "form", word: @word %> <br> <div> <%= link_to "Back to words", words_path %> </div> <% end %>
ここでも、フォームと[Back to words]リンクをturbo_frame_tagメソッドで囲っています。こちらも、これでフレームとなります。turbo_frame_tagメソッドの引数はモデルオブジェクトを指定していますので、これによりid属性は一意的に生成されます。
Framesの動作
この時点でアプリケーションを実行して、/wordsから[New word]リンクをクリックすると、[New word]リンクが新規作成フォームに置き換わることが確認できます(図7)。確かめる必要はないですが、もちろんページ遷移は発生していません。
内部で何が行われているかを以下にまとめてみました(図8)。
- 一覧ページの[New word]リンクのクリックにより、/words/newへのfetchリクエストが発生する
- 新規作成ページであるnew.html.erbのレンダリング結果がレスポンスとして返る
- レスポンスにはturbo_frame_tagメソッドによりturbo-frameタグが含まれるので、この中身で呼び出し側のturbo-frameタグの中身を書き換える
このように、フレーム内部からのリクエストでは、フレームの内部をレスポンスのフレームの内容でそっくり置き換えます。このように、Framesではページの一部を極めて単純な指定だけで書き換えることができます。
[NOTE]FramesではURLは変更されない
Framesでは、あくまでもページの部分的な更新であるため、基本的にはURLは変更されません。Driveと同様にURLを変更したい場合には、data-turbo-action属性をフレームかリンクのどちらかに指定します。以下は、フレームに対してURLの変更を指定した例です。
…略… <%# フレームの更新でURLを変更する %> <%= turbo_frame_tag 'new_word', data: { turbo_action: :advance } do %> <%= link_to "New word", new_word_path %> <% end %>
なお、:advanceは新しいURLとしますが:replaceでは最新のURLを置き換えます。通常は:advanceで問題ないでしょう。
[NOTE]遅延ローディング(Lazy loading)
Driveでは、時間のかかる処理のためにプログレスバーやキャッシュの仕組みがありました。Framesでは、遅延ローディングの仕組みを使うことができます。遅延ローディングでは、読み込み中に表示する内容をturbo-frames要素のコンテンツとして配置し、src属性(turbo_frame_tagメソッドのsrcオプション)に読み込み先のURLを指定します。以下は、フォームの読み込み中にスピナーを表示しておく例です。
…略… <%# フレームの更新でURLを変更する %> <%= turbo_frame_tag 'new_word', src: new_word_path do %> <%= image_tag 'spinner.png' %> <% end %>
まとめ
今回は、アプリケーションにSPAライクな振る舞いを導入できるHotwrireについて、主にTurbo DriveとTurbo Framesの機能を紹介しました。次回は後半として、Turbo StreamsとStimulusの機能を紹介します。