Railsアプリケーションを書くときに、turbolinksやstimulusなどを使い、なるべくサーバサイドはHTMLを返してjsをあまり書かずに楽をする、という方針でいくとします。このように割り切るとだいぶ楽なのですが、例えば次のようなケースで悩みます。
- コメントを最新10件表示する
- コメントが11件以上ある場合は「もっと読む」ボタンが表示される
- 「もっと読む」を押したときにajax経由でHTML片を取得し、新しくコメントを10件追加表示する
- 最後のコメントにたどり着いたときに「もっと読む」ボタンを消す
一番最後の「もっと読む」ボタンを消すやり方が悩みどころです。単にHTMLを受け取りコメントの末尾に差し込むやり方では対応できません。だからといってレスポンスをJSONで返し、そこからDOMを生成するのは面倒なのでやりたくない。
いくつかやり方を考えてみました。
- DOM構造を変更し、「もっと読む」ボタンを上書きできるような形でコメントを追加できるようにする
- サーバからはHTMLを含んだjsを返し、クライアント側ではそれを実行するようにする。js内でHTMLの挿入と「もっと読む」ボタンの削除を両方できるように書く
- (stimulusを使っていれば)「最後のコメントまで来た」というdata-targetを追加で挿入しておき、HTMLを挿入したあとにtargetをチェックする
- レスポンスヘッダに「これが最後のコメントである」という情報を含める
3か4のどちらかがよさそうです。1はDOM構造がページングの仕様に引きずられてしまうのがよくないでしょうし、2はstimulusを使っている場合はjsの処理が散らばるので可読性が下がりそう(stimulusを使っていなければこれが楽でよさそうです)。3は悪くないですが、これも若干DOM構造がページングの仕様に引きずられる感じがありますね。直感的ではない。
stimulusを使っているので、最終的に4で実装しました。こんな感じ。
fetch(Routes.api_comments_path({ page: nextPage }), { credentials: 'include' }) .then(response => { if (response.headers.has('X-Comments-Last-Page')) { this.pagingButtonTarget.style.display = 'none' } return response.text() }) .then(html => { this.commentListTarget.insertAdjacentHTML('beforeend', html) this.data.set('page', nextPage) })