[Re:ゼロから始めるVue生活]メモ

Vueで個人的に使えそうなものをメモとしてピックアップしていきます。
この記事の内容は全てではないので、1から学習したい方は公式ドキュメントを参照してください。

# VueをCDNで使う雛形

```vue:VueをCDNで使う雛形

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.17.1/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script>
<title>Vue App</title>
</head>
<body>
    <div id="app">
        {{ message }}
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                title: '',
                message: 'Hello Vue!'
            }
        });
    </script>
</body>
</html>
```

# リストレンダリング（画像表示あり）

```vue:リストレンダリング（画像表示あり）
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Vue App</title>
</head>
<body>
    <div id="app">
        <!-- 配列なし -->
        <img :src="imageSrc">
        0: 配列なし
        <!-- 配列あり -->
        <div v-for="(item, index) in items" v-bind:key="item.id">
            <img v-if="item.imageSrc" :src="item.imageSrc" :style="item.imageStyle">
            {{ item.id }}: {{ item.title }}
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                // 配列なし
                imageSrc: './img/ph1.jpg',
                // 配列
                items: [
                  {
                    id: 1,
                    title: '配列から表示',
                    imageSrc: './img/ph1.jpg',
                    imageStyle: 'width:30%;height:30%;object-fit:cover;',
                  },
                  {
                    id: 2,
                    title: '配列から表示',
                    imageSrc: './img/ph2.jpg',
                    imageStyle: 'width:10%;height:10%;object-fit:cover;',
                  },
                ],
            }
        });
    </script>
</body>
</html>
```

# 検索＆ソート

```vue:検索＆ソート
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Vue App</title>
</head>
<body>
    <div id="app">
        <input v-model.number="budget">円以下
        <p>{{ matched.length }}件表示中</p>
        <button v-on:click="order=!order">価格 ▼</button>
        <div v-for="item in limited" v-bind:key="item.id">
            {{ item.name }}: {{ item.price }}円
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                // 検索初期値
                budget: '',
                // 検索数
                limit: 10000000000000,
                // 検索リスト
                list: [
                    { id: 1, name: 'A商品', price: '500' },
                    { id: 2, name: 'B商品', price: '300' },
                    { id: 3, name: 'C商品', price: '200' },
                    { id: 4, name: 'D商品', price: '700' },
                    { id: 5, name: 'E商品', price: '100' },
                    { id: 6, name: 'F商品', price: '900' },
                ],
                // ソート初期値
                order: false,
            },
            computed: {
                matched: function() {
                    return this.list.filter(function(el) {
                        return el.price <= this.budget
                    }, this)
                },
                sorted: function() {
                    return _.orderBy(this.matched, 'price', this.order ? 'desc' : 'asc')
                },
                limited: function() {
                    return this.sorted.slice(0, this.limit)
                }
            }
        });
    </script>
</body>
</html>
```

# ゲッター・セッター

```vue:ゲッター・セッター
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Vue App</title>
</head>
<body>
    <div id="app">
        {{ fullName }}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                firstName: 'default',
                familyName: 'default',
            },
            computed: {
                fullName: {
                    get: function() {
                        // 最終的にこの部分が算出されて描画される
                        return this.firstName + ' - ' + this.familyName
                    },
                    set: function(newValue) {
                        let names = newValue.split(',')
                        // split関数 = 指定した記号で文字列を分割する
                        // ここではnewValue「hoge,fuga」を「,」で分割して「hoge」（names[0]）「fuga」（names[1]）に分かれる
                        this.firstName = names[0]
                        this.familyName = names[1]
                    }
                },
            }
        });
        // ここで書き換えるとset関数の処理がされ、get関数でreturnされる
        app.fullName = 'hoge,fuga'
    </script>
</body>
</html>
```

# トランジション雛形

```vue:トランジション雛形
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.17.1/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script>
<title>Vue App</title>
</head>
<body>
    <div id="app">
        <p><button @click="show=!show">切替</button></p>
        <transition>
            <div v-show="show">
                トランジション
            </div>
        </transition>
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                show: true
            },
        })
    </script>
    <style>
        .v-enter-active,
        .v-leave-active {
            transition: opacity 1s;
        }
        .v-enter,
        .v-leave-to {
            opacity: 0;
        }
    </style>
</body>
</html>
```

