画像をドラッグ＆ドロップでアップロードして並び替え

画像をドラッグ＆ドロップでアップロード
＆
画像をドラッグ＆ドロップで並び替える
方法をご紹介します！

# 用途
webサイトとかで、施設に画像を結びつけて、かつ、それをソート順で表示したいとかあるときに使えるよ。

ついでに画像も削除できます。
ついでに画像に説明文も登録できます。

![画像のドラッグ＆ドロップでアップロードと並び替え.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392006/3ef943ec-3660-2d80-7b44-d1b538fac12a.png)


# 参考サイト
https://codepen.io/malkafly/pen/gbVYZb
↑ほぼこれを丸パクリです。ありがとうございます！！！


# 使うプラグイン
* みんな大好き[DropzoneJS](https://www.dropzonejs.com/)
* jQuey UIのSortable
私は[ここ](https://www.npmjs.com/package/es-jquery-sortable)からyarnで追加しました


# コード
## html側
```html
<form method="post" action="/api/v1/facility-image/upload/" id="my-awesome-dropzone" class="image-upload-area dropzone">
    <input type="hidden" name="client_id" value="{{ $facility->id }}"></div>
</form>
    
<ul class="visualizacao sortable dropzone-previews"></ul>
<div class="preview" style="display:none;">
    <li>
        <div>
            <div class="dz-preview dz-file-preview">
                <img class="facility-image" data-dz-thumbnail />
                <input type="text" class="caption">
                <button type="button" class="btn btn-info caption-button" data-image-id="">説明文登録</button>
                <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
                <div class="dz-error-message"><span data-dz-errormessage></span></div>
            </div>
        </div>
    </li>
</div>
```

## javascript側
``` javascript
import Dropzone from 'dropzone'

require('es-jquery-sortable')

let myDropzone = {}

dropzoneSetting()

//画像の並び替え
$(document).ready(function(){
    let group = $('.sortable').sortable({
        group: 'sortable',
        onDrop: function($item, container, _super) {
            var data = group.sortable('serialize').get()
            _super($item, container)

            //DB上の並び順を更新する
            axios.post('/api/v1/facility-image/update-order/', {
                    data: data,
                })
                .catch((error) => {
                    swal('', '並び順の更新に失敗しました', 'error')
                    console.log(error)
                })
        }
    })
})

/**
 * dropzone.jsの処理
 * 
 */
async function dropzoneSetting() {
    Dropzone.autoDiscover = false

    //最大ファイルアップロードサイズ
    let maxFileSize = 3

    //企業ID
    let clientId = $('#client-id').data('clientId')

    myDropzone = new Dropzone('.image-upload-area', {
        url: '/api/v1/facility-image/upload/',
        params: {
            client_id: clientId,
        },
        dictDefaultMessage: 'ここにファイルをドラッグ＆ドロップ、または、クリックしてファイルを選択',
        dictRemoveFile: '',
        dictCancelUpload: '',
        dictFileTooBig: 'ファイルサイズは' + maxFileSize + 'MBまで',
        dictInvalidFileType: 'JPEG,PNGのみ可能',
        addRemoveLinks: true,
        maxFilesize: maxFileSize,
        acceptedFiles: 'image/*',
        parallelUploads: 1,
        uploadMultiple: false,
        previewsContainer: '.visualizacao', 
        previewTemplate : $('.preview').html(),
        renameFile: (file) => {
            let fileName = new Date().getTime() + '_' + file.size

            let extension = file.name.split('.').pop()
            return fileName + '.' + extension
        },
    })

    //アップロードに失敗した場合
    myDropzone.on('error', async function(file, errorMessage) {
        //ファイル数が多い場合
        if(errorMessage === '最大8ファイルまでしか添付できません') {
            myDropzone.removeFile(file)
            swal('', errorMessage, 'warning')
        } else if(errorMessage === 'セッションが切れました。もう一度ログインしてください。') {
            await swal('', 'セッションが切れました。もう一度ログインし直してください。', 'warning')
            location.replace(location.href)
        }
    })
    
    //画像を削除したときに、サーバー上のファイルも削除する
    myDropzone.on("removedfile", function(file) {
        let imageId = file.imageId

        //画像ID
        axios.get('/api/v1/facility-image/delete-by-image-id/' + imageId)
            .catch((error) => {
                console.log(error)
            })
    })

    //既存の画像が表示されたときに情報を追加する
    myDropzone.on("addedfile", function(file) {
        $('.dz-preview').each(function() {
            let imageId = $(this).children('.caption-button').data('imageId')

            //画像IDが設定されていない場合のみ追加する
            if(!imageId) {
                let imageId = file.imageId

                //今回追加した画像の場合は飛ばす
                if(!imageId) {
                    return
                }

                $(this).children('.caption-button').data('imageId', imageId)

                let caption = file.caption
                $(this).children('.caption').val(caption)

                $(this).parents('li').data('imageId', imageId)
            }
        })
    })

    //新しい画像がアップロードされたときに、画像IDを追加する
    myDropzone.on("success", function(file, response) {
        $('.dz-preview').each(function() {
            let existImageId = $(this).children('.caption-button').data('imageId')

            //画像IDが設定されていない場合のみ追加する
            if(!existImageId) {
                let imageId = response
                $(this).children('.caption-button').data('imageId', imageId)
                $(this).parents('li').data('imageId', imageId)
            }
        })
    })

    //すでに保存されている画像を表示する
    $('.exist-image').each(function() {
        let path = $(this).data('path')
        let info = {
            name: '',
            size: '',
            imageId: $(this).data('id'),
            caption: $(this).data('caption')
        }
        myDropzone.emit("addedfile", info)

        if(path) {
            myDropzone.emit("thumbnail", info, path)
        } else {
            let base64 = 'data:' + $(this).data('type') + ';base64,' + $(this).data('binary')
            myDropzone.emit("thumbnail", info, base64)
        }

        myDropzone.emit("complete", info)
    })

    //画像の説明文を登録
    $(document).on('click', '.caption-button', function() {
        let input = $(this).prev()

        axios.post('/api/v1/facility-image/save-caption/', {
                caption: input.val(),
                image_id: $(this).data('imageId'),
            })
            .then(() => {
                swal('', '説明文を登録しました', 'success')
            })
            .catch((error) => {
                swal('', '説明文の登録に失敗しました', 'error')
                console.log(error)
            })
    })
}
```

#まとめ
php側（私はLaravel使用しています）は、自分で実装してくださいね。
cssも自分で書いてね。

全然コードの説明してなくて、すみません。
いや、こういうの毎回やろうと思ったときに、過去のソースひっぱってくるの大変なので、Qiitaに置きました...