I tried to implement the image preview function with Rails / jQuery

Introduction

While creating the portfolio site, I had an image upload function, but there was a problem that it was difficult to understand whether any image could be selected properly because there was no preview function.

Therefore, I decided to add an image preview function and implemented it.

Although there are some issues, I have implemented it, so I would like to summarize it.

Premise

detail of function

Image upload flow

  1. Press the input tag for file input
  2. Image selection
  3. Replace the plastic view image with the selected image

At the time of validation / post editing

--The selected file is displayed in large size as a saved image --Press the "Change image" button → Show / hide the preview image and input tag with toggle

Implementation

View

erb:app/views/posts/_form.html.erb



<%= form_with(model: post, local: true) do |form| %>

	<!--···abridgement···-->

	<div class="form-file custom-file mb-3">
    <% if post.image.present? %>
      <div class="form-image-uploader__saved-img">
        <span class="form-image-uploader__saved-img-inner">
          <%= image_tag post.image.to_s %>
        </span>
      </div>
      <div class="block-toggle">
        <div class="block-toggle__press">
          <div class="btn btn-outline-secondary">
            <i class="fas fa-arrow-circle-down"></i>Change the image
          </div>
        </div>
        <div class="block-toggle__content" style="display:none;">
          <label for="post_image" class="form-image-uploader__preview">
            <%= image_tag 'nophoto.jpg' %>
          </label>
          <%= form.file_field :image, class:'form-image-uploader__save' %>
          <%= form.hidden_field :image_cache, class:'form-image-uploader__cache' %>
        </div>
      </div>
    <% else %>
      <label for="post_image" class="form-image-uploader__preview">
        <%= image_tag 'nophoto.jpg' %>
      </label>
      <%= form.file_field :image, class:'form-image-uploader__save' %>
      <%= form.hidden_field :image_cache, class:'form-image-uploader__cache' %>
    <% end %>
  </div>

	<!--···abridgement···-->

<% end %>

JS