# リストトランジション

```vue:リストトランジション
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.17.1/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script>
<title>Vue App</title>
</head>
<body>
    <div id="app">
        <p><button @click="order=!order">切替</button></p>
        <transition-group class="list">
            <div v-for="item in sortedList" :key="item.id">
                {{ item.name }}: {{ item.price }}円
            </div>
        </transition-group>
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                order: false,
                list: [
                    { id: 1, name: '商品A', price: '100' },
                    { id: 2, name: '商品B', price: '500' },
                    { id: 3, name: '商品C', price: '2500' },
                    { id: 4, name: '商品D', price: '5200' },
                    { id: 5, name: '商品E', price: '100' },
                    { id: 6, name: '商品F', price: '11500' },
                ]
            },
            computed: {
                sortedList: function() {
                    return _.orderBy(this.list, 'price', this.order ? 'desc' : 'asc')
                }
            }
        });
    </script>
    <style>
        .v-move {
            transition: transform 1s;
        }
    </style>
</body>
</html>
```

# キーワード検索

```vue:キーワード検索
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.17.1/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script>
<title>Vue App</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="keyword">
        <ul>
            <li v-for="user in filteredUsers" :key="user.id">
                {{ user.name }}: {{ user.email }}
            </li>
        </ul>
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                keyword: '',
                users: [
                    { id: 1, name: '太郎', email: 'taro@example.com' },
                    { id: 2, name: '花子', email: 'hanako@example.jp' },
                    { id: 3, name: '鈴木', email: 'suzuki@example.com' },
                    { id: 4, name: '佐藤', email: 'sato@example.ne.jp' },
                ]
            },
            computed: {
                filteredUsers: function() {
                    let users = [];
                    for(let i in this.users) {
                        let user = this.users[i];
                        // indexOfは値が見つからなければ-1を返す
                        if(user.name.indexOf(this.keyword) !== -1 ||
                            user.email.indexOf(this.keyword) !== -1) {
                            // -1ではない要素（値が見つかった要素）を配列に追加（push）する
                            users.push(user);
                        }
                    }
                    return users;
                }
            }
        });
    </script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            color: #696969;
            background: #eee;
        }
        .contents {
            margin: 50px;
        }
    </style>
</body>
</html>
```

# スタッガリングリストトランジションを利用したキーワード検索
ふわっとした表示のUIになる
※tableだと「transition-group」はエラーで使えません。

```vue:スタッガリングリストトランジションを利用した検索
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.17.1/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script><!-- 追加 -->
<title>Vue App</title>
</head>
<body>
    <div id="app">
        <div class="contents">
            <input type="text" v-model="query">
            <transition-group
                class="list-item"
                name="staggered-fade"
                tag="table"
                v-bind:css="false"
                v-on:before-enter="beforeEnter"
                v-on:enter="enter"
                v-on:leave="leave"
            >
                <div
                    class="user"
                    v-for="(item, index) in computedList"
                    v-bind:key="item.id"
                    v-bind:data-index="index"
                >
                    {{ item.userName }}
                    {{ item.email }}
                </div>
            </transition-group>
        </div>
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                query: '',
                list: [
                    { id: '1', userName: '太郎', email: 'taro@example.com' },
                    { id: '2', userName: '花子', email: 'hanako@sample.com' },
                    { id: '3', userName: 'John', email: 'john@example.ne.jp' },
                    { id: '4', userName: 'Mike', email: 'mike@example.jp' },
                ]
            },
            computed: {
                computedList: function() {
                    let vm = this
                    return this.list.filter(function(item){
                        return item.userName.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1 || 
                               item.email.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
                    })
                }
            },
            methods: {
                beforeEnter: function(el) {
                    el.style.opacity = 0
                    el.style.height = 0
                },
                enter: function(el, done) {
                    let delay = el.dataset.index * 150
                    setTimeout(function() {
                        Velocity(
                            el,
                            { opacity: 1, height: '1.6em' },
                            { complete: done }
                        )
                    }, delay)
                },
                leave: function(el, done) {
                    let delay = el.dataset.index * 150
                    setTimeout(function() {
                        Velocity(
                            el,
                            { opacity: 0, height: 0 },
                            { complete: done }
                        )
                    }, delay)
                }
            }
        });
    </script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            color: #696969;
            background: #eee;
        }
        .contents {
            margin: 50px;
        }
    </style>
</body>
</html>
```

