🍰

チーム開発 機能追加

2023/05/30に公開2

はじめに

プログラミング学習歴2ヶ月目の初学者です!
他のチームでいいなと思った機能を自分のとこの完成物にも取り入れてみました😆

商品新規登録画面でジャンル登録もできるようにする🌱

View

新しいジャンルを作成するためのフォームは、デフォルトでは非表示(style="display: none;")になっています。

app/views/admin/items/new.html.erb
  <!-- ジャンル新規登録欄 -->
  <div class="col-4 mx-auto form-group rounded" id="new-genre-form"
  style="display: none; position: absolute; top: 29%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(0, 0, 0, 0.6); padding: 15px; z-index: 9999;">
    <table class="table table-borderless mb-0">
      <tr>
        <td>
          <%= form_with model: @genre, url: admin_genres_path do |f| %>
            <div class="d-flex mt-3">
              <%= f.text_field :name, placeholder: "新規ジャンル名", class: "form-control me-3" %>
              <%= f.submit "ジャンル作成", class:"btn font-bold", style: "background-color: #E5E5CB; color: #1A120B;" %>
            </div>
              <p class="text-white mt-3"> ※作成をやめる場合、以下のジャンルをお選びください</p>
          <% end %>
        </td>
      </tr>
    </table>
  </div>

(f.select :genre_id)部分で、プルダウンリストから既存のジャンルを選ぶか、新しいジャンルを作成する選択肢を提供しています。

app/views/admin/items/new.html.erb
  <!-- ジャンル選択欄 -->
    <%= form_with model: @item, url:admin_items_path, method: :post do |f|  %>
        <tr>
          <td>ジャンル</td>
          <td>
+          <%= f.select :genre_id, options_for_select([["新しくジャンルを作成する", +"new"]].concat(@genres.map { |genre| [genre.name, genre.id] })),
+            { include_blank: "-- 選択してください --" }, {required: true, class: +"form-control", id: "genre-select" } %>
          </td>
        </tr>

JavaScript部分では、ジャンルのプルダウンリストの選択が変更された時に発火するイベントリスナーを設定しています。もしプルダウンリストで「新しくジャンルを作成する」("new")が選ばれた場合、非表示になっている新しいジャンル作成フォームが表示されます。それ以外のジャンルが選ばれた場合、フォームは非表示になります。

app/views/admin/items/new.html.erb
<!--「新しくジャンルを作成する」の選択時に発火-->
<script>
 # HTMLドキュメントが読み込まれた後に実行される関数
  $(document).ready(function() {
    var genreSelect = $("#genre-select");
    var newGenreForm = $("#new-genre-form");

 # ジャンル選択の内容がチェンジされたときに実行する処理
    genreSelect.on("change", function() {
      if (genreSelect.val() === "new") {
        slideDown(newGenreForm);
      } else {
        slideUp(newGenreForm);
      }
    });

  # 引数で指定された要素をスライドダウンさせる処理
    function slideDown(element) {
      element.fadeIn(300);
    }
    
  # 引数で指定された要素をスライドアップさせる処理
    function slideUp(element) {
      element.fadeOut(300);
    }
  });
</script>
🌱解説

$(document).ready(function() { ... });
これはjQueryの文法で、全てのHTMLドキュメントが読み込まれてからスクリプトが実行されるようにするためのものです。

var genreSelect = $("#genre-select");
ジャンルを選択するHTML要素(セレクトボックス)をJavaScriptの変数 genreSelect に代入しています。$("#genre-select") はid属性が "genre-select" の要素を選択します。

var newGenreForm = $("#new-genre-form");
新しいジャンルを作成するフォームのHTML要素をJavaScriptの変数 newGenreForm に代入しています。

genreSelect.on("change", function() { ... });
ジャンルの選択が変わった(セレクトボックスで新しい項目が選ばれた)ときに、その後の処理を行います。

if (genreSelect.val() === "new") { ... }
選択されたジャンルが "new"(新しいジャンルを作成する)の場合、その後の処理を行います。

slideDown(newGenreForm);
新しいジャンルの入力フォームを表示します。slideDownは次に説明します。

slideUp(newGenreForm);
新しいジャンルの入力フォームを非表示にします。slideUpは次に説明します。

function slideDown(element) { ... }
この関数は指定されたHTML要素(ここでは newGenreForm)を滑らかに表示します。element.fadeIn(300); の部分で、300ミリ秒(0.3秒)かけて要素をフェードイン(表示)します。

function slideUp(element) { ... }
この関数は指定されたHTML要素(ここでは newGenreForm)を滑らかに非表示にします。element.fadeOut(300); の部分で、300ミリ秒(0.3秒)かけて要素をフェードアウト(非表示)します。

Controller

このメソッドは新しいジャンルを作成し、それが成功すればユーザを適切な場所にリダイレクトします。成功しなかった場合は、ユーザにエラーを通知してジャンル一覧画面を表示します。