ʻThe this.noPhotoImgPath of the ImgUplorader` class sets any path.

app/javascript/packs/application.js



/*Slide Toggle
------------------------------------------------------*/
$(document).on('turbolinks:load', () =>{
  $('.block-toggle__press .btn').on('click', event =>{
    $(event.currentTarget).parent('.block-toggle__press').next('.block-toggle__content').slideToggle(700);
  });
});

/*Image uplorader
------------------------------------------------------*/
$(document).on('turbolinks:load', () =>{

  const imgUplorader = new ImgUplorader;
  imgUplorader.copyToSaveInput();

});

class ImgUplorader{
  constructor(){
    this.selectorPreview = '.form-image-uploader__preview';
    this.selectorSave = '.form-image-uploader__save';
    this.selectorCache = '.form-image-uploader__cache';
    this.noPhotoImgPath = '/assets/nophoto.jpg';// <=Nophoto Set image path

  }

  /*
  * Change preview image to nophoto image when image is not selected
  * @param input : Element of current target
  */
  copyToSaveInput(){
    $(document).on('change', this.selectorSave, event => {

      const input = $(event.currentTarget);
      const filesLength = input[0].files.length;
      const cacheDefaultVal = $(input).next(this.selectorCache)[0].defaultValue;

      // Change preview image to nophoto image when image is not selected
      if (this.hasNotImg(filesLength)) {
        this.changeNoPhotoImg(input);
        return;
      }

      // Change preview image to selected image when image is selected
      this.changeSelectedImg(input);

    });
  }

  /*
   * Return true when input doesn't have file
   * @param filesLength : file length of input
   * @return bool
  */
  hasCacheDefaultImg(filesLength){
    if(filesLength == 0){
      return true;
    }

    return false;
  }

  /*
   * Return true when input doesn't have file
   * @param filesLength : file length of input
   * @return bool
  */
  hasNotImg(filesLength){
    if(filesLength == 0){
      return true;
    }

    return false;
  }

  /*
   * Change preview image to nophoto image when image is not selected
   * @param input : Element of current target
  */
  changeNoPhotoImg(input){
    $(input).prev(this.selectorImg).children('img').attr('src', this.noPhotoImgPath);
  }

  /*
   * Change preview image to selected image when image is selected
   * @param input : Element of current target
  */
  changeSelectedImg(input){
    const reader = new FileReader();
    reader.onload = (progressEvent) => {
      $(input).prev(this.selectorImg).children('img').attr('src', progressEvent.currentTarget.result);
    }

    const file = input[0].files[0];
    reader.readAsDataURL(file);
  }
}

SCSS

app/assets/stylesheets/application.scss



/*form-image-uploader
------------------------------------------------------*/
.form-image-uploader {
  @at-root {
    #{&}__saved-img {
      margin-bottom: 1em;

      @at-root {
        #{&}-inner {
          border: 1px solid #ced4da;
          border-radius: 0.25rem;
          display: inline-block;
        }
      }

      img {
        max-height: 300px;
      }
    }

    #{&}__preview {
      display: inline-block;
      border: 1px solid #ced4da;
      border-radius: 0.25rem;
      position: relative;
      cursor: pointer;

      img {
        max-width: 100px;
        width: auto;
        max-height: 100px;
        height: 100%;

        &:hover {
          opacity: 0.7;
        }
      }
    }
  }
}

/*block-toggle
------------------------------------------------------*/
.block-toggle {
  @at-root {
    #{&}__press {
      cursor: pointer;
      margin-bottom: 0.5em;
    }

    #{&}__content {
      border: 1px solid #ced4da;
      border-radius: 0.25rem;
      padding: 0.5em;
    }
  }
}

Verification

The following operation verification was performed on Chrome, Firefox, and Safari.

At the time of new posting

_2020-11-03_6.39.46.mov.gif

When editing a post

_2020-11-03_6.41.47.mov.gif

Task

--If the selected image name is long, the file name will be long horizontally and exceed the wrapping HTML element. --After selecting an image, press the "Cancel" button → It would be nice to be able to add a function to return the preview image to noptho.

finally

Next time, I would like to implement an image preview function that overcomes the above issues.

Reference article

[Rails] Implementation of image preview function --Qiita

Bootstrap4 custom-file mutiple image preview

Recommended Posts

I tried to implement the image preview function with Rails / jQuery
I tried to implement the like function by asynchronous communication
[Rails] I tried to implement batch processing with Rake task
[Rails] I tried to implement "Like function" using rails and js
I tried to implement the Iterator pattern
I tried to implement ModanShogi with Kinx
I tried to implement Ajax processing of like function in Rails
I tried to make a group function (bulletin board) with Rails
I tried to verify AdoptOpenJDK 11 (11.0.2) with Docker image
[Rails] I tried to raise the Rails version from 5.0 to 5.2
I tried to organize the session in Rails
I tried to implement file upload with Spring MVC
I tried to implement TCP / IP + BIO with JAVA
I tried to implement a function equivalent to Felica Lite with HCE-F of Android
[Rails] I tried playing with the comment send button
I tried to implement Stalin sort with Java Collector
[Rails] Implement image posting function
I tried to implement the Euclidean algorithm in Java
Rails API mode I tried to implement the keyword multiple search function using arrays and iterative processing.
I tried to increase the processing speed with spiritual engineering
[Rails] I tried to create a mini app with FullCalendar
I tried upgrading from CentOS 6.5 to CentOS 7 with the upgrade tool
I tried to interact with Java
I tried to explain the method
[Rails] I tried deleting the application
[Ruby on Rails] Since I finished all Rails Tutorial, I tried to implement an additional "stock function"
I tried to solve the problem of "multi-stage selection" with Ruby
I want to add a browsing function with ruby on rails
I tried to build the environment of PlantUML Server with Docker
I tried to implement flexible OR mapping with MyBatis Dynamic SQL
I tried to understand how the rails method "redirect_to" is defined
I tried to check the operation of gRPC server with grpcurl
I tried to understand how the rails method "link_to" is defined
[JQuery] How to preview the selected image immediately + Add image posting gem
Rails Tutorial Extension: I tried to create an RSS feed function
I tried to summarize the methods used
I tried to introduce CircleCI 2.0 to Rails app
[Rails] Implementation of multi-layer category function using ancestry "I tried to make a window with Bootstrap 3"
I tried to get started with WebAssembly
Try to implement login function with Spring-Boot
[Swift] How to implement the countdown function
How to implement TextInputLayout with validation function
I tried to summarize the Stream API
I tried to use Selenium like JQuery
How to implement image posting using rails
How to implement search function with rails (multiple columns are also supported)
[Rails] I tried to implement a transaction that combines multiple DB processes
[Ruby] I tried to diet the if statement code with the ternary operator
I tried to solve the tribonacci sequence problem in Ruby, with recursion.
[Rails] Implement the product purchase function with a credit card registered with PAY.JP
I tried to visualize the access of Lambda → Athena with AWS X-Ray
I want to introduce the committee with Rails without getting too dirty
I tried to measure and compare the speed of GraalVM with JMH
After all I wanted to preview the contents of mysql with Docker ...
Image preview function
I tried to figure out the flow when performing image analysis with Vision Framework and Core ML
I tried to implement polymorphic related in Nogizaka.
I tried to manage struts configuration with Coggle
I tried to manage login information with JMX
[Swift] How to implement the LINE login function
I tried writing CRUD with Rails + Vue + devise_token_auth