[RUBY] Implementation method of linking multiple images to one post and posting at the same time

Introduction

I posted an article as a memo that I had a hard time implementing the function to post multiple images

Launch the app

$ rails _5.2.3_new app name-d mysql
$cd app name
$ bin/rails db:create

Introduction of haml

Since this implementation will be done with haml, we will introduce haml

Add the following to your gem file Don't forget to do bundle install

gem 'haml-rails'

Create a post function easily

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 and migration

product migration file

class CreateProducts < ActiveRecord::Migration[5.2]
  def change
    create_table :products do |t|
      t.string :name

      t.timestamps
    end
  end
end

image migration file


 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 model


class Product < ApplicationRecord
  has_many :images
  accepts_nested_attributes_for :images, allow_destroy: true
end

image model


class Image < ApplicationRecord
  mount_uploader :src, ImageUploader
  belongs_to :product
end

We will be able to upload images with the image model.

Add the following to the Gemfile and $ bundle install

gem 'carrierwave'
gem 'mini_magick'

Create uploader

Do the following in the terminal

$ rails g uploader image

Next, the image_uploader.rb file has been generated, so edit the following

include CarrierWave::MiniMagick  //Find this description and uncomment it

process resize_to_fit: [100, 100]  //This description is added

Finally edit haml and 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
Exhibition image
            %span.necessary
Mandatory
        %p.upload
You can upload up to 5 photos
        
        #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
Product name
              %span.necessary
Mandatory
          .name-input
            = f.text_field :name, {class: "drop-input", placeholder: "Up to 40 characters"}
    
    .input-field
      .input-field__contents
        .input-field__contents-price
          .sell
            = f.submit "Sell", 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;
    }
  }
}

If it looks like the following, it is complete. https://gyazo.com/a1d705516656f50c689abc7c18de5ec9

Post multiple images

Introduction of jQuery

gem 'jquery-rails'

Then do bundle install

Editing application.js

//= require rails-ujs
//= require activestorage
//= require jquery
//= require_tree .

Editing new.haml


-#Before editing
#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
-#After editing

#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

Create and edit new.js to post multiple images

new.js


$(function(){
  //Create a box to store data with DataTransfer object
  var dataBox = new DataTransfer(); //Step ②
  //file with querySelector_get field
  var file_field = document.querySelector('input[type=file]')
  //Event that fires when file is selected
  $('#img-file').change(function(){
    //Get the object of the selected file with prop
    var files = $('input[type=file]').prop('files')[0];
    $.each(this.files, function(i,file){
    //Read the File object specified by readAsDataURL of FileReader
    var fileReader = new FileReader();

    //Add file to DataTransfer object
    dataBox.items.add(file) //Step ②
    //file list of files in dataTransfer object_Substitute in field
    file_field.files =  dataBox.files //Step ②

    var num = $('.item-image').length + 1 + i //Step ②
    fileReader.readAsDataURL(file); //Step ②
     //When the number of images reaches 10, delete the drop box when it exceeds
     if (num == 5){ //Step ②
      $('#image-box__container').css('display', 'none')
     }
    //When loading is complete, store the URL of file in src
    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'>Delete</div>
                    </div>
                  </div>`
     //image_box__Insert html before the container element
      $('#image-box__container').before(html);
      };
    //   fileReader.readAsDataURL(file);
    //  });
     //image-box__Change the class of container and change the size of the drop box with CSS.
     $('#image-box__container').attr('class', `item-num-${num}`)
    });
  });
    $(document).on("click", '.item-image__operetion--delete', function(){
      //Get preview element
      var target_image = $(this).parent().parent()
      //Delete preview
      target_image.remove();
      //Delete the file in the input tag
      file_field.val("")
    })
});

Editing 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;
      }  
    }
    //Review view 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;
    }
  }
}

This completes the whole process Check the operation https://gyazo.com/372657130a2696e1865c02fdd6e9e303

Edit and delete functions will be continued in another article

Recommended Posts

Implementation method of linking multiple images to one post and posting at the same time
[Java] Method call error when inheritance and interface implementation are performed at the same time
Setting to start multiple units at the same time with Vagrant
[Spring Boot] Post files and other data at the same time [Axios]
[Register multiple photos] Register multiple rails images at the same time Primitive power technique
[Spring Boot] POST file array / list and other data at the same time [Axios]
[Must-see for beginners] I changed the display of the posting time to Japanese time [l method]
Method to add the number of years and get the end of the month
How to call multiple names at once in the same category
Draw a bar graph and a line graph at the same time with MPAndroidChart
How to change the maximum and maximum number of POST data in Spark
I want to pass the argument of Annotation and the argument of the calling method to aspect
Choose the first non-empty one from multiple Optional and call that method
I translated the grammar of R and Java [Updated from time to time]
How to implement the email authentication function at the time of user registration
[Rails] How to solve the time lag of created_at after save method
[Java] Is it unnecessary to check "identity" in the implementation of the equals () method?
[Ruby] Define the hierarchy at the same time as Hash initialization with tap method
Set the time of LocalDateTime to a specific time
Output of how to use the slice method
Summary of Japan time setting and display method
How to delete the tweet associated with the user when you delete it at the same time
How to write offline real time Implementation example by ruby and C99 of F04
I tried to create a log reproduction script at the time of apt install
Behavior noticed when adding RadioButton and initial check at the same time in code
Form and process file and String data at the same time with Spring Boot + Java