はじめに
複数枚の画像を投稿する機能の実装に、苦労したてめメモとして記事を投稿します
アプリを立ち上げる
$ rails _5.2.3_ new アプリ名 -d mysql
$ cd アプリ名
$ bin/rails db:create
##hamlの導入
今回実装はhamlで行うため、hamlを導入します
gemファイルに以下を追加します
忘れずにbundle installをしてください
gem 'haml-rails'
簡単に投稿機能を作成します
##routes
Rails.application.routes.draw do
root 'products#index'
resources :products, only: [:index, :new, :create]
end
##controller
class ProductsController < ApplicationController
def index
@products = Product.includes(:images).order('created_at DESC')
end
def new
@product = Product.new
@product.images.new
end
def create
@product = Product.new(product_params)
if @product.save
redirect_to root_path
else
render :new
end
end
private
def product_params
params.require(:product).permit(:name, images_attributes: [:src])
end
end
modelとマイグレーション
productマイグレーションファイル
class CreateProducts < ActiveRecord::Migration[5.2]
def change
create_table :products do |t|
t.string :name
t.timestamps
end
end
end
imageマイグレーションファイル
class CreateImages < ActiveRecord::Migration[5.2]
def change
create_table :images do |t|
t.string :src
t.references :product, foreign_key: true
t.timestamps
end
end
end
productsモデル
class Product < ApplicationRecord
has_many :images
accepts_nested_attributes_for :images, allow_destroy: true
end
imageモデル
class Image < ApplicationRecord
mount_uploader :src, ImageUploader
belongs_to :product
end
imageモデルで画像をアップロードできるようにしていきます。
Gemfileに以下を追記して$ bundle install
gem 'carrierwave'
gem 'mini_magick'
##uploaderを作成します
ターミナルで以下を実行します
$ rails g uploader image
続いて、image_uploader.rbファイルが生成されたので以下を編集します
include CarrierWave::MiniMagick // この記述を探し、コメントアウトを外す
process resize_to_fit: [100, 100] // この記述は追記
##最後にhamlとscssの編集
haml/new
.lead
=link_to "/products" do
=image_tag "http://furima.tokyo/assets/logo-d3d78326971d78b06e3d6f0ba666d025a8ad681286b4d9e00e7dbe8673bcfd23.svg", class: "lead__img"
= form_with model: @product, local: true do |f|
.input-field
.input-field__contents
.input-field__contents-image
.input-field__contents-image__headline
.headlabel
出品画像
%span.necessary
必須
%p.upload
最大5枚までアップロードできます
#image-box-1
.item-num-0#image-box__container
= f.fields_for :images do |i|
.input-field__contents-image__drop__js-file
.input-area
= i.file_field :src
.input-field__contents-name
.input-field__contents-image__headline
.headlabel
%label
商品名
%span.necessary
必須
.name-input
= f.text_field :name, {class: "drop-input", placeholder: "40文字まで"}
.input-field
.input-field__contents
.input-field__contents-price
.sell
= f.submit "出品する", class: "sellbtn", tabindex: "0"
new.scss
.lead {
background-color: rgb(245, 245, 245);
text-align: center;
height: 128px;
line-height: 10;
}
.input-field {
background-color: rgb(245, 245, 245);
width: 100%;
.input-field__contents {
left: 0;
background-color: white;
max-width: 800px;
margin: 0 auto;
padding: 40px;
border-bottom: 1px solid hsl(0, 0%, 77%);
height: 100%;
.input-field__contents-image {
width: 800px;
border-bottom: rgb(204, 204, 204);
.input-field__contents-image__headline{
margin-top: 20px;
margin-left: 5px;
}
.upload {
margin-top: 16px;
margin-left: 5px;
}
#image-box-1 {
display: flex;
height: 130px;
width: 100%;
margin-right: 0px;
text-align: center;
i{
padding-top: 50px;
}
.item-num-0#image-box__container {
background-color: rgb(245, 245, 245);
height: 100%;
width: 100%;
border-width: 1px;
border-style: dashed;
border-color: rgb(204, 204, 204);
border-image: initial;
text-align: center;
}
}
}
}
.drop-input {
width: 60%;
height: 50px;
border-color: #cccccc;
border-radius: 4px;
border-style: solid;
border-width: 1px;
margin: 10px 10px 0 0;
}
.name-input{
.drop-input{
width: 100%;
height: 50px;
border-color: #cccccc;
border-style: solid;
}
}
.sell {
text-align: center;
display: grid;
width: 50%;
margin-left: 200px;
.sellbtn {
background-color: #3ccace;
color: white;
border-color: transparent;
font-weight: 600;
line-height: 3;
cursor: pointer;
}
}
}
以下の様になれば、完成です。
https://gyazo.com/a1d705516656f50c689abc7c18de5ec9
画像を複数枚投稿する
###jQueryの導入
gem 'jquery-rails'
続いて、bundle installをしてください
###application.jsの編集
//= require rails-ujs
//= require activestorage
//= require jquery
//= require_tree .
new.hamlの編集
-# 編集前
#image-box-1
.item-num-0#image-box__container
= f.fields_for :images do |i|
.input-field__contents-image__drop__js-file
.input-area
= i.file_field :src
-# 編集後
#image-box-1
.item-num-0#image-box__container
= f.fields_for :images do |i|
.input-field__contents-image__drop__js-file
.input-area
= i.file_field :src, type: 'file', name: "product[images_attributes][][name]", value:"", style: "display:none", id:"img-file"
%label{for: "img-file"}
%i.fas.fa-camera
画像を複数枚投稿する様にする new.js作成し、編集する
new.js
$(function(){
//DataTransferオブジェクトで、データを格納する箱を作る
var dataBox = new DataTransfer(); //ステップ②
//querySelectorでfile_fieldを取得
var file_field = document.querySelector('input[type=file]')
//fileが選択された時に発火するイベント
$('#img-file').change(function(){
//選択したfileのオブジェクトをpropで取得
var files = $('input[type=file]').prop('files')[0];
$.each(this.files, function(i,file){
//FileReaderのreadAsDataURLで指定したFileオブジェクトを読み込む
var fileReader = new FileReader();
//DataTransferオブジェクトに対して、fileを追加
dataBox.items.add(file) //ステップ②
//dataTransferオブジェクトに入ったfile一覧をfile_fieldの中に代入
file_field.files = dataBox.files //ステップ②
var num = $('.item-image').length + 1 + i //ステップ②
fileReader.readAsDataURL(file); //ステップ②
//画像が10枚になったら超えたらドロップボックスを削除する
if (num == 5){ //ステップ②
$('#image-box__container').css('display', 'none')
}
//読み込みが完了すると、srcにfileのURLを格納
fileReader.onloadend = function() {
var src = fileReader.result
var html = `<div class='item-image' data-image="${file.name}">
<div class=' item-image__content'>
<div class='item-image__content--icon'>
<img src=${src} width="150" height="90" >
</div>
</div>
<div class='item-image__operetion'>
<div class='item-image__operetion--delete'>削除</div>
</div>
</div>`
//image_box__container要素の前にhtmlを差し込む
$('#image-box__container').before(html);
};
// fileReader.readAsDataURL(file);
// });
//image-box__containerのクラスを変更し、CSSでドロップボックスの大きさを変えてやる。
$('#image-box__container').attr('class', `item-num-${num}`)
});
});
$(document).on("click", '.item-image__operetion--delete', function(){
//プレビュー要素を取得
var target_image = $(this).parent().parent()
//プレビューを削除
target_image.remove();
//inputタグに入ったファイルを削除
file_field.val("")
})
});
scssの編集
.lead {
background-color: rgb(245, 245, 245);
text-align: center;
height: 128px;
line-height: 10;
}
.input-field {
background-color: rgb(245, 245, 245);
width: 100%;
&__contents {
left: 0;
background-color: white;
max-width: 800px;
margin: 0 auto;
padding: 40px;
border-bottom: 1px solid hsl(0, 0%, 77%);
height: 100%;
}
.input-field__contents-image {
width: 800px;
border-bottom: rgb(204, 204, 204);
.input-field__contents-image__headline{
font-weight: 600;
margin-top: 20px;
margin-left: 5px;
.name-input {
height: 54px;
.option-input {
display: block;
width: 93%;
border-color: #cccccc;
height: 100%;
border-radius: 4px;
font-weight: bolder;
padding: 0px 2px 1px;
border-width: 1px;
}
}
}
.upload {
margin-top: 16px;
margin-left: 5px;
}
#image-box-1 {
display: flex;
height: 130px;
width: 100%;
margin-right: 0px;
text-align: center;
i{
padding-top: 50px;
cursor: pointer;
}
.item-num-0#image-box__container {
background-color: rgb(245, 245, 245);
height: 100%;
width: 100%;
border-width: 1px;
border-style: dashed;
border-color: rgb(204, 204, 204);
border-image: initial;
text-align: center;
}
.item-num-1{
background-color: rgb(245, 245, 245);
height: 100%;
width: 100%;
border-width: 1px;
border-style: dashed;
border-color: rgb(204, 204, 204);
border-image: initial;
text-align: center;
}
.item-num-2{
background-color: rgb(245, 245, 245);
height: 100%;
width: 100%;
border-width: 1px;
border-style: dashed;
border-color: rgb(204, 204, 204);
border-image: initial;
text-align: center;
}
.item-num-3{
background-color: rgb(245, 245, 245);
height: 100%;
width: 100%;
border-width: 1px;
border-style: dashed;
border-color: rgb(204, 204, 204);
border-image: initial;
text-align: center;
}
.item-num-4{
background-color: rgb(245, 245, 245);
height: 100%;
width: 100%;
border-width: 1px;
border-style: dashed;
border-color: rgb(204, 204, 204);
border-image: initial;
text-align: center;
}
.item-num-5{
background-color: rgb(245, 245, 245);
height: 100%;
width: 100%;
border-width: 1px;
border-style: dashed;
border-color: rgb(204, 204, 204);
border-image: initial;
text-align: center;
}
}
//レビュー表示のCSS
.item-image{
height: 130px;
width: 160px;
border: 1px solid #eee;
margin-right: 10px;
.item-image__content{
padding-top: 10px;
.item-image__content--icon{
}
}
.item-image__operetion{
.item-image__operetion--delete{
color: #00b0ff;
cursor: pointer;
padding-top: 5px;
text-align: center;
}
}
}
}
.text-area {
border-radius: 4px;
font-size: 16px;
padding: 13px 16px;
border-color: #cccccc;
margin-top: 30px;
}
.drop-input {
width: 100%;
height: 50px;
border-color: #cccccc;
border-radius: 4px;
border-style: solid;
border-width: 1px;
margin: 10px 10px 0 0;
::placeholder {
padding: 20px;
font-weight: inherit;
}
}
.headlabel {
margin-top: 30px;
.necessary {
background-color: #3ccace;
color: white;
padding: 2px 4px;
font-size: 14px;
margin-left: 3px;
cursor: pointer;
border-radius: 2px;
}
}
.sell {
text-align: center;
display: grid;
width: 50%;
margin-left: 200px;
.sellbtn {
background-color: #3ccace;
color: white;
font-size: 20px;
min-height: 48px;
padding: 0 24px;
border-color: transparent;
border-radius: 2px;
font-weight: 600;
line-height: 3;
}
}
}
これで一通りは完成
動作を確認
https://gyazo.com/372657130a2696e1865c02fdd6e9e303
編集や削除の機能は別記事で続きを書きます