# phpの多重連想配列からVueへの受け渡し

```php:phpの多重連想配列からVueへの受け渡し
<?php
    $data = array(
        array(
            'Shop' => array( 'id' => '1', 'name' => 'A' ),
        ),
        array(
            'Shop' => array( 'id' => '2', 'name' => 'B' ),
        ),
    );
    $list_json = json_encode($data, JSON_UNESCAPED_UNICODE);
?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.17.1/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script>
<title>Vue App</title>
</head>
<body>
    <div id="app">
        <div v-for='items in list'>
            {{ items.Shop.id }}: {{ items.Shop.name }}
        </div>
    </div>
    <script>
        let list = JSON.parse('<?php echo $list_json; ?>');
        console.log(list);
        const app = new Vue({
            el: '#app',
            data: {
            list: list
          }
        });
    </script>
</body>
</html>
```

# Vue CLI × Firebase フォーム送信

```vue
<!-- components/Form.vue -->
<!-- ①router/index.js => ルーティング設定　② $ npm install bootstrap-vue  ③main.js => Bootstrapをインポート -->
<!--
④Firebase上で設定　「request.auth != null」 => 認証あり　「request.auth == null」 => 認証なし
    service cloud.firestore {
        match /databases/{database}/documents {
            match /{document=**} {
                allow read, write: if request.auth != null || request.auth == null;
            }
        }
    }
-->

<!-- 参考サイト 住んでるシェアハウスの掲示板をVue.jsとFirebaseで作ってみた
https://qiita.com/jiroshin/items/e6e3c06a3e41515114ee
※必要部分のみ抜粋 -->

<template>
  <div class="container">

    <transition name="post-alert">
      <b-alert v-show="post_message" variant="success" show>内容が投稿されました</b-alert>
    </transition>
    <transition name="post-validataion">
      <b-alert v-show="post_validation" variant="danger" show>内容を入力して下さい</b-alert>
    </transition>

    <b-card-text>
        <div id="spinner" v-show="spinner">
            <b-spinner variant="success" label="Spinning"></b-spinner>
        </div>
        <b-form>
            <div v-show="card_form_cont">
                <b-form-input v-model="form_data.name" placeholder="name" required></b-form-input>
                <b-form-select v-model="form_data.cont_type" :options="cont_types" required></b-form-select>
                <b-form-textarea v-model="form_data.cont" placeholder="content" required></b-form-textarea>
            </div>
            <b-button @click="submit_form" variant="outline-success">お問い合わせ</b-button>
        </b-form>
    </b-card-text>
  </div>
</template>

<script>
import firebase from 'firebase'

export default {
  data() {
    return {
        form_data: {
            name: '',
            cont_type: 'example-1',
            cont: '',
        },
        cont_types: [
            { text: 'example-1', value: 'example-1' },
            { text: 'example-2', value: 'example-2' },
            { text: 'example-3', value: 'example-3' }
        ],
        id_last: '0',
        post_message: false,
        post_validation: false,
        spinner: false,
        card_form_cont: true,
    }
  },
  created: function() {
      firebase.firestore().collection('posts_table').get().then(function(querySnapshot) {
          querySnapshot.forEach(function(doc){
              if(Number(doc.data().id) > Number(this.id_last)) {
                  this.id_last = doc.data().id
              }
          }.bind(this))
      }.bind(this))
  },
  methods: {
    submit_form: function() {
        if (this.form_data.name == '' || this.form_data.cont == '') {
            this.post_validation = true
            setTimeout(function(){
                this.post_validation = false
            }.bind(this), 3000)
            return
        }

        this.id_last = String(Number(this.id_last) + 1)

        firebase.firestore().collection('posts_table').add({
            id: this.id_last,
            name: this.form_data.name.trim(),
            cont_type: this.form_data.cont_type.trim(),
            cont: this.form_data.cont.trim()
        })

        this.form_data.name = ''
        this.form_data.cont_type = 'example-1'
        this.form_data.cont = ''

        this.card_form_cont = false
        this.spinner = true
        setTimeout(function(){ this.card_form_cont = true }.bind(this), 700)
        setTimeout(function(){ this.spinner = false }.bind(this), 700)

        this.post_message = true
        setTimeout(function(){ this.post_message = false }.bind(this), 2000)
    }
  }
}
</script>

<style scoped>
    .post-alert-enter-active, .post-alert-leave-active {
        transition: opacity 2s, transform 1.5s;
    }
    .post-alert-enter {
        opacity: 0;
        transform: translateY(-50px);
    }
    .post-alert-leave-to {
        opacity: 0;
        transform: translateY(200px);
    }
    .post-validataion-enter-active, .post-validataion-leave-active {
        transition: opacity 1s, transform 1s;
    }
    .post-validataion-enter, .post-validataion-leave-to {
        opacity: 0;
        transform: translateY(-50px);
    }
</style>
```