app/controllers/admin/genres_controller.rb
  def create
    @genre = Genre.new(genre_params)
    if @genre.save
+      if request.referer == new_admin_item_url
+        flash[:notice] = "ジャンルの作成に成功しました"
+        redirect_to request.referer
+      else
+        redirect_to admin_genres_path
+      end
    else
      @genres = Genre.all
      render :index
    end
  end
items_controllerはこんな感じ!🌱
app/controllers/admin/items_controller.rb
class Admin::ItemsController < ApplicationController
  def index
    @items = Item.page(params[:page])
  end

  def new
    @item = Item.new
    @genres = Genre.all
    @genre = Genre.new
  end
  
  def create
    @item = Item.new(item_params)
    if @item.save
      redirect_to admin_item_path(@item.id),notice: "商品の登録が完了しました"
    else
      @genres = Genre.all
      render :new
    end
  end

  def show
    @item = Item.find(params[:id])
  end

  def edit
    @item = Item.find(params[:id])
    @genres = Genre.all
  end
  
  def update
    @item = Item.find(params[:id])
    if @item.update(item_params)
      redirect_to admin_item_path(@item.id), notice: "商品情報が更新されました"
    else
      @genres = Genre.all
      render :edit
    end
  end
  
  private
  
  def item_params
    params.require(:item).permit(:image,:genre_id,:name,:description,
                                 :price_without_tax,:is_on_sale)
  end
end

画像のプレビュー表示

View

app/views/admin/items/new.html.erb
  <table class="table table-borderless">
    <tr>
      <td>商品画像</td>
      <td>
	<%= f.file_field :image, accept: "image/png, image/jpeg" %><br>
+	<!--プレビュー表示-->
+	<img class="mt-2" id="image-preview" src="#" alt="プレビュー" style="max-width: 150px; border: solid thin lightgray; border-radius: 5px;">
      </td>
app/views/admin/items/new.html.erb
<!--画像ファイルが選択された際に発火し、画像のプレビューを表示-->
<script>
  # 初期状態では非表示に
  $("#image-preview").hide();

  # 画像が選択された際に実行される関数
  function showPreview(event) {
    var file = event.target.files[0];
    var reader = new FileReader();

    # アップロードした画像をセットし、表示する
    reader.onload = function(e) {
      var previewElement = $("#image-preview");
      previewElement.attr("src", e.target.result);
      previewElement.show(); # 画像を表示する
    }

    # ファイルをデータURL形式で読み込む
    if (file) {
      reader.readAsDataURL(file);
    }
  }

  # 再度別画像が読み込まれた時にプレビューを変更する
  var fileInput = $("#item_image");
  fileInput.on("change", showPreview);
</script>
🌱解説

$("#image-preview").hide();
まず、ページが読み込まれた時点でプレビュー用の画像エレメント(#image-preview)を非表示にします。これは、画像がまだ選択されていない時にプレビューが表示されることを防ぐためです。

function showPreview(event) {...}
次に、選択されたファイルのプレビューを表示する関数を定義します。この関数は、ファイルが選択されたとき(つまり、ファイル選択エレメントのchangeイベントが発生したとき)に実行されます。

var file = event.target.files[0];
まず、選択されたファイルを取得します。ここでのevent.targetはファイル選択エレメントを、files[0]はそのエレメントで選択されたファイルのリストの最初の要素(つまり、選択されたファイル)を指します。

var reader = new FileReader();
次に、FileReaderオブジェクトを作成します。このオブジェクトは、ブラウザにファイルを読み込ませるために使用します。

reader.onload = function(e) {...}
FileReaderオブジェクトのonloadイベントハンドラを設定します。この関数は、ファイルが正常に読み込まれたときに実行されます。

var previewElement = $("#image-preview");
プレビューを表示するエレメントを取得します。

previewElement.attr("src", e.target.result);
読み込まれたファイルのデータ(ここでのe.target.result)をプレビュー画像のsrc属性に設定します。これにより、選択された画像がプレビューエレメントに表示されます。

previewElement.show();
プレビューエレメントを表示します。

if (file) {...}
ファイルが正常に選択されていることを確認します。もし選択されたファイルがあれば(つまり、fileがnullやundefinedでなければ)、そのファイルを読み込みます。

var fileInput = $("#item_image");
ファイル選択エレメントを取得します。

fileInput.on("change", showPreview);
ファイル選択エレメントのchangeイベントが発生したとき(つまり、ユーザーが新しいファイルを選択したとき)にshowPreview関数を実行します。


便利になったー!!
JavaScript早く学ばなければですね🥺

Discussion

AirichanAirichan

デザインめっちゃ綺麗!!!!

sayasaya

ありがとう!
デザインは細かい部分まで直したから伝わって嬉しい🥺