学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

Rails7.1 | 動画学習アプリ作成 | 25 | Dropzone

↓↓クリックして頂けると励みになります。



24 | プロジェクトの登録】 << 【ホーム】 >> 【26 | タスクの登録




Dropzone.jsをRuby on Railsプロジェクトに統合することは、ファイルアップロードのユーザーエクスペリエンスを向上させ、アプリケーション全体の機能性と使いやすさを向上させるために非常に有益です。


Dropzone.jsは、ユーザーエクスペリエンスを向上させるために、ドラッグアンドドロップを使用してファイルをアップロードする方法を提供します。
ユーザーはファイルを選択するだけでなく、ファイルをブラウザウィンドウにドラッグしてアップロードできます。
これにより、使いやすさが向上し、アップロードプロセスがシームレスになります。


Rails7.1使用の場合、Importmapからの読み込みがうまくいかないため、CDN経由でスクリプトを読み込ませるようにします。


「app/views/layouts/application.html.erb」ファイルのheadタグに以下の記述を追加します。

    <!-- Dropzone5.5.1 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/min/dropzone.min.js" integrity="sha512-jytq61HY3/eCNwWirBhRofDxujTCMFEiQeTe+kHR4eYLNTXrUq7kY2qQDKOUnsVAKN5XGBJjQ3TvNkIkW/itGw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>



記述追加 【app/views/layouts/application.html.erb】23行目

<!DOCTYPE html>
<html>
  <head>
    <title>StreamAcademe</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>

    <!-- noty -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/noty/3.1.4/noty.min.js" integrity="sha512-lOrm9FgT1LKOJRUXF3tp6QaMorJftUjowOWiDcG5GFZ/q7ukof19V0HKx/GWzXCdt9zYju3/KhBNdCLzK8b90Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    <!-- Google Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Kaisei+Opti&family=Kosugi+Maru&family=Rampart+One&display=swap" rel="stylesheet">

    <!-- Font Awesome -->
    <script src="https://kit.fontawesome.com/dd8c589546.js" crossorigin="anonymous"></script>

    <!-- Dropzone5.5.1 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/min/dropzone.min.js" integrity="sha512-jytq61HY3/eCNwWirBhRofDxujTCMFEiQeTe+kHR4eYLNTXrUq7kY2qQDKOUnsVAKN5XGBJjQ3TvNkIkW/itGw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

  </head>

  <body>

    <!-- ナビゲーションバー -->
    <%= render  "shared/navbar" %>

    <!-- noty -->
    <%= render 'shared/notification' %>

    <%= yield %>
    
  </body>
</html>



「app/assets/stylesheets」フォルダに「_dropzone.scss」ファイルを新規作成します。
作成した「_dropzone.scss」ファイルを以下のように編集します。



新規作成 【app/assets/stylesheets/_dropzone.scss】