# Vue CLI × Firebase DB描画
※Vue CLI × Firebase フォーム送信の続き

```vue
<!-- components/Post.vue -->
<!-- router/index.js => ルーティング設定　-->

<!-- 参考サイト 住んでるシェアハウスの掲示板をVue.jsとFirebaseで作ってみた
https://qiita.com/jiroshin/items/e6e3c06a3e41515114ee
※必要部分のみ抜粋 -->

<template>
  <div class="container">
    <b-button @click="button_all" variant="outline-primary">全て</b-button>
    <b-button @click="button_example1" variant="outline-primary">example-1</b-button>
    <b-button @click="button_example2" variant="outline-primary">example-2</b-button>
    <b-button @click="button_example3" variant="outline-primary">example-3</b-button>

    <transition-group name="transition">
        <div v-for="post in posts" :key="post.id">
            <div v-if='post.cont_type=="example-1"' v-show="show_example1">
                {{ post.id }} - {{ post.name }}: {{ post.cont }} - {{ post.cont_type }}
            </div>
            <div v-if='post.cont_type=="example-2"' v-show="show_example2">
                {{ post.id }} - {{ post.name }}: {{ post.cont }} - {{ post.cont_type }}
            </div>
            <div v-if='post.cont_type=="example-3"' v-show="show_example3">
                {{ post.id }} - {{ post.name }}: {{ post.cont }} - {{ post.cont_type }}
            </div>
        </div>
    </transition-group>
    
  </div>
</template>
<script>
import firebase from 'firebase'

export default {
    data() {
        return {
            posts: [],
            show_example1: true,
            show_example2: true,
            show_example3: true,
        }
    },
    created: function() {
        firebase.firestore().collection('posts_table').where('id','>=','1')
        .onSnapshot(function(querySnapshot) {
            querySnapshot.forEach(function(doc) {
                let id_ary = new Array()
                this.posts.forEach(function(post) {
                    id_ary.push(post.id)
                })
                if(id_ary.indexOf(doc.data().id) == -1) {
                    this.posts.push(doc.data())
                }
            }.bind(this))
        }.bind(this))
    },
    methods: {
        button_all: function() {
            this.show_example1 = true
            this.show_example2 = true
            this.show_example3 = true
        },
        button_example1: function() {
            this.show_example1 = true
            this.show_example2 = false
            this.show_example3 = false
        },
        button_example2: function() {
            this.show_example1 = false
            this.show_example2 = true
            this.show_example3 = false
        },
        button_example3: function() {
            this.show_example1 = false
            this.show_example2 = false
            this.show_example3 = true
        }
    }
}
</script>

<style scoped>
    .transition-enter-active, .transition-leave-active {
        transition: opacity 1s, transform 1s;
    }
    .transition-enter {
        opacity: 0;
        transform: translateX(-100px);
    }
    .transition-leave-to {
        opacity: 0;
        transform: translateX(100px);
    }
</style>
```