Active Storageで追加した画像をjavascriptを使ってプレビューする方法

# はじめに
active Storageを使って保存する画像を保存前に表示させて確認したいと思って調べたもののまとめです。

# 前提
すでに画像保存機能は出来上がっている

# javascriptファイルの追加
app/javascript/packsの中にプレビュー用のファイルを追加します。今回、名前はpreview.jsとします。

# preview.jsの読み込み
前述で追加したpreview.jsをpacks内application.jsで読み込みます。
app/javascript/packs/application.js

```js
require('./preview')
```

# 読み込まれたときに動作する関数を定義
まずは動作ようの記述を。
app/javascript/packs/preview.js

```js
document.addEventListener('DOMContentLoaded', function(){
});
```
# 画像を表示するビューファイルに画像表示スペースをつくる

app/views/messages/_form.html.erb

```erb
<div id="image-list"></div>
```

# HTMLの要素を、JavaScript側で取得
続いて、今記述した画像を表示するためのHTMLの要素を、JavaScript側で取得します。
app/javascript/packs/preview.js

```js
document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');
});
```
# input要素で値の変化が起こったときに呼び出される関数を定義


```js
document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');
  document.getElementById('message_image').addEventListener('change', function(e){
    console.log(222);
  });
});
```
念の為ファイルを選択する」をクリックして適当な画像を選択し、以下のように２２２と表示できているかを確認しておきましょう。
# inputの中にある画像を取得
発火したイベントeの中の、targetの中の、filesという配列に格納されています。

次に取得した画像の情報は、変数fileとして宣言します。
app/javascript/packs/preview.js

```js
document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');

  document.getElementById('message_image').addEventListener('change', function(e){
    const file = e.target.files[0];
  });
});
```

# URLオブジェクトのcreateObjectURLメソッドを呼び出し、変数fileを引数として渡す
次はcreateObjectURLメソッドを引数として渡します。
app/javascript/packs/preview.js

```js
document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');

  document.getElementById('message_image').addEventListener('change', function(e){
    const file = e.target.files[0];
    const blob = window.URL.createObjectURL(file);
  });
});
```

# 実際に画像を表示できるようにする。
preview.jsを以下のように編集して画像を表示します。

```js
document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');

  document.getElementById('message_image').addEventListener('change', function(e){
    const file = e.target.files[0];
    const blob = window.URL.createObjectURL(file);

    // 画像を表示するためのdiv要素を生成
    const imageElement = document.createElement('div');

    // 表示する画像を生成
    const blobImage = document.createElement('img');
  });
});
```
# 画像URLをimg要素のsrc属性に設定
setAttribute()というdocumentオブジェクトを用いて、画像情報をsrc属性に指定します。
app/javascript/packs/preview.js

```js
document.addEventListener('DOMContentLoaded', function(){
const ImageList = document.getElementById('image-list');

  document.getElementById('message_image').addEventListener('change', function(e){
    const file = e.target.files[0];
    const blob = window.URL.createObjectURL(file);

    // 画像を表示するためのdiv要素を生成
    const imageElement = document.createElement('div');

    // 表示する画像を生成
    const blobImage = document.createElement('img');
    blobImage.setAttribute('src', blob);
  });
});
```

# appendChild()
指定した親要素の中に要素を追加するappendChildメソッドを使って、HTML要素を追加します。
app/javascript/packs/preview.js

```js
document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');

  document.getElementById('message_image').addEventListener('change', function(e){
    const file = e.target.files[0];
    const blob = window.URL.createObjectURL(file);

    // 画像を表示するためのdiv要素を生成
    const imageElement = document.createElement('div');

    // 表示する画像を生成
    const blobImage = document.createElement('img');
    blobImage.setAttribute('src', blob);

  // 生成したHTMLの要素をブラウザに表示させる
    imageElement.appendChild(blobImage);
    ImageList.appendChild(imageElement);
  });
});
```

# 条件分岐でトップページのエラーを解決
このままではpreview.jsで指定している要素が、新規投稿ページや編集ページにだけ存在し、TOP画面などでは表示されないため、エラーが発生します。
そのため、下記のようにして条件分岐してエラーを起こさないようにします。
app/javascript/packs/preview.js

```js
if (document.URL.match( /new/ ) || document.URL.match( /edit/ )) {
  document.addEventListener('DOMContentLoaded', function(){
    const ImageList = document.getElementById('image-list');

    document.getElementById('message_image').addEventListener('change', function(e){
      const file = e.target.files[0];
      const blob = window.URL.createObjectURL(file);

      // 画像を表示するためのdiv要素を生成
      const imageElement = document.createElement('div');

      // 表示する画像を生成
      const blobImage = document.createElement('img');
      blobImage.setAttribute('src', blob);

      // 生成したHTMLの要素をブラウザに表示させる
      imageElement.appendChild(blobImage);
      ImageList.appendChild(imageElement);
    });
  });
}
```

# 表示画像を編集可能にする。
最後に既存のプレビューを削除した後に新しい画像のプレビューを表示することで画像を編集可能にして起きます。

```js
if (document.URL.match( /new/ ) || document.URL.match( /edit/ )) {
  document.addEventListener('DOMContentLoaded', function(){
    const ImageList = document.getElementById('image-list');

    document.getElementById('message_image').addEventListener('change', function(e){
      // 画像が表示されている場合のみ、すでに存在している画像を削除する
      const imageContent = document.querySelector('img');
      if (imageContent){
        imageContent.remove();
      }
      const file = e.target.files[0];
      const blob = window.URL.createObjectURL(file);

      const imageElement = document.createElement('div');

      const blobImage = document.createElement('img');
      blobImage.setAttribute('src', blob);

      imageElement.appendChild(blobImage);
      ImageList.appendChild(imageElement);
    });
  });
}
```

# 完成
以上で完成です🎊