#はじめに
当初はネイティブJSで実装を行う予定でしたが、laravelがvueをサポートしているということを知り良い機会だと思い取り入れて見ました。
#想定・前提
- vue理解のため導入箇所を絞ってシンプルに導入(コンポーネントのみ)
- ルーティングはlaravel側で行う(SPAはなし)
- バージョンは下記の通り
laravel: 5.7
vue: 2.5.7
#関連ファイルの準備
今回は、予めlaravelのblade側で実装していたテーブルをvueでコンポーネント化し、Twitterでよくみかける無限スクロールっぽい実装を行なっています。
##bladeの設定
- 共通テンプレート
resources/views/layouts/layout.blade.php
<!doctype html>
<html lang="ja">
<head>
@include('includes.partials.page_meta')
<link href="{{ mix('/css/common.css') }}" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app" class="container">
@yield('content')
@include('includes.footer')
</div>
<script src="{{ mix('/js/app.js') }}" defer></script>
</body>
</html>
- ページ固有テンプレート
resources/views/pages/page.blade.php
@extends('layouts.layout')
@section('pageMetaTags')
@stop
@section('pageHeadStyles')
<link href="{{ mix('/css/page.css') }}" rel="stylesheet" type="text/css">
@stop
@section('content')
<div class="contents">
<div class="wrap">
<demo-component
results="{{ empty($demo['results']) ? json_encode('') : json_encode($demo['results']) }}"
:limit="5"
>
</demo-component>
</div>
</div>
@stop
axiosを使わなくても、bladeから直接データを直接渡せるということなのでコントローラー側には手を入れていません。
[results] => Array
(
[0] => Array
(
[data1] => Array
(
[date] =>
[name] =>
[item1] =>
[item2] =>
)
[data2] => Array
コントローラーから受け取るデータが上記のような場合だと、Object
では渡せないためjson_encode
を使用してJSON形式の文字列にしてコンポーネント側に渡し配列に変換が必要でした。
##webpackの設定
let mix = require('laravel-mix');
mix.disableNotifications()
.options({
postCss: [
require('autoprefixer')({
grid: true,
browsers: ['ie >= 9', 'Edge >= 12', 'ff >= 50', 'chrome >= 56', 'Safari >= 7', 'ios_saf >= 8', 'Android >= 4']
})
]
})
//.js('resources/assets/js/common.js', 'public/js')
.js('resources/assets/js/app.js', 'public/js');
if (!mix.inProduction()) {
mix.webpackConfig({
devtool: 'source-map'
});
mix.sourceMaps();
}
if (mix.inProduction()) {
mix.version();
}
もともと共通用のスクリプトを書いていたcommon.js
はvueを使用するにあたりapp.js
にimportさせています。
##インスタンスの設定
window.Vue = require('vue');
Vue.component('demo-component', require('./components/demoComponent.vue'));
const app = new Vue({
el: '#app'
});
##コンポーネントファイルの設定
<template>
<div v-bind:class="{'table-scroll': wrapFlg}">
<table>
<thead>
<tr>
<th>Date</th>
<th>Name</th>
<th>Item1</th>
<th>Item2</th>
</tr>
</thead>
<tbody v-if="records.length > 0">
<tr v-for="record in records">
<td>{{ record.date }}</td>
<td>
<a :href="'/detail/' + record.id">{{ record.name }}</a>
</td>
<td>{{ record.item1 }}</td>
<td>{{ record.item2 }}</td>
</tr>
<tr v-show="loader">
<td colspan="4">
<i class="icon-loader"></i>
</td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="4">
<span>データはまだありません。</span>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped>
.table-scroll {
overflow-x: scroll;
}
.icon-loader {
display: inline-block;
background-image: url(/img/loader.gif);
background-repeat: no-repeat;
background-size: contain;
width: 30px;
height: 30px;
vertical-align: middle;
}
</style>
<script>
export default {
props: {
results: {
type: String,
default: ''
},
wrapFlg: {
type: Boolean,
default: true,
},
limit: {
type: Number,
default: 10
}
},
data () {
return {
timer: null,
startPos: 0,
records: [],
loader: false
}
},
created: function () {
for (let cnt = 0; cnt < this.limit; cnt++) {
if (this.parseResults[cnt]) {
this.records.push(this.parseResults[cnt]);
}
}
},
mounted: function () {
window.addEventListener('scroll', this.infiniteScroll);
},
beforeDestroy: function () {
window.removeEventListener('scroll', this.infiniteScroll);
},
computed: {
parseResults: function () {
return JSON.parse(this.results);
}
},
methods: {
async fetch () {
if (this.parseResults[this.records.length]) {
this.loader = true;
if (this.timer === null) {
this.timer = setTimeout(function () {
this.records.push(this.parseResults[this.records.length]);
this.timer = null;
this.loader = false;
}.bind(this), 500);
}
}
},
getElementRect: function () {
return this.$el.getBoundingClientRect();
},
infiniteScroll: function () {
const {top, height} = this.getElementRect();
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const targetTop = top + scrollTop;
if (window.innerHeight + scrollTop > targetTop + height) {
if (scrollTop > this.startPos) {
this.fetch();
}
}
this.startPos = scrollTop;
}
}
}
</script>
#つかってみて
はじめてvueを使ってみましたが、思いのほか全体の設計を変えずに導入できました。
templateもv-bind
などvue独自のものを除けば、比較的bladeの記述に近い形になるのと日本語マニュアルにある程度網羅されているので、普段laravelに触れている方は気軽に始めることができるのかと思いました。
また、自由度が高くフレームワーク依存が少ないので後からの切り離しも楽かと。