Vue.js の フォーム とか tailwindcss とか Vue Router とか コンポーネント とか

この記事は、[静岡 Advent Calendar 2019 ](https://qiita.com/advent-calendar/2019/shizuoka)の25日目の記事です。


## はじめに
静岡の勉強会事情等々、皆様がいろいろと書いてくださっているので、自分はそちらの内容はまたの機会にすることにして、当初、Laravel を用いてテストについて何か書こうかなと思っておりました。

が、ちょっとばかり路線変更しまして、Vue.js の フォーム を中心に時間が許す限り何か思うがままに書こうかなと思います。
テストにつきましては、また別の機会にでも書きます。sqlite を使ってやるよくあるやつです。大した内容ではございません。
それから、次点として当初考えておりました、Laravel で過去に作った簡単なプロジェクトを Symfony（完全未経験） で書き直す。というものも考えてはおりましたが、風邪で昨日まで１週間寝込むという愚行を犯した自分には難しかったです。（今のところ娘に移っていないようなのが救いだったり...）

うだうだと書きましたが、それでは、早速本題に入りますよ。

今回は下記スクリーンショットのような form を想定しております。

<img width="1234" alt="スクリーンショット 2019-12-24 16.44.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128347/02736591-75d1-6fed-1da2-db9b9918f2a6.png">

環境につきましては、バックエンドに Laravel （今回あまり関係ございませんが、ディレクトリ名とか Vue CLI のものとは異なります）を、フロントエンドに Vue.js / tailwindcss （Laravel Mixを使用）を使用いたします。

tailwindcss?? なんじゃそりゃ？ という方は、[こちら](https://medium.com/fabric-tokyo-engineering/nuxt-js-tailwind-css-%E3%81%A7%E7%88%86%E9%80%9F%E3%82%B3%E3%83%BC%E3%83%9D%E3%83%AC%E3%83%BC%E3%83%88%E3%82%B5%E3%82%A4%E3%83%88%E4%BD%9C%E6%88%90-da2564d73f90)をご参照くださいませ。

CSSのクラスとして提供しているだけであり、個人的には、UI系のフレームワークより使い勝手がいいかなと思っています。作者さんは[Adam Wathan](https://github.com/adamwathan)です。
まぁ、Laravel / Vue 関連クラスターの著名な方なので、なんとなく安心感もありますね笑

何はともあれ、まずはセットアップから〜


## Vue.js と tailwindcss のセットアップ

それでは、最初に Vue.js と tailwindcss の下準備とセットアップでもしましょうか。

どこか好きな ディレクトリ に新規で Laravel プロジェクト を作りましょう。

```terminal
$ composer create-project laravel/laravel プロジェクト名 "バージョン指定.*"
Application key XXXXXXXXXうにゃうにゃでます set successfully.
```

set successfully. と出ていたらインストールが完了です。インストールが終わりましたら、今作成したプロジェクトのディレクトリへ移動しておきましょう。
また、エディタでもプロジェクトを開いておきます。

下記のコマンドを打って下準備をはじめましょう。

```terminal
$ php artisan preset none
```

これで package.json の構成が最小限のものに変わったかと思います。
続きまして、今日の本題 Vue.js を入れていきましょう。

```terminal
$ php artisan preset vue
```

これで、package.json でも ```"vue": "^2.5.7",```な感じに追加されていることが確認できるかと思います。

### npm or yarn で [Vue Router](https://router.vuejs.org/ja/) と [tailwindcss](https://tailwindcss.com/) を入れる

```terminal
## Using npm
npm install vue-router tailwindcss --save-dev

## Using Yarn
yarn add vue-router tailwindcss --save-dev
```

package.json に、下記のものが追加されていればOKです。

```JavaScript:package.json
"devDependencies": {
    "tailwindcss": "^1.1.4",
    "vue": "^2.5.7",       
    "vue-router": "^3.1.3"
},
```

あともう少しです。

### CSS に tailwindcss を追加します
resources/assets/sass/app.scss に下記の内容を書きます。


```scss:app.scss

@tailwind base;

@tailwind components;

@tailwind utilities;
```

tailwindcssは、ビルド時にこれらのディレクティブを、生成されたすべてのCSSと交換します。

### tailwindcss 構成ファイルを作成します

下記のコマンドを打ち込み、tailwind.config.jsを作成します。

```terminal
npx tailwind init
```
これにより、tailwind.config.js が、プロジェクトのルートディレクトリの直下に作成され、最小限のファイルが作成されます。tailwindcssに何か追加したいときに追加していくものですね。
詳細は[こちら](https://tailwindcss.com/docs/configuration/)。


### webpack.mix.js を修正する （Laravel5.5 の場合）

```JavaScript:webpack.mix.js
let mix = require('laravel-mix');
let tailwindcss = require('tailwindcss')

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/assets/js/app.js', 'public/js')
   .sass('resources/assets/sass/app.scss', 'public/css')
   .options({
       processCssUrls: false,
       postCss: [ tailwindcss('./tailwind.config.js') ],
});

```

###  webpack.mix.js を修正する。（Laravel6.0 の場合）

```JavaScript:webpack.mix.js
const mix = require('laravel-mix');
const tailwindcss = require('tailwindcss')

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css')
   .options({
       processCssUrls: false,
       postCss: [ tailwindcss('./tailwind.config.js') ],
});

```

### npm or yarn で ビルド する

```terminal
## Using npm
npm run watch

## Using yarn
yarn run watch
```

ちょっと長かったですね、あとちょっとです。

resources/views/layouts/app.blade.php の bladeテンプレートの、```<div id="app"></div>```の中を、下記のように変更します。

```php:resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
        <div class="h-screen">
            @yield('content')
        </div>
    </div>
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}"></script>
</body>
</html>
```

それから、resources/views/home.blade.php も、下記のように変更しましょう。
resources/assets/js/components/App.vue を読み込むように記載しています。

```php:resources/views/home.blade.php
@extends('layouts.app')

@section('content')
    <App></App>
@endsection
```

続きまして、resources/assets/js/components/App.vue も下記のように書きます。
ポイントは、```<router-link to="/contacts/create">```の箇所です。
/contacts/create のURLに遷移するように設定します。 （ Vue Router用の設定 ）


```vue:resources/assets/js/components/App.vue
<template>
    <div>
        <router-link to="/contacts/create">
            <p>新規登録</p>
        </router-link>
    </div>
</template>
```

ここまで出来たので、冒頭に導入した Vue Router を使ってみます。
resources/assets/js/app.js と resources/assets/js/router.js を、それぞれ開きます。

まずは、resources/assets/js/app.js から修正していきますね。


下記のようにしておきましょう。

```JavaScript:resources/assets/js/app.js
import Vue from 'vue';
import router from "./router";
import App from "./components/App";

const app = new Vue({
    el: '#app',
    components: {
        App
    },
    router
});
```

続いて、resources/assets/js/router.js では、ヒストリーモードを指定し、URLが、/contacts/create のときに、ContactsCreate のコンポーネントが表示されるようにします。

```JavaScript:resources/assets/js/router.js

import Vue from 'vue';
import VueRouter from 'vue-router';
import ContactsCreate from "./views/ContactsCreate";

Vue.use(VueRouter);

export default new VueRouter({
    routes: [
        { path: '/contacts/create', name: 'ContactsCreate', component: ContactsCreate }
    ],
    mode: 'history'
});
```

それでは、下準備が終わったところで、

まずは、ファイルを２つ用意しましょう。それぞれ、views/ContactsCreate.vue と、components/InputField.vue を作ります。

views/ContactsCreate.vue は、先ほど resources/assets/js/router.js で 指定したコンポーネントのファイルになりますね。


ContactsCreate.vue から作業を進めていきます。
一旦、props を用いまして、親コンポーネントであります ContactsCreate.vue から、
子コンポーネントの components/InputField.vue へ、
v-bind を 使って、label や placeholder の値を受け渡すところまで書いてみます。


```JavaScript:views/ContactsCreate.vue
<template>
    <div>
        <form>
            <input-field name="name" label="お名前" placeholder="例：山田ルイ53世"/>
            <input-field name="email" label="メールアドレス" placeholder="例：test@test.jp"/>
            <input-field name="company" label="組織名" placeholder="例：藤原カンパニー" />
            <input-field name="birthday" label="生年月日" placeholder="例：MM/DD/YYYY"/>
            <div class="flex justify-end relative pb-4">
                <button class="py-2 px-4 text-blue-500 border border-blue-500 mr-5 hover:bg-gray-100 hover:border-blue-300 rounded">キャンセル</button>
                <button class="bg-blue-500 border border-blue-500 py-2 px-4 text-white hover:bg-blue-400 mr-1 rounded">新規登録</button>
            </div>
        </form>
    </div>
</template>

<script>
    import InputField from "../components/InputField";
    export default {
        name: "ContactsCreate",
        components: {
            InputField,
        },
        data() {
            return {
                form: {
                    'name': '',
                    'email': '',
                    'company': '',
                    'birthday': ''
                }
            }
        }
    }
</script>

<style scoped>

</style>
```

続きまして、components/InputField.vue です。
props に、'name', 'label', 'placeholder' を指定しております。

```JavaScript:components/InputField.vue
<template>
    <div class="relative pb-4">
        <label :for="name" class="text-blue-500 pt-2 uppercase text-xs font-bold absolute">{{ label }}</label>
        <input :id="name" type="text" class="pt-8 w-full text-gray-900 border-b pb-2 focus:outline-none focus:border-blue-400" :placeholder="placeholder">
    </div>
</template>

<script>
    export default {
        name: "InputField",
        props: [
            'name', 'label', 'placeholder'
        ],
        data() {
            return {
                value: ''
            }
        }
    }
</script>

<style scoped>

</style>
```

tailwindcss の部分も書いておりますので、ちょっとごちゃごちゃと見難い部分があるかと思いますが、
これで、name や placeholder、label に値が受け渡されたのが確認できるかと思います。

次は、今やったこととは反対に、子コンポーネントから親コンポーネントへデータを受け渡してみましょう。

```JavaScript:components/InputField.vue
// <input>タグに @input="updateField()" を追記
<template>
    <div class="relative pb-4">
        <label :for="name" class="text-blue-500 pt-2 uppercase text-xs font-bold absolute">{{ label }}</label>
        <input :id="name" type="text" class="pt-8 w-full text-gray-900 border-b pb-2 focus:outline-none focus:border-blue-400" :placeholder="placeholder" v-model="value" @input="updateField()">
    </div>
</template>

<script>
    export default {
        name: "InputField",
        props: [
            'name', 'label', 'placeholder'
        ],
        data() {
            return {
                value: ''
            }
        },
// 以下の methods:{} 部分を追加
        methods: {
            updateField() {
                this.$emit('update:field', this.value);
            }
        }
    }
</script>
```

最後に、views/ContactsCreate.vue に ```@update:field="form.name = $event"``` 等を```<input-field/>```の中に書いて終わりです。```<input-field/>```のコンポーネントが4つある感じになりますね。

```JavaScript:views/ContactsCreate.vue
<template>
    <div>
        <form>
            <input-field name="name" label="お名前" placeholder="例：山田ルイ53世" @update:field="form.name = $event"/>
            <input-field name="email" label="メールアドレス" placeholder="例：test@test.jp" @update:field="form.email = $event"/>
            <input-field name="company" label="組織名" placeholder="例：藤原カンパニー" @update:field="form.company = $event"/>
            <input-field name="birthday" label="生年月日" placeholder="例：MM/DD/YYYY" @update:field="form.birthday = $event"/>
            <div class="flex justify-end relative pb-4">
                <button class="py-2 px-4 text-blue-500 border border-blue-500 mr-5 hover:bg-gray-100 hover:border-blue-300 rounded">キャンセル</button>
                <button class="bg-blue-500 border border-blue-500 py-2 px-4 text-white hover:bg-blue-400 mr-1 rounded">新規登録</button>
            </div>
        </form>
    </div>
</template>

<script>
    import InputField from "../components/InputField";
    export default {
        name: "ContactsCreate",
        components: {
            InputField,
        },
        data() {
            return {
                form: {
                    'name': '',
                    'email': '',
                    'company': '',
                    'birthday': ''
                }
            }
        }
    }
</script>

<style scoped>

</style>
```

いかがでしたでしょうか？  いろいろと足らない部分は多々ございますが、一旦ここで筆を置かせていただき、自分の担当であります、洗濯に移ります。明日も朝から洗濯するぞ！

tailwindcss は、一見カオティックな印象ですが、慣れるとそうでもないようなところがなかなか面白いですね！



それでは皆様、

Merry Christmas and Happy New year

良いお年を〜