Bootstrap導入と適用

Ruby on Railsのバージョン:7.2.1
Rubyのバージョン:3.3.5

前回CRUD機能の実装行いました。それの続きでBootstrapを使用してデザインを行っていこうと思います。

## 1.Bootstrap導入
・CDNを利用する方法
・Yarnなどでパッケージをインストールする方法
上記２つがありますが、Yarnでインストールする方法はエラーがあり、進めることができなかったためCDNを利用する方法で進めていきます。

①HTMLにBootstrapのCDNを追加
app/views/layouts/application.html.erbのheadセクションに追加します。
```application.html.erb
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-..." crossorigin="anonymous">
<!-- Bootstrap JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-..." crossorigin="anonymous"></script>
```
上記のように記載し、HTMLにBootstrapのクラスを追加するだけでスタイルが適用されます。

## 2.デザインの適用
①レイアウト全体
```application.html.erb
<body>
~
~
    <div class="container my-4">　←追加
      <%= yield %>
    </div>
    <%= javascript_importmap_tags %>
</body>
```
全体のレイアウトを整え、デザインを統一するためにdiv class="container my-4"を追加することでページ全体の幅を適切に調整し、中央揃えのデザインを実現する。

②ハンバーガーメニューの実装
スマホやタブレットなど画面幅が狭いデバイスで、メニューを効率よく表示するためにハンバーガーメニューを導入します。
app/views/layouts/application.html.erbに追加
```
<nav class="navbar navbar-expand-lg navbar-light bg-light">
      <div class="container-fluid">
        <!-- アプリ名（リンク付き） -->
        <a class="navbar-brand" href="<%= root_path %>">Post App</a>
        <!-- ハンバーガーメニューのトグルボタン -->
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <!-- ナビゲーションメニュー -->
        <div class="collapse navbar-collapse" id="navbarNav">
          <ul class="navbar-nav ms-auto">
            <% if user_signed_in? %>
              <li class="nav-item">
                <%= link_to 'ホーム', posts_path, class: "nav-link" %>
              </li>
              <li class="nav-item">
                <%= link_to 'ログアウト', destroy_user_session_path, data: { turbo_method: :delete } , class: "btn btn-danger" %>
              </li>
            <% else %>
              <li class="nav-item">
                <%= link_to 'ログイン', new_user_session_path, class: "btn btn-primary me-2" %>
              </li>
              <li class="nav-item">
                <%= link_to 'サインアップ', new_user_registration_path, class: "btn btn-success" %>
              </li>
            <% end %>
          </ul>
        </div>
    </div>
</nav>
```
navbar-toggler: ハンバーガーメニューのトグルボタンを作成。
data-bs-toggle="collapse": 指定した要素を開閉可能にする。
data-bs-target="#navbarNav": 操作対象を指定。

アクセシビリティ関連属性（aria-controls, aria-expanded, aria-label）
・メニューの状態（開閉）や役割を補助技術（スクリーンリーダーなど）に伝える。

