Laravel5.5で個別に選択した複数の画像をアップロードする処理

# 画像アップロード
以前作成した処理は、画像1つのプレビューとアップロードでした。
今回複数扱いたいな〜と思ったのでふわっと実装してみます。

# やりたいことは
1つのフォームから複数の画像をアップロードしたい。

やってることはこちらの記事とほぼ同じです。

- [Laravel 5.5で複数の画像をアップロードする方法 - Qiita](https://qiita.com/netfish/items/ef01cdb5f58742563e87)

ただこれだとmultiple属性を付けたinputで選択した複数画像を選ぶことになります。
例題だと商品の画像を登録する場合ですけど、作業効率とか気にしない普通のユーザーだと**複数のディレクトリにアップロードしたい画像が散らばっている**ことも考えられますよね。
まあその是非はともかく。
そういうわけで、multipleではなく1枚1枚画像を選択していく方法を考えます。

挙動としてはヤフオクみたいな感じ。
UIは多分違いますけど。

というわけで複数画像の選択・プレビュー・アップロードの方法を実装します。


# 実践
## 挫折した方法
最初、multipleをアリにして、複数選択した画像をスタックさせて5枚まで選択できる、みたいなことをしようとしました。
javascript上でファイル情報をグローバルな配列に保存して、最大5枚までプレビューする……みたいな。

- 最初に1枚選択。
- 次に2枚をいっぺんに選択。
- 画面に3枚の画像が表示される。
- submitすると、その3枚の画像がアップロードされる。

みたいな感じです。
で、プレビューするところまではできたんですが。

**そのファイル情報をphpに渡す方法がわからず断念しました。**

普通のinputではただのstringですからFileオブジェクトは渡せません。
じゃあtype=fileのinputをjavascriptで書き換えて……と思ったんですが、**セキュリティ的にいじくれない**んだそうで。

画像をData URIに変換すればいけそうな気はします。


## 実装した方法
画像を選択するたびに`<input tyle="file">`を追加してみました。
名前も配列っぽくしてみました。
でも削除できることを考えると、単純にカウントアップというのもなんか違う気がします。
なので（？）ランダム文字列にしています。

あとから考えると特に意味は無いです。
必要な数だけinput作ればいいような気もします。

```php:画面側
    <script>
        // 画像フィールド追加
        function addTourImageDivChild() {
            divParent = document.getElementsByName('hogeImagesDiv')[0];

            divChild = document.createElement('div');
            divChild.setAttribute('name', 'hogeImageDiv');

            div = document.createElement('div');
            divChild.appendChild(div);

            label = document.createElement('label');
            span = document.createElement('span');
            span.setAttribute("class", "btn btn-info");

            inputFile = document.createElement('input');
            inputFile.setAttribute("name", "hogeImage["+Math.random().toString(36).slice(-8)+"]");
            inputFile.setAttribute("onchange", "displayImage(this);");
            inputFile.setAttribute("type", "file");
            inputFile.setAttribute("style", "display:none;");
            inputFile.setAttribute("accept", "image/*");
            span.appendChild(inputFile);
            span.innerHTML += "アップロード";
            label.appendChild(span);
            divChild.appendChild(label);

            divParent.appendChild(divChild);
        }
        // 画像プレビュー
        function displayImage(obj)
        {
            file = obj.files[0];
            div = obj;
            while (div.tagName != 'DIV') div = div.parentNode;

            div = div.children[0];

            if (file.size > 500 * 1024) {
                alert('登録できるファイルサイズの上限は500KBです');
                deleteImage(obj);
            } else {
                divChild = document.createElement('div');
                divChild.setAttribute("onclick", "if (confirm('この画像を削除しますか？')) deleteImage(this);");
                divChild.setAttribute("onMouseOver", "imgMouseOver(this);");
                divChild.setAttribute("onMouseOut", "imgMouseOut(this);");
                divChild.style.maxHeight = "300px";
                divChild.style.maxWidth = "50%";

                img = document.createElement('img');
                img.setAttribute("src", window.URL.createObjectURL(file));
                img.style.maxHeight = "250px";
                img.style.maxWidth = "100%";

                divChild.appendChild(img);

                div.nextElementSibling.style.display = "none";
                div.appendChild(divChild);
            }
            addTourImageDivChild();
        }
        // 要素の削除
        function deleteImage(obj)
        {
            targetName = 'hogeImageDiv';
            parent = obj;

            while (parent.getAttribute('name') != targetName) parent = parent.parentNode;

            if (parent.getAttribute('name') == targetName) parent.parentNode.removeChild(parent);
        }
        // マウス当てたときに灰色にボヤかす
        function imgMouseOver(t) {
            t.style.webkitFilter = "grayscale(100%) blur(1px)";
            t.style.filter = "grayscale(100%) blur(1px)";
        }
        function imgMouseOut(t) {
            t.style.webkitFilter = "grayscale(0) blur(0px)";
            t.style.filter = "grayscale(0) blur(0px)";
            t.style.webkitTransition = ".1s ease-in-out";
            t.style.transition = ".1s ease-in-out";
        }
　　　　　　　　　　　　　　　　// 最初のアップロードボタンを追加
        $('document').ready(function () {
            addTourImageDivChild();
        });

    </script>

    <form name="hogeForm" action="{{ url('s') }}" method="post" enctype="multipart/form-data">
        {{ csrf_field() }}
        {{ method_field('POST') }}
        <div class="form-group">
            <label for="Images">{{ __(' Images') }}</label>
            <div name="ImagesDiv"></div>
            <div>
                ※最大5枚まで、1枚の容量は500KB以内まで
            </div>
        </div>
        <input type="button" class="btn btn-success" onclick="if (confirm('登録します。よろしいですか？')) document.hogeForm.submit();" value="{{ __('Submit') }}" />
    </form>

```


コントローラー側はこんな感じで受け取れました。
特に画像を区別する必要がないなら、名前を変にいじらなくても`$request->file()`をforeachするだけでよさそうですね……。

```php:コントローラー側
        foreach ($request->file()['hogeImage'] as $index => $e) {
            $path = $e->store('upload');
            // photosメソッドにより、商品に紐付けられた画像を保存する
            $hoge->hogeImages()->create(['path'=> $path]);
        }

```

ちなみにcreateで指定する項目は`$fillable`で明示しておく必要があるみたいです。

- [Eloquentでcreateするときは割り当て許可が必要 - ふたりはララベル （Laravel PHP Framework）](http://laravel.hatenablog.com/entry/2013/10/24/005050)

関係なさそう……とか思って省くと、MassAssignmentException とやらで怒られます。


### 画像の順番？
キーがランダムなので、ここがちょっと気になります。
何度か試してみましたが、どうやら画面の表示順そのままで登録されるっぽいです。

たぶんこれ、編集して画像の順番を入れ替えて登録して……とか考えるときに問題になりますね。

とりあえずいまは見なかったことにしておこうと思います。

# とりあえず登録はできたんですが
表示はまあどうでもいいとして、編集はどうしましょう……ってなってます。

動的〜とか面白がってないで、最初から枠だけ用意してやればよかったような気がします。