実装内容
carrierwaveを使用して画像アップロード機能を実装し、「よし!これで画像の投稿機能の完成だ!!」と思っていたのですが、何か物足りなさを感じて画像投稿機能に関する記事をいろいろと調べていた所、プレビュー機能というワクワク機能に出会ったので今回はその紹介 + バリデーション失敗時でも画像を表示する方法も紹介しております。
処理全体の流れ
- プレビュー機能の実装
- バリデーション失敗時の画像表示方法
完成形はこちら
プレビュー機能
バリデーション失敗時
環境
macOS Big Sur 11.2.3
ruby: 2.7.2
rails: 6.1.3
jQuery
テンプレートエンジン: slim
レイアウト: bootstrap4
前提
- jquery,deviseの導入は省いております。
- carrierwaveの導入と実装は省いております。
viewファイル
※Rail6ではform_forは非推奨ではありますが、上記に示した環境では問題なく動作しております。
= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f|
= render 'layouts/error_messages', model: f.object
・
・
・
.form-group
.d-block
= f.label :profile_image
- if f.object.profile_image?
= image_tag f.object.profile_image.to_s, id: 'profile_img_prev', class: 'user-information__img'
.form-group.mt-2
= f.check_box :remove_profile_image, class: 'mr-2'
= f.label :remove_profile_image
= f.hidden_field :profile_image_cache
- else
div
= image_tag 'no_profile_img.png', id: 'profile_img_prev', class: 'user-information__img'
= f.hidden_field :profile_image_cache
br
label.upload-btn
= 'ファイルを選択'
= f.file_field :profile_image, id: 'file_btn'
jQueryの操作ではid属性を使ってセレクタの指定を行なっていきます。
今回は、画像を表示するimage_tagにid: 'profile_img_prev'
を付与し、ファイル選択ボタンにid: 'file_btn'
を付与しています。
プレビュー機能の実装
webAPIのFileReader
というものを扱っています。
こちらを使うことで画像を非同期で読み取ることができます。
require("shared/image_preview");
$(function () {
$('#file_btn').on('change', function (e) {
var reader = new FileReader();
reader.onload = function (e) {
$('#profile_img_prev').attr('src', e.target.result);
}
reader.readAsDataURL(e.target.files[0]);
});
});
それでは順を追って説明いたします。
$('#file_btn').on('change', function (e) {
上記コードは、id:file_btn
を付与したファイル選択ボタンが押され、画像が選択されるとイベントが発生し、スコープ内の処理内容を実行してくれます。
(e)
はイベントオブジェクトのことで、発生したイベントに関する様々な情報(データ)が入っており、今回の場合、#file_btn
のデータが入っています。
var reader = new FileReader(); // FileReaderオブジェクトの生成
reader.onload = function (e) {
$('#profile_img_prev').attr('src', e.target.result);
}
次に、処理内容について説明します。
上記では、FileReaderオブジェクトを生成し、この後に説明するreadAsDataURLで画像の読み込みが完了したらreader.onload
でイベントが発生し、スコープ内の$('#profile_img_prev').attr('src', e.target.result);
が実行され、該当idを付与したimage_tag
のsrc値
に先ほど取得した画像データを反映させています。
reader.readAsDataURL(e.target.files[0]);
最後に、先ほどの説明でも登場したreadAsDataURLで指定したFileオブジェクト
を読み込んでいます。
また、ファイルを配列(複数)で受け取るようになっているため、最初のファイルのみ取得するようfiles[0]
としています。
ここで読み込むが完了すると、先ほどのonloadメソッド
が実行され、非同期で画像が表示されるようになります。
以上でプレビュー機能の完成です!
バリデーションエラー時の画像の保持方法
ここでは、キャッシュ機能を使用してバリデーションの失敗時
でもアップロードした画像ファイルのデータをもとに画像を表示する方法を説明しています。
- if f.object.profile_image?
= image_tag f.object.profile_image.to_s, id: 'profile_img_prev', class: 'user-information__img'
.form-group.mt-2
= f.check_box :remove_profile_image, class: 'mr-2' # 削除機能
= f.label :remove_profile_image # ラベル
= f.hidden_field :profile_image_cache # キャッシュ機能
キャッシュ機能を使用するためには下記の2点を記述する
必要があります!
-
「(カラム名)_cache」
という名前のhidden_fieldの追加 -
ストロングパラメーターに
「(カラム名)_cache」
の記述
そうすることで、f.object.profile_image.to_s
でアップロードした画像ファイルのデータを取得することができ、バリデーション失敗時でも画像を表示することができます。
最後に
今回プレビュー機能を実装してみての感想ですが、「javascript、、ムズい」です笑
でも記事にすることで再度実装内容を見直すことで多少は理解できたのかなと感じています!
今後も色んなことに挑戦しながら知識を付けていきたいと思います。
間違っている箇所や分かりづらい箇所が多々あるかと思います。
その際は、気軽にコメントいただけれると幸いです。
最後までご覧いただき、ありがとうございました!