③index,new,show,edit,のビューファイルにbootstrapのデザインを適用させます。
```index.html.erb
<div class="container">
  <h1 class="my-4">投稿一覧</h1>.
  <% if @posts.present? %>
    <% @posts.each do |post| %>
      <div class="row mb-3">
        <div class="col-md-6">
          <div class="card">
            <div class="card-header">
              <%= link_to post.title, post_path(post), class: "text-decoration-none" %>
            </div>
            <div class="card-body">
              <p><%= post.content.truncate(100) %></p>
              <%# truncate(100)は投稿内容が100文字を超える場合、101文字目以降を省略し、... を末尾に追加します %>
            </div>
            <div class="card-footer">
              <%= form_with url: post_path(post), method: :delete, local: true do %>
                <%= submit_tag "削除", data: { confirm: "本当に削除しますか？" }, class: "btn btn-danger" %>
              <% end %>
            </div>
          </div>
        </div>
      </div>
    <% end %>
  <% else %>
    <p>まだ投稿がありません</p>
  <% end %>
  <div class="mb-5">
    <%= link_to '新規投稿', new_post_path, class: "btn btn-primary" %>
  </div>
</div>
```
```new.html.erb
<div class="container my-5">
  <div class="row justify-content-center align-items-center" style="min-height: 60vh;">
    <div class="col-md-8">
      <div class="card">
        <div class="card-header bg-primary text-white">
          <h1 class="h3">新規投稿</h1>
        </div>
        <div class="card-body">
          <%= form_with model: @post, local: true, data: { turbo: false } do |form| %>
            <%= render 'shared/error_messages', object: @post %>
            <%# エラーメッセージは基本的にフォームの一番上に配置 %>

            <div class="mb-3">
              <%= form.label :title, "タイトル", class: "form-label" %>
              <%= form.text_field :title, class: "form-control" %>
            </div>

            <div class="mb-3">
              <%= form.label :content, "内容", class: "form-label" %>
              <%= form.text_area :content, class: "form-control" %>
            </div>

            <div class="text-center">
              <%= form.submit "投稿する", class: "btn btn-primary" %>
            </div>
          <% end %>
        </div>
        <div class="card-footer text-center">
          <%= link_to '投稿一覧に戻る', posts_path, class: "btn btn-secondary" %>
        </div>
      </div>
    </div>
  </div>
</div>
```
```show.html.erb
<div class="row justify-content-center align-items-center" style="min-height: 80vh;">
  <div class="row justify-content-center">
    <div class="col-md-8">
      <div class="card mb-4">
        <div class="card-header bg-primary text-white">
          <h1 class="h3"><%= @post.title %></h1>
        </div>
        <div class="card-body">
          <p class="card-text"><%= @post.content %></p>
        </div>
        <div class="card-footer d-flex justify-content-between">
          <%= link_to "編集する", edit_post_path(@post), class: "btn btn-secondary me-2" %>
          <div>
            <%= form_with url: post_path(@post), method: :delete, local: true do %>
              <%= submit_tag "削除", data: { confirm: "本当に削除しますか？" }, class: "btn btn-danger" %>
              <%# <%= link_to "削除", post_path(@post), method: :delete, data: { confirm: "本当に削除しますか？", turbo: false } %> 
              <%# 上記で記載すると反応しない、Deleteメソッドが送られて欲しいところにGETメソッドが送られている。%>
              <%# @rails/ujsに問題ありみたい、一旦上記で削除できるボタンが実装できたので進める%>
            <% end %>
          </div>
        </div>
      </div>
      <%= link_to "投稿一覧に戻る", posts_path, class: "btn btn-primary" %>
    </div>
  </div>
</div>
```
```edit.html.erb
<div class="container my-5">
  <div class="row justify-content-center align-items-center" style="min-height: 60vh;">
    <div class="col-md-8">
      <div class="card">
        <div class="card-header bg-primary text-white">
          <h1 class="h3">投稿を編集</h1>
        </div>
        <div class="card-body">
          <%= form_with model: @post, local: true, data: { turbo: false } do |form| %>
            <%= render 'shared/error_messages', object: @post %>
            
            <div class="mb-3">
              <%= form.label :title, "タイトル", class: "form-label" %>
              <%= form.text_field :title, class: "form-control" %>
            </div>
            
            <div class="mb-3">
              <%= form.label :content, "内容", class: "form-label" %>
              <%= form.text_area :content, class: "form-control" %>
            </div>
            
            <div class="d-flex justify-content-between">
              <%= form.submit "更新する", class: "btn btn-primary" %>
            </div>
          <% end %>

          <div>
            <%= form_with url: post_path(@post), method: :delete, local: true do %>
              <%= submit_tag "削除", data: { confirm: "本当に削除しますか？" }, class: "my-1 btn btn-danger" %>
            <% end %>
          </div>
        </div>
      </div>
      <div class="mt-3">
          <%= link_to '投稿一覧に戻る', posts_path, class: "btn btn-secondary" %>
      </div>
    </div>
  </div>
</div>
```
index
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3931580/976aeb14-64c7-e74e-c502-a5ec24cb8e63.png)

new
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3931580/e6a7da51-7d87-237e-b36c-648efb288bb4.png)

show
![スクリーンショット 2025-01-05 20.43.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3931580/f850534d-1b62-a69c-b706-f55fdc323a94.png)

edit
![スクリーンショット 2025-01-05 20.45.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3931580/43ca0dde-15dc-5b2d-f227-ad6d041c4775.png)

こんな感じですね
**Bootstrapには膨大な数のクラスが用意されており、クラスを設定するとそのクラスに応じたデザインやフォームなどが反映される仕組みになっています。**

どのようなクラスを設定すればいいかなどの判断は正直初学者には難しいのではないかと思う。（実際私もできる気がしないです。）
なので私はチャットGPTにどのようなデザインにすればいいか聞きながらやってます。

生成AIと対話しながらデザインを実装していけばどのクラスがどういうデザインになるのか、どのようなフォームになるのか、などのことを確認しながら進めることができます。

デザインが気に食わなかったり、余白が欲しければそれを生成AIに指示して別のクラスでの実装をするようにしていけばいいと思います。
数こなしていけば、いつの間にかある程度のデザインはできるようになっているのではないのでしょうか。（希望的観測）
ただAIに出してもらったコードを写すだけだと何の意味もないのでしっかりコードの意味を理解してください！

Qiitaに下記のような神記事があったのでこれも参考にしながらデザインしていくといいと思う。
あと、Bootstrapの公式サイトは全部英語なので私にとっては難しかったです。

https://qiita.com/nakanishi03/items/c80a16b9b9796c25f890