@-webkit-keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%, 70%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@-moz-keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%, 70%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%, 70%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@-webkit-keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}}@-moz-keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}}@keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}}@-webkit-keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}@-moz-keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}@keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}.dropzone,.dropzone *{box-sizing:border-box}.dropzone{min-height:150px;border:2px solid rgba(0,0,0,0.3);background:white;padding:20px 20px}.dropzone.dz-clickable{cursor:pointer}.dropzone.dz-clickable *{cursor:default}.dropzone.dz-clickable .dz-message,.dropzone.dz-clickable .dz-message *{cursor:pointer}.dropzone.dz-started .dz-message{display:none}.dropzone.dz-drag-hover{border-style:solid}.dropzone.dz-drag-hover .dz-message{opacity:0.5}.dropzone .dz-message{text-align:center;margin:2em 0}.dropzone .dz-preview{position:relative;display:inline-block;vertical-align:top;margin:16px;min-height:100px}.dropzone .dz-preview:hover{z-index:1000}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview.dz-file-preview .dz-image{border-radius:20px;background:#999;background:linear-gradient(to bottom, #eee, #ddd)}.dropzone .dz-preview.dz-file-preview .dz-details{opacity:1}.dropzone .dz-preview.dz-image-preview{background:white}.dropzone .dz-preview.dz-image-preview .dz-details{-webkit-transition:opacity 0.2s linear;-moz-transition:opacity 0.2s linear;-ms-transition:opacity 0.2s linear;-o-transition:opacity 0.2s linear;transition:opacity 0.2s linear}.dropzone .dz-preview .dz-remove{font-size:14px;text-align:center;display:block;cursor:pointer;border:none}.dropzone .dz-preview .dz-remove:hover{text-decoration:underline}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview .dz-details{z-index:20;position:absolute;top:0;left:0;opacity:0;font-size:13px;min-width:100%;max-width:100%;padding:2em 1em;text-align:center;color:rgba(0,0,0,0.9);line-height:150%}.dropzone .dz-preview .dz-details .dz-size{margin-bottom:1em;font-size:16px}.dropzone .dz-preview .dz-details .dz-filename{white-space:nowrap}.dropzone .dz-preview .dz-details .dz-filename:hover span{border:1px solid rgba(200,200,200,0.8);background-color:rgba(255,255,255,0.8)}.dropzone .dz-preview .dz-details .dz-filename:not(:hover){overflow:hidden;text-overflow:ellipsis}.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span{border:1px solid transparent}.dropzone .dz-preview .dz-details .dz-filename span,.dropzone .dz-preview .dz-details .dz-size span{background-color:rgba(255,255,255,0.4);padding:0 0.4em;border-radius:3px}.dropzone .dz-preview:hover .dz-image img{-webkit-transform:scale(1.05, 1.05);-moz-transform:scale(1.05, 1.05);-ms-transform:scale(1.05, 1.05);-o-transform:scale(1.05, 1.05);transform:scale(1.05, 1.05);-webkit-filter:blur(8px);filter:blur(8px)}.dropzone .dz-preview .dz-image{border-radius:20px;overflow:hidden;width:120px;height:120px;position:relative;display:block;z-index:10}.dropzone .dz-preview .dz-image img{display:block}.dropzone .dz-preview.dz-success .dz-success-mark{-webkit-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);-moz-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);-ms-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);-o-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1)}.dropzone .dz-preview.dz-error .dz-error-mark{opacity:1;-webkit-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);-moz-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);-ms-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);-o-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1)}.dropzone .dz-preview .dz-success-mark,.dropzone .dz-preview .dz-error-mark{pointer-events:none;opacity:0;z-index:500;position:absolute;display:block;top:50%;left:50%;margin-left:-27px;margin-top:-27px}.dropzone .dz-preview .dz-success-mark svg,.dropzone .dz-preview .dz-error-mark svg{display:block;width:54px;height:54px}.dropzone .dz-preview.dz-processing .dz-progress{opacity:1;-webkit-transition:all 0.2s linear;-moz-transition:all 0.2s linear;-ms-transition:all 0.2s linear;-o-transition:all 0.2s linear;transition:all 0.2s linear}.dropzone .dz-preview.dz-complete .dz-progress{opacity:0;-webkit-transition:opacity 0.4s ease-in;-moz-transition:opacity 0.4s ease-in;-ms-transition:opacity 0.4s ease-in;-o-transition:opacity 0.4s ease-in;transition:opacity 0.4s ease-in}.dropzone .dz-preview:not(.dz-processing) .dz-progress{-webkit-animation:pulse 6s ease infinite;-moz-animation:pulse 6s ease infinite;-ms-animation:pulse 6s ease infinite;-o-animation:pulse 6s ease infinite;animation:pulse 6s ease infinite}.dropzone .dz-preview .dz-progress{opacity:1;z-index:1000;pointer-events:none;position:absolute;height:16px;left:50%;top:50%;margin-top:-8px;width:80px;margin-left:-40px;background:rgba(255,255,255,0.9);-webkit-transform:scale(1);border-radius:8px;overflow:hidden}.dropzone .dz-preview .dz-progress .dz-upload{background:#333;background:linear-gradient(to bottom, #666, #444);position:absolute;top:0;left:0;bottom:0;width:0;-webkit-transition:width 300ms ease-in-out;-moz-transition:width 300ms ease-in-out;-ms-transition:width 300ms ease-in-out;-o-transition:width 300ms ease-in-out;transition:width 300ms ease-in-out}.dropzone .dz-preview.dz-error .dz-error-message{display:block}.dropzone .dz-preview.dz-error:hover .dz-error-message{opacity:1;pointer-events:auto}.dropzone .dz-preview .dz-error-message{pointer-events:none;z-index:1000;position:absolute;display:block;display:none;opacity:0;-webkit-transition:opacity 0.3s ease;-moz-transition:opacity 0.3s ease;-ms-transition:opacity 0.3s ease;-o-transition:opacity 0.3s ease;transition:opacity 0.3s ease;border-radius:8px;font-size:13px;top:130px;left:-10px;width:140px;background:#be2626;background:linear-gradient(to bottom, #be2626, #a92222);padding:0.5em 1.2em;color:white}.dropzone .dz-preview .dz-error-message:after{content:'';position:absolute;top:-6px;left:64px;width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #be2626}



「app/assets/stylesheets/application.scss」ファイルの21行目に以下の記述を追加します。

 @use "./dropzone";



記述追加 【app/assets/stylesheets/application.scss】21行目

/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *
 *= require_tree .
 *= require_self
 */

 @import "bootstrap";

 @use "./noty";

 @use "./dropzone";

 body * {
    font-family: Kosugi Maru;
}

.font1 {
    font-family: Rampart One;
}

.font2 {
    font-family: Kaisei Opti;
}

//アバター オンライン
.avatar {
    position: relative;
    display: inline-block;
    &::before {
      content: "";
      position: absolute;
      bottom: 1px;
      left: 38px;
      width: 10px;
      height: 10px;
      border-radius: 100%;
      border: 1px solid white;
    }
    &.online:before {
      background-color: #1dbf73;
    }
    &.offline:before {
      background-color: gray;
    }
}



「app/controllers/projects_controller.rb」ファイルを以下の手順で編集していきます。


1.3行目に以下の記述を追加します。

protect_from_forgery except: [:upload_photo]



2.6行目に以下の記述を追加します。

before_action :is_authorised, only: [:naming, :pricing, :description, :photo_upload, :update]



3.27行目に以下の記述を追加します。

new_params = project_params.merge(active: true) if is_ready_project



4.50行目に「upload_photo()」メソッド、「delete_photo()」メソッドを追加します。

  def upload_photo
    @project.images.attach(params[:file])
    render json: { success: true }
  end

  def delete_photo
    @image = ActiveStorage::Attachment.find(params[:photo_id])
    @image.purge
    redirect_to photo_upload_project_path(@project)
  end



5.70行目に以下の記述を追加します。

  def is_authorised
    redirect_to root_path, alert: "権限がありません。" unless current_user.id == @project.user_id
  end
  
  def is_ready_project
    !@project.active && !@project.price.blank? && !@project.name.blank? && !@project.images.blank? && !@project.description.blank?
  end



記述更新 app/controllers/projects_controller.rb

class ProjectsController < ApplicationController

  protect_from_forgery except: [:upload_photo]
  before_action :authenticate_user!, except: [:show]
  before_action :set_project, except: [:new, :create, :show, :index]
  before_action :is_authorised, only: [:naming, :pricing, :description, :photo_upload, :update]

  def index
  	@projects = Project.all
  end

  def new
    @project = current_user.projects.build
  end

  def create
    @project = current_user.projects.build(project_params)
    if @project.save
      redirect_to naming_project_path(@project), notice: "保存しました"
    else
      redirect_to request.referrer, flash: { error: @project.errors.full_messages }
    end
  end

  def update
    new_params = project_params
    new_params = project_params.merge(active: true) if is_ready_project

    if @project.update(new_params)
      flash[:notice] = "保存しました。"
    else
      flash[:alert] = { error: @project.errors.full_messages }
    end
    redirect_back(fallback_location: request.referer)
  end

  def show
  	@project = Project.find(params[:id])
  	@tasks = @project.tasks.order(:tag)
    @i = 0
    @images = @project.images

  end

  def edit
    @project = Project.find(params[:id])
  	@tasks = @project.tasks.order(:tag)
  end

  def upload_photo
    @project.images.attach(params[:file])
    render json: { success: true }
  end

  def delete_photo
    @image = ActiveStorage::Attachment.find(params[:photo_id])
    @image.purge
    redirect_to photo_upload_project_path(@project)
  end

  private
  def set_project
    @project = Project.find(params[:id])
  end

  def project_params
    params.require(:project).permit(:name, :content, :price, :description, :images, :active)
  end

  def is_authorised
    redirect_to root_path, alert: "権限がありません。" unless current_user.id == @project.user_id
  end
  
  def is_ready_project
    !@project.active && !@project.price.blank? && !@project.name.blank? && !@project.images.blank? && !@project.description.blank?
  end

end



ルートの設定を以下の内容に更新します。


記述更新 config\routes.rb(26行目)

      delete :delete_photo
      post :upload_photo



config\routes.rb

Rails.application.routes.draw do

  # ルートを app\views\pages\home.html.erb に設定
  root 'pages#home'

  # get
  get 'pages/home'
  get '/dashboard', to: 'users#dashboard'
  get '/users/:id', to: 'users#show', as: 'user'

  # post
  post '/users/edit', to: 'users#update'

  # device
  devise_for :users, 
    path: '', 
    path_names: {sign_up: 'register', sign_in: 'login', edit: 'profile', sign_out: 'logout'},
    controllers: {omniauth_callbacks: 'omniauth_callbacks', registrations: 'registrations'}

  resources :projects do
    member do
      get 'naming'
      get 'pricing'
      get 'description'
      get 'photo_upload'
      delete :delete_photo
      post :upload_photo
    end
    resources :tasks, only: [:show, :index]
  end

  resources :tasks, except: [:edit] do
    member do
      get 'naming'
      get 'description'
      get 'video'
      get 'code'
    end
  end

  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  get "up" => "rails/health#show", as: :rails_health_check

  # Defines the root path route ("/")
  # root "posts#index"
end



「app/views/projects/_project_menu.html.erb」ファイルの22〜26行目を以下のように編集します。
Rails6.1の時は画面推移の時にJavascriptがうまく読み込まれないターボリンクの問題を解決するために「data: { turbolinks: false} 」という記述を入れていましたが、Rails7.1ではturbolinksがturboに置き換わったため、「data: { turbo: false }」という記述に変更しています。

            <li class="list-group-item" style="border:none;">
                <%= link_to "写真アップロード", photo_upload_project_path, class: "btn btn-light", data: { turbo: false }  %>
                <% if !@project.images.blank? %>
                    <span class="text-danger"><i class="fa fa-check"></i></span>
                <% end %>
            </li>



30行目の記述も以下のように変更します。

<% is_ready = !@project.active && !@project.price.blank? && !@project.name.blank? && !@project.description.blank? && !@project.images.blank? %>



app/views/projects/_project_menu.html.erb

<div class="card">
    <div class="card-body">
        <ul class="list-group">
            <li class="list-group-item" style="border:none;">
                <%= link_to "タイトル", naming_project_path, class: "btn btn-light" %>
                <% if !@project.name.blank? %>
                    <span class="text-danger"><i class="fa fa-check"></i></span>
                <% end %>    
            </li>
            <li class="list-group-item" style="border:none;">
                <%= link_to "内容", description_project_path, class: "btn btn-light" %>
                <% if !@project.description.blank? %>
                    <span class="text-danger"><i class="fa fa-check"></i></span>
                <% end %>        
            </li>
            <li class="list-group-item" style="border:none;">
                <%= link_to "価格", pricing_project_path, class: "btn btn-light" %>
                <% if !@project.price.blank? %>
                    <span class="text-danger"><i class="fa fa-check"></i></span>
                <% end %>
            </li>
            <li class="list-group-item" style="border:none;">
                <%= link_to "写真アップロード", photo_upload_project_path, class: "btn btn-light", data: { turbo: false }  %>
                <% if !@project.images.blank? %>
                    <span class="text-danger"><i class="fa fa-check"></i></span>
                <% end %>
            </li>
        </ul>
        <div class="mt-4">
            <% is_ready = !@project.active && !@project.price.blank? && !@project.name.blank? && !@project.description.blank? && !@project.images.blank? %>
            <%= form_for @project do |f| %>
            <%= f.hidden_field :active, value: true %>
            <%= f.submit "公開する", class: "btn btn-danger w-100", disabled: !is_ready %>
            <% end %>
        </div>
    </div>
</div>



「app/views/projects/photo_upload.html.erb」ファイルに記述を追加します。
削除のリンクを機能させるにはlink_toメソッドではなく、button_toメソッドを使用する必要があります。


app/views/rooms/photo_upload.html.erb

<div class="container mt-4">
    <div class="row">
        <div class="col-md-3">
            <%= render 'project_menu' %>
        </div>
        <div class="col-md-9">

            <div class="card">
                <div class="card-body">
                    <h4 class="mt-4 mb-4"><b>写真アップロード</b></h4>

                    <!-- 写真アップロード -->      
                    <div class="dropzone" id="myDropzone"  style="height: 200px; border: dashed 1px #333; border-radius: 10px; text-align: center; padding-top: 1rem;" action="/projects/<%= @project.id %>/upload_photo"></div>
                    <div class="container">
                        <div class="row">
                            <% @project.images.each do |photo| %>

                                <div class="col-4">
                                    <div class="card mt-2">
                                        <%= image_tag url_for(photo), class: "card-img-top"  %>
                                        <div class="card-body">               
                                            <p class="card-text">
                                                <%= button_to '削除', delete_photo_project_url(photo_id: photo.id, id: @project.id), 
                                                    method: :delete,
                                                    data: { turbo: false },
                                                    form: { onSubmit: "return check()" },
                                                    style: "z-index: 100;",
                                                    class: "btn btn-outline-danger btn-sm"
                                                %>
                                            </p>
                    
                                        </div>
                                    </div>
                                </div>
                            <% end %>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    Dropzone.options.myDropzone = {
        paramName: "file",
        maxFilesize: 5,
        acceptedFiles: "image/*",
        dictDefaultMessage: "ここに写真をドラッグ&ドロップして下さい。<br/>または、クリックすることで写真を選択することもできます。",
        init: function() {
            this.on('complete', function (file) {
                location.reload();
            })
        }
    }

    function check(){
        if(window.confirm('本当に削除してよろしいですか?')){ 
          return true;
        }
        else{
          window.alert('キャンセルされました'); 
          return false; 
        }
      }    
</script>



これで写真のアップロード、削除ができます。
複数の写真も同時にアップロードできます。
ブラウザ確認
http://localhost:3000/projects/4/photo_upload

PCレイアウト
PCレイアウト


モバイルレイアウト
モバイルレイアウト



24 | プロジェクトの登録】 << 【ホーム】 >> 【26 | タスクの登録




↓↓クリックして頂けると励みになります。