Laravel 6.x 【Laravel-mix】 input type="file" のデザインを Bootstrap 4 で変更 JaveScript(jQuery) でファイル名を表示させる方法

#制作環境

Windows 10
Laravel : 6.18.35
Laravel/ui : 1.0
Laravel-mix : 5.0.1
Bootstrap : 4.0.0
jQuery : 3.2
XAMPP
PHP : 7.4.3
Visual Studio Code

#はじめに

この記事はプログラミングをはじめたばかりの素人が、できたことをメモするのに利用しています。
内容には誤りがあるかもしれません。

あくまでデザインとファイル名の表示が目的のため、実際に送信(POST)できるようにはしてません。
最低限必要だと思う内容しか記載していないので、バリデーション等も直接必要ではないので割愛してます。

#はじめる前に

Laravelをインストールしておいて下さい。
Laravel-mixを使用することを前提にしています。

Bootstrap 4がインストールされていない方は、まずインストールを行ってください。
<font color="blue">インストールにはnpmを利用するので、Node.jsのインストールが先に必要です。</font>

Node.jsをインストールしていない場合は、下記よりダウンロードして先にインストールをお願いいたします。

Node.js
http://nodejs.org/

#Bootstrap 4のインストール

Laravelのスカフォールドを利用します。

<font color="red">**※以降のやり方はLaravelのバージョンによって、使用するコマンド等が違います。
 Laravelのリファレンスに沿ってインストールをお願いいたします。**</font>

Laravelのプロジェクトのディレクトリでターミナルを起動し、以下を実行する。

```PHP:Laravel6.x
composer require laravel/ui:^1.0 --dev
```

次に、以下を実行。

```PHP:Laravel6.x
php artisan ui bootstrap
```

次に、以下を実行。

```PHP:Laravel6.x
npm run install
```

最後に以下を実行。

```PHP:Laravel6.x
npm run dev
```

---

リファレンス
https://readouble.com/laravel/6.x/ja/frontend.html

#ビューの作成

ベースとなるビューを作成します。
resources>views 内に新しくinputfile.blade.phpを作成します。

```PHP:inputfile.blade.php
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">

    <title>ファイル選択</title>
</head>

<body>

    <h1>input file</h1>

    <form>
        <label>画像選択 <input type="file" name="img" accept=".png,.jpg,.jpeg,image/png,image/jpg"></label>
    </form>

</body>

</html>
```

実際に送信するのであれば、`<form>`に`action`や`enctype="multipart/form-data`を記述しないといけませんが、今回は見た目だけに重点を置いているので記述はしません。

#ルーティングの作成

今回送信や登録は行わないので、コントローラは使用しません。
指定のアドレスにアクセスされたら、viewを直接返すようにします。

routes 内のweb.phpを開き以下のように記述します。

```PHP:web.php
Route::get('/inputfile', function () {
    return view('inputfile');
});
```

#現状のデザイン

`/inputfile`にアクセスすると

![input.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/a6aa6cae-e552-3e55-4e9c-b08eadcd5650.jpeg)


画像選択の画面のみが表示されます。
ファイルを選択すると、

![coke.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/484374b3-96d8-6aa5-9463-0f544030c5c9.jpeg)

「選択されていません」の部分にファイル名が表示されます。 

このデザインをBootstrap 4を使い変更します。

#webpack.mix.jsの編集

webpack.mix.jsを開き内容を以下に変更します。

**<font color="blue">変更前</font>**

```JavaScript:webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css');
```

**<font color="red">変更後</font>**

```JavaScript:webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .version();
```

この記事の目的からすると、この記述はなくても全然いいのですが、laravel-mixの内容を見てもらうためにあえて触れてます。
この記述がなくても、今後の操作に影響はありません。

Laravel-mixですが、すごい大雑把に言うと特定の場所に置かれたJavaScriptファイルやcssファイル・Font AwesomeやBootstrap等をまとめて記述できるようにしてくれるものです。
※素人のイメージなので間違ってるかもしれませんが、そんなには遠くないと思いますwww。

使い方は次のビューの編集でやります。

##記述の内容①

```JavaScript:webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
```

上記は、`resources>js` 内にある`app.js`を`public/js`ディレクトリに出力するという意味です。
出力されると、`public/js` 内に同じ名前の`app.js`ファイルが作成されます。

`resources>js` 内にある`app.js`に必要なファイルを読み込むよう記述すると、1つにまとめて出力(トランスパイル)してくれます。
現在同じディレクトリには`bootstrap.js`しかないので、`app.js`を開くと先頭には

```JavaScript:app.js
require('./bootstrap');
```

しかありませんが、ここに追記するだけで、自身で作成したjsファイルもまとめて出力してくれます。
<font color="blue">ファイル名を表示するJavaScript(jQuery)を作成する時は、このLaravel-mixを使用します。</font>

**イメージ**
![mix.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/74081131-1364-4e0a-2c04-b49638f89032.jpeg)


**<font color="red">【注意事項】</font>**
<font color="red">内容を変更した時は必ずターミナルで以下を実行してください。</font>

```PHP
npm run dev
```

都度やるのが面倒な場合は、

```PHP
npm run watch-poll
```

これで変更があれば自動で更新してくれます。

##記述の内容②

```JavaScript:webpack.mix.js
.sass('resources/sass/app.scss', 'public/css')
```

これも内容は一緒でcssに関するものをまとめて出力してます。
<font color="blue">また、その過程でsass等新しい記述のcssを古いcssの記述に変換してくれてます。</font>

記述の内容③

```JavaScript:webpack.mix.js
.version();
```

これはキャッシュに残った古いJavaScriptを読み込ませないようにするための記述です。
これを記述するとidパラメーターが都度採番されます。

#ビューの編集

ビューに以下を追記します。

```PHP:inputfile.blade.php
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">

    <!-- Bootstrap ここから追記 -->
    <link href="{{ mix('css/app.css') }}" rel="stylesheet">
    <!-- ここまで追記 -->
    <title>ファイル選択</title>
</head>

<body>

    <h1>input file</h1>

    <form>
        <label>画像選択 <input type="file" name="img" accept=".png,.jpg,.jpeg,image/png,image/jpg"></label>
    </form>

    <!-- Bootstrap JavaScript ここから追記 -->
    <script src="{{ mix('js/app.js') }}"></script>
    <!-- ここまで追記 -->
</body>

</html>
```

2箇所追記してます。
この記述で、先ほどまとめたcssファイルやjsファイルを読み込んでいます。

cssは`<head>`の中に記述し、jsは`<body>`の終了タグの直前に記述します。

この状態で`/inputfile`にアクセスすると、フォントなどの見た目が少しかわるはずです。

**イメージ**
![boot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/c6284dfc-69ac-4e62-9630-0bf1e4a4bdd3.jpeg)

#デザインの変更①

Bootstrapで見た目を変更してみます。

`<form>`の中を以下のように変更してみてください。

```PHP:inputfile.blade.php
<form>
    <div class="form-group">
        <label for="img">画像選択</label>
        <input type="file" class="form-control-file" id="img" name="img" accept=".png,.jpg,.jpeg,image/png,image/jpg">
    </div>
</form>
```

**イメージ**
![bootbase.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/d4dda72e-3878-228e-a963-c4d10a58902f.jpeg)

見ての通り、これだとHTML標準のものとあまり変わりません。

---
Bootstrap 4 リファレンス
https://getbootstrap.jp/docs/4.3/components/forms/

#デザインの変更② カスタムファイル

カスタムファイルを使用して、もう少しBootstrapらしいデザインにしたいと思います。
`<form>`の中を以下のように変更してみてください。

```PHP:inputfile.blade.php
<form>
    <div class="input-group">
        <div class="input-group-prepend">
            <span class="input-group-text" id="inputGroupFileAddon">画像選択</span>
        </div>

        <div class="custom-file col-3">
            <input type="file" class="custom-file-input" id="inputGroupFile" name="img" accept=".png,.jpg,.jpeg,image/png,image/jpg" aria-describedby="inputGroupFileAddon">
            <label class="custom-file-label" for="inputGroupFile" data-browse="参照">ファイルを選択</label>
        </div>

    </div>
</form>
```

このままだと左上の端に表示され見にくいので、以下を更に追記します。

```PHP:inputfile.blade.php
<body>
<div class="container">

    <h1 class=" pt-5">input file</h1>


    <!-- 省略 -->


</div>

    <!-- Bootstrap JavaScript -->
    <script src="{{ mix('js/app.js') }}"></script>
</body>
```

現状のデザインを確認してみてください。

![boot2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/eff3dbdc-34ab-dd1a-12be-f895b2e2ad13.jpeg)

見た目はよくなりましたが、実際にファイルを選択しても、デザインを変更する前のようにファイル名が表示されません。
これでは選択されたかどうかがわかりづらいので、ファイル名が表示されるようにします。

#スクリプトの作成

`resources>js` 内に新しく`inputfile.js`を作成します。
作成したら、同じディレクトリにある`app.js`を開き、`require('./bootstrap');`の下に以下のように追記します。

```JavaScript:app.js
require('./inputfile');
```

#JavaScriptで記述

ファイル名の表示を行うスクリプトを、JavaScriptで作成します。
先ほど作成した、`inputfile.js`を開き以下のように記述してください。

```JavaScript:inputfile.js
// id="inputGroupFile"の要素の内容を取得し その要素でchangeイベントが発生したら関数を実行する
document.getElementById("inputGroupFile").addEventListener("change", function() {
    // 選択されたファイルの最初のファイルのデータを変数fileに代入する
    let file = this.files[0];
    // class="custom-file-label"の要素の内容を取得し その内容の最初のオブジェクトの文章をfile.nameで書き替える
    document.getElementsByClassName("custom-file-label")[0].innerHTML = file.name;
}, false);
```

**<font color="blue">filesで取得できる値</font>**

* name (ファイル名)
* size (Bytes)
* type (MIME Type)


##files[0]の記述について

<font color="blue">`input type="file"` は同時に複数のファイルをアップロードすることが可能なため、値を取り出す時には何番目にアップロードしたものか指定しないといけません。</font>

今回の場合は1つしかアップできないので、最初のファイルを指定するため[0]を記述する。
<font color="red">※最初は[0]で2つ目が[1]になるので注意!</font>

##getElementsByClassName()で取得できる要素の内容について

`files`と同じく、`getElementsByClassName`でも`getElementsByClassName("custom-file-label")[0]`で[0]を指定してます。
理由は<font color="red">取得される要素の内容が、配列ではなくオブジェクト型</font>だからです。

idと違い、classは同じ名前を何回でも使用できます。
そのため、複数の要素の内容が入るようにオブジェクト型になっています。
なので、innerHTMLで文章を変更する時は、何番目のオブジェクトの文章なのかを指定してあげないといけません。
この[0]が抜けるとうまくいきませんので気をつけてください。

#スクリプトをまとめる

`inputfile.js`の保存が完了したら、ターミナルを起動し以下を実行します。

```PHP
npm run dev または npm run watch-poll
```

操作が完了したら、`/inputfile`にアクセスして動作を確認してください。

**イメージ**
![file.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/a76e5250-54c2-7d2a-e505-5e055404b47c.jpeg)

上のように選択した画像ファイルの名前が表示されたらOKです。
ちなみにinputで指定してるので、pngかjpegしか選択できません。

#jQueryで記述

せっかくなので、jQueryで同じ記述を書いてみます。
やってることは一緒です。
<font color="blue">JavaScriptの記述はコメントアウトするか、削除してください。</font>

`inputfile.js`を開き以下のように記述してください。

```JavaScript:inputfile.js
$(function() {
    $('#inputGroupFile').on('change', function() {
        let file = $(this).prop('files')[0];
        $('.custom-file-label').text(file.name);
    });
});
```

jQueryの方がすっきりしてますね。

保存したら忘れず、ターミナルで

```PHP
npm run dev または npm run watch-poll
```

を実行してください。

操作が完了したら、`/inputfile`にアクセスして動作を確認してください。

**イメージ**
![jq.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/f90318f5-7268-f971-056b-87fbda589590.jpeg)

同じようにファイル名が表示されていればOKです。