1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LaravelでUIkitのUploadを使う

Last updated at Posted at 2020-06-20

#Laravelのファイルアップロード
Laravelのファイルアップロードでは、CSRF保護のためにCSRF「トークン」をフォームに含めて送信を行う必要があります。CSS,JSフレームワークのUIkitにはアップロードコンポーネントが用意されていますが、そのまま使うとCSRF「トークン」が用意されていないため419エラーが出てしまします。

#環境

  • Laravel 6
  • UIkit 3

#bladeテンプレート
今回はDrop areaを使います。
drop_area.png

**.blade.php
<div class="js-upload uk-placeholder uk-text-center">
    <span uk-icon="icon: cloud-upload"></span>
    <span class="uk-text-middle">Attach binaries by dropping them here or</span>
    <div uk-form-custom>
        @csrf //CSRFトークン生成のBladeディレクティブを追加
        <input type="file" multiple>
        <span class="uk-link">selecting one</span>
    </div>
</div>

<progress id="js-progressbar" class="uk-progress" value="0" max="100" hidden></progress>

</div>

#Script
JavaScriptはblade内に<script></script>でおいても別ファイルにして読み込んでもいいです。解説はソースの後!

script(js)
var bar = document.getElementById('js-progressbar');

UIkit.upload('.js-upload', {

    csrf_token: $('meta[name="csrf-token"]').attr('content'), //csrfトークンを取得
    name: 'thumbnail', //name指定はここでやります。HTMLのinputには書きません。
    url: '', //送信先のurl、Controllerにファイルを渡します。
    multiple: true,

    beforeSend: function () {
        console.log('beforeSend', arguments);
    },
    beforeAll: function () {
        console.log('beforeAll', arguments);
    },
    load: function () {
        console.log('load', arguments);
    },
    error: function () {
        console.log('error', arguments);
    },
    complete: function () {
        console.log('complete', arguments);
    },

    loadStart: function (e) {
        console.log('loadStart', arguments);

        bar.removeAttribute('hidden');
        bar.max = e.total;
        bar.value = e.loaded;
    },

    progress: function (e) {
        console.log('progress', arguments);

        bar.max = e.total;
        bar.value = e.loaded;
    },

    loadEnd: function (e) {
        console.log('loadEnd', arguments);

        bar.max = e.total;
        bar.value = e.loaded;
    },

    completeAll: function () {
        console.log('completeAll', arguments);

        setTimeout(function () {
            bar.setAttribute('hidden', 'hidden');
        }, 1000);

        alert('アップロードが完了しました');
    }

});

UIkitでは各コンポーネントをカスタムできるようにJavaScript compornentが用意されています。

UIkit_component
UIkit.upload(element, options);

Uploadはこれの機能がやたらと豊富なんですね。オプションで値を送ってやることで色々とカスタマイズできます。ただ、csrfトークンは送れないんですね...
だから渋々ですがcsrfトークンを送って、受け取れるようにuikit.jsを書き換えちゃいます。とりあえず、optionsとしてscrf_tokenを勝手に作ってcsrfトークンを取得してセットしましょう。

#uikit.js
やたら長いですが、書き足すところはちょっとなのでご容赦を...

uikit.js
//11998行目くらいです。
var upload = {

        props: {
            allow: String,
            clsDragover: String,
            concurrent: Number,
            maxSize: Number,
            method: String,
            mime: String,
            msgInvalidMime: String,
            msgInvalidName: String,
            msgInvalidSize: String,
            multiple: Boolean,
            name: String,
            params: Object,
            type: String,
            url: String
        },

        data: {
            allow: false,
            clsDragover: 'uk-dragover',
            concurrent: 1,
            maxSize: 0,
            method: 'POST',
            mime: false,
            msgInvalidMime: 'Invalid File Type: %s',
            msgInvalidName: 'Invalid File Name: %s',
            msgInvalidSize: 'Invalid File Size: %s Kilobytes Max',
            multiple: false,
            name: 'files[]',
            params: {},
            type: '',
            url: '',
            abort: noop,
            beforeAll: noop,
            beforeSend: noop,
            complete: noop,
            completeAll: noop,
            error: noop,
            fail: noop,
            load: noop,
            loadEnd: noop,
            loadStart: noop,
            progress: noop,
            csrf_token: ''//送ったcsrf_tokenを受け取れるようにする。
        },

        events: {

            change: function(e) {

                if (!matches(e.target, 'input[type="file"]')) {
                    return;
                }

                e.preventDefault();

                if (e.target.files) {
                    this.upload(e.target.files);
                }

                e.target.value = '';
            },

            drop: function(e) {
                stop(e);

                var transfer = e.dataTransfer;

                if (!transfer || !transfer.files) {
                    return;
                }

                removeClass(this.$el, this.clsDragover);

                this.upload(transfer.files);
            },

            dragenter: function(e) {
                stop(e);
            },

            dragover: function(e) {
                stop(e);
                addClass(this.$el, this.clsDragover);
            },

            dragleave: function(e) {
                stop(e);
                removeClass(this.$el, this.clsDragover);
            }

        },

        methods: {

            upload: function(files) {
                var this$1 = this;


                if (!files.length) {
                    return;
                }

                trigger(this.$el, 'upload', [files]);

                for (var i = 0; i < files.length; i++) {

                    if (this.maxSize && this.maxSize * 1000 < files[i].size) {
                        this.fail(this.msgInvalidSize.replace('%s', this.maxSize));
                        return;
                    }

                    if (this.allow && !match$1(this.allow, files[i].name)) {
                        this.fail(this.msgInvalidName.replace('%s', this.allow));
                        return;
                    }

                    if (this.mime && !match$1(this.mime, files[i].type)) {
                        this.fail(this.msgInvalidMime.replace('%s', this.mime));
                        return;
                    }

                }

                if (!this.multiple) {
                    files = [files[0]];
                }

                this.beforeAll(this, files);

                var chunks = chunk(files, this.concurrent);
                var upload = function (files) {

                    var data = new FormData();

                    files.forEach(function (file) { return data.append(this$1.name, file); });

                    for (var key in this$1.params) {
                        data.append(key, this$1.params[key]);
                    }

                    ajax(this$1.url, {
                        //ここから
                        headers: {
                            'X-CSRF-TOKEN': this$1.csrf_token,
                        }, //ここまで追加
                        data: data,
                        method: this$1.method,
                        responseType: this$1.type,
                        beforeSend: function (env) {

                            var xhr = env.xhr;
                            xhr.upload && on(xhr.upload, 'progress', this$1.progress);
                            ['loadStart', 'load', 'loadEnd', 'abort'].forEach(function (type) { return on(xhr, type.toLowerCase(), this$1[type]); }
                            );

                            this$1.beforeSend(env);

                        }
                    }).then(
                        function (xhr) {

                            this$1.complete(xhr);

                            if (chunks.length) {
                                upload(chunks.shift());
                            } else {
                                this$1.completeAll(xhr);
                            }

                        },
                        function (e) { return this$1.error(e); }
                    );

                };

                upload(chunks.shift());

            }

        }

    };

##ポイント1
まずは、optionsとして送ったcsrf_tokenの受け皿を作るところ。

data: {
            allow: false,
            // ~~
            progress: noop,
            csrf_token: ''//送ったcsrf_tokenを受け取れるようにする。
        },

##ポイント2
ajaxを使っているので、ajaxに対応したcsrf保護を行います。headersを追加して、X-CSRF-TOKENで値を渡しましょう。公式ドキュメント

ajax(this$1.url, {
    //ここから
    headers: {
        'X-CSRF-TOKEN': this$1.csrf_token,
    }, //ここまで追加
    data: data,
    method: this$1.method,
    responseType: this$1.type,
    beforeSend: function (env) {
        // ~~
    }
}).then(
    // ~~
);

これで、Controllerに値を渡せるようになります。

#おしまい
Laravelを使って趣味でWebアプリを作って1ヶ月半が経ちました。CSSとJSは自分であまり書きたくないのでフレームワーク何使おうかと色々考えて、なんとなくBootstrapはやだなあと思っていたところにUIkitというフレームワークが刺さりました。シンプルですがめちゃくちゃパワフルで楽しく使っています。ただ情報が少ないんですね... 日本語でのUIkit紹介記事はそこそこあるんですが、実際使って困ったことを共有している人はほとんどいない。そもそもユーザーが少ない可能性は大いにありますが、とりあえず需要あるか実験的に記事投稿していこうかと思います。

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?