LoginSignup
2
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-16

画像アップロード

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

やりたいことは

1つのフォームから複数の画像をアップロードしたい。

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

ただこれだと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作ればいいような気もします。

画面側
    <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するだけでよさそうですね……。

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

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

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

画像の順番?

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

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

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

とりあえず登録はできたんですが

表示はまあどうでもいいとして、編集はどうしましょう……ってなってます。

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

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3