Stimulusの習作: インタラクティブな削除

チュートリアル

Stimulus.js Tutorial: Interactive Deletes with Rails UJS
こちらのチュートリアルを参考にコードを書いてみました。

このチュートリアルでは、リモート削除リンクを利用して、その場で確認メッセージを追加し、削除要求がサーバーに送信されたときに要素を非表示にしています。リクエストが成功して戻ってくると、要素はページから削除され、エラーが発生すると要素は元の場所に戻されます。

環境

macOS 10.15.4
Ruby 2.6.5
Rails 6.0.2.2
Yarn 1.22.4
Node 13.12.0
stimulus@1.1.1

リポジトリ

アプリの新規作成

stimulusを指定してRailsアプリを新規作成します。

rails new stimulus_tutorial_interactive_deletes_with_rails_ujs --webpack=stimulus
cd stimulus_tutorial_interactive_deletes_with_rails_ujs

stimulusが導入されたことを確認します。
※typoしていてもエラーが出ないので念のため。

cat app/javascript/controllers/index.js

ここまでの成果を保存。

git add .
git commit -m 'rails new stimulus_tutorial_interactive_deletes_with_rails_ujs --webpack=stimulus'

足場の生成

チュートリアルを参考に足場を作成します。
投稿(Post)モデルはタイトルと著者を持ちます。

bin/rails g scaffold post title author
bin/rails db:migrate

ここまでの成果を保存。

git add .
git commit -m 'bin/rails g scaffold post title author'

ダミーデータ生成

書籍のダミーデータを生成するためにGemfileにFakerを追加します。

gem 'faker'

Gemをインストールします。

bundle install

db/seeds.rbにダミーデータの生成処理を記述します。
書籍ではありませんがFaker::Bookを流用しました。

100.times do |i|
  Post.create!(title: Faker::Book.title,
               author: Faker::Book.author)
  print '.' if (i % 10).zero?
end

ダミーデータを生成します。

bin/rails db:seed

ここまでの成果を保存。

git add .
git commit -m "gem 'faker'"

ビューの修正

チュートリアルを参考にindex.html.erbを変更します。
要素の属性にdata-controller="delete"を追加します。

<h1 >All Posts</h1>
<%= link_to "New Post", new_post_path %>
<% @posts.each do |post| %>
 <div data-controller="delete">
   <p>
   <h2><%= link_to post.title, post %></h2>
   <strong><%= post.author %></strong>
   <br />
   <em><%= post.created_at.strftime("%l:%M %P") %></em>
   <br />
   <%= link_to "Delete", post_path(post), method: :delete, remote: true, data: { target: "delete.link", action: "ajax:beforeSend->delete#click" } %>
   </p>
 </div>
<% end %>

実際の表示内容が意図したものであることを確認しておきます。

bin/rails s
open http://localhost:3000/posts/

画像1

ここまでの成果を保存。

git add .
git commit -m 'data-controller="delete"'

コントローラーの作成

チュートリアルを参考にdelete_controller.jsを追加します。

const RESET_TIMEOUT_MILLIS = 3000;
const CONFIRMATION_MESSAGE = '<strong>Are you sure?</strong>';

import { Controller } from "stimulus"

export default class extends Controller {

  static targets = ['link']

  initialize() {
    this.handleSuccess = (event) => {
      clearTimeout(this.timeout)
      this.element.parentNode.removeChild(this.element)
    }
    this.handleError = (event) => {
      clearTimeout(this.timeout)
      this.resetState()
      this.element.style = ''
    }
  }

  connect() {
    this.delete = false
  }

  click(event) {
    if (this.delete) {
      this.element.style = 'display: none;'
      this.linkTarget.addEventListener('ajax:success', this.handleSuccess)
      this.linkTarget.addEventListener('ajax:error', this.handleError)
    } else {
      this.oldMessage = this.linkTarget.innerHTML
      this.linkTarget.innerHTML = CONFIRMATION_MESSAGE
      this.delete = true
      this.timeout = setTimeout(() => {
        this.resetState()
      }, RESET_TIMEOUT_MILLIS)
      event.preventDefault()
      return false
    }
  }

  resetState() {
    if (this.delete) {
      this.linkTarget.removeEventListener('ajax:success', this.handleSuccess)
      this.linkTarget.removeEventListener('ajax:error', this.handleError)
      this.linkTarget.innerHTML = this.oldMessage
      this.delete = false
    }
  }
}

意図通りに動作していることを確認できたら、ここまでの成果を保存。

git add .
git commit -m 'delete_controller.js'

お題に挑戦

Try to add something to alert the deleter on the page when the deletion fails. It can assume that failure isn’t a likely case, so the error message should be very disruptive to the page flow.

ということで、削除に失敗したときにアラートを表示してみます。

app/controllers/posts_controller.rb
必ず削除に失敗するようにして、

  def destroy
   respond_to do |format|
     if false # @post.destroy
       format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
       format.json { head :no_content }
     else
       format.html {
         @posts = Post.all
         render :index
       }
       format.json { head :unprocessable_entity }
     end
   end
 end

app/views/posts/index.html.erb
JSON側をたたくように変えてみました。

    <%= link_to "Delete", post_path(post, format: :json), method: :delete, remote: true, data: { target: "delete.link", action: "ajax:beforeSend->delete#click" } %>

app/javascript/controllers/delete_controller.js
アラート表示を追加しました。

    this.handleError = (event) => {
      clearTimeout(this.timeout)
      this.resetState()
      this.element.style = ''
      alert("Failed to delete.")
   }

意図通りに動作していることを確認できたら、ここまでの成果を保存。

git add .
git commit -m 'Add alert for failed deletion'

この記事が気に入ったらサポートをしてみませんか?