【Bootstrap5.3】キャンセル機能をモーダルで

# はじめに
ポートフォリオで予約機能を作成中、キャンセル期限内だったらユーザ側でキャンセル処理が行える設定を付けたくなりました。

制作も終盤に近付いており、新しくビューを作るのが面倒だな…と感じ、せっかくだからBootstrapに用意されているモーダルウィンドウを使ってキャンセル機能を実装してみることにしました。

![2024-06-21キャンセルはこちら.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3762011/3a8c8b8b-b4bc-f8bd-05a7-2dbfa90515a2.png)

# 実装内容
今回はモーダルウィンドウを追加することがメインなので、キャンセル機能については軽く触れるだけにしておきます。

## コントローラ
一般ユーザ側のコントローラで、updateアクションとしてキャンセル機能を追加しました。

```ruby:controllers/public/pre_orders_controller.rb
  def update
    pre_order = PreOrder.find(params[:id])
    pre_order.update(status: 'cancel')
    redirect_to pre_order_path(pre_order)
  end
```
他にpre_orders_controllerで更新する内容もなかったのでupdateアクションを使用しています。

この場合、cancelアクションなどを作った方が分かりやすいだろうか…という点も少し迷いましたが、デフォルトのアクションが空いているからそのまま使おうと思い、updateにしました。

## ビュー
モーダル画面は部分テンプレートで作成しました。
予約詳細から「キャンセルはこちら」のテキストをクリックするとモーダル画面が開くようになっています。
```html:public/pre_orders/show.html.erb
:
:
<% if @pre_order.status == 'before_visit' && (@pre_order.visit_day - @pre_order.item.deadline.to_i.days) > Date.today %>
  <span data-bs-toggle="modal" data-bs-target="#exampleModal">
    キャンセルはこちら
  </span>
<% end %>
:
:
<%= render 'cancel_check', pre_order: @pre_order %>
```
:::note info
<b>data-bs-toggle="modal"</b>
Bootstrapのデータ属性で、この要素がモーダルウィンドウをトリガーすることを示します。
この属性を設定すると、クリックイベントなどの特定のイベントが発生したときにモーダルが表示されるようになります。
:::

:::note info
<b>data-bs-target="#exampleModal"</b>
トリガーされるモーダルウィンドウのIDです。
この属性により、クリックイベントが発生したときに、指定されたIDのモーダルウィンドウが表示されます。
※Bootstrapのドキュメントをそのまま使ったのでexampleModalになっています。
:::

ちなみに、「キャンセルはこちら」のリンクは、
@&#8203;pre_order.status（予約状況）がbefore_visit（来店済み）で尚且つ、
@&#8203;pre_order.visit_day（来店日時）から@pre_order.item.deadline.to_i.days（キャンセル可能期の日数）を引いた日付が今日より前の日付だったら表示させる設定になっています。


```html:public/pre_orders/_cancel_check.html.erb（モーダルウィンドウ）
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h3 class="modal-title fs-5 fw-bold" id="exampleModalLabel">再度ご確認ください</h3>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        一度キャンセルすると、同じ商品でも再度予約する必要があります。<br>
        キャンセルする内容に間違いはありませんか？
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-info" data-bs-dismiss="modal">予約詳細に戻る</button>
        <%= link_to '予約をキャンセルする', pre_order_path(pre_order), 'data-confirm' => '本当にキャンセルしますか？', method: :patch, class: "btn btn-outline-info" %>
      </div>
    </div>
  </div>
</div>
```

:::note info
<b>class="modal fade"について…</b>
<b>modal</b>
モーダルウィンドウの基本的なスタイルと機能を提供するclass。

<b>fade</b>
フェードイン（徐々に表示される）とフェードアウト（徐々に非表示になる）のアニメーションを適用するclass。
:::


:::note info
<b>tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"について…</b>
<b>tabindex="-1"</b>
ユーザーがすぐにモーダルウィンドウ内の内容を操作できるように、最初にフォーカスを設定。

<b>aria-labelledby="exampleModalLabel"</b>
モーダルウィンドウが何のためのものかを説明するためのラベル。
 id="exampleModalLabel"でモーダルウィンドウのタイトルを識別しています。
※BootstrapドキュメントではデフォルトがexampleModalLabelになっています。

<b>aria-hidden="true"</b>
スクリーンリーダー（音声で読み上げなど）が読み上げないようにするため使用。
:::

:::note info
<b>classについて…</b>
<b>modal-dialog</b>
モーダルウィンドウ全体の外枠を定義し、モーダルウィンドウのサイズや配置を設定します。

<b>modal-content</b>
モーダルウィンドウの中身全体を囲む要素で、モーダルの外枠の中に配置され、モーダルの内容を包含します。

<b>modal-header</b>
モーダルウィンドウの上部、タイトルや閉じるボタンを含む部分です。

<b>modal-header</b>
モーダルウィンドウの上部、タイトルや閉じるボタンを含む部分です。

<b>modal-title</b>
モーダルウィンドウのタイトルを定義します。

<b>modal-body</b>
モーダルウィンドウの中央部、メインコンテンツ部分です。

<b>modal-footer</b>
モーダルウィンドウの下部、ボタンなどの操作部分を含む部分です。
※私はここに「予約をキャンセルするボタン」を設置しています。
:::


:::note info
<b>data-bs-dismiss="modal"</b>
モーダルウィンドウを閉じるためのclassです。
これを指定した要素がクリックされると、自動的にモーダルウィンドウが閉じられます。
※私はここを「予約詳細に戻るボタン」にしています。
:::

予約機能自体は、単純に、
<%= link_to '予約をキャンセルする', pre_order_path(pre_order),, method: :patch %>
でupdateアクションに飛ばしました。


# おわりに
そもそもBootstrapを使わないモーダルウィンドウを作成したことがなかったので、仕組みを理解する前に完成してしまった感がありました。

今度は、Bootstrapを使わないとどんな記述になるのか、確認しながら取り入れてみようと思います。