Part.1 では犬種リストの表示まで実装しました。今回は画像表示から「いいね!」ボタン、ページネーションの実装までを行いアプリを完成させます。
過去の記事は以下から。
Nuxt.js で簡単な画像一覧アプリを作成する – Part.1
構成について
特定の犬種をクリックした際、画像が一覧表示されるようにします。エンドポイントは https://dog.ceo/api/breed/[犬種名]/images
です。Dog APIのドキュメントも参照してください。
ルーティング(ディレクトリ構成)は上記のようになります。
ディレクトリ名がアンダースコア付きで _breed
となっているので、犬種は動的に設定できます。
画像取得のメソッドを追加する
それでは、さっそく開発を進めていきましょう。
イヌの画像を取得するメソッドをDogApiクラスに追加します。
api/dog.js
class DogApi {
// ...中略...
dogs( breed ) {
return axios.get(`${this.apiBase}/breed/${breed}/images`)
.then(json => {
return json.data.message.map( (d) => {
return {
url: d, // 画像URL
like: 0, // 「いいね!」の件数
};
});
})
.catch(e => ({ error: e }));
}
}
取得したデータを保存できるように、state と mutation も追加しておきます。
store/index.js
const appStore = () => {
return new Vuex.Store({
state: {
breed_list: {},
dog_list: {}, // 追加
},
mutations: {
breed_list_update(state, payload) {
state.breed_list = {...payload}
},
// 追加
dog_list_update(state, payload) {
state.dog_list = [...payload]
},
}
})
};
画像一覧ページの追加
pages/dogs/_breed/index.vue
を追加し、 fetch() で画像取得のAPIを叩くようにします。
pages/dogs/_breed/index.vue
<template>
<section class="container">
<div class="columns is-multiline">
</div>
</section>
</template>
<script>
import dogApi from '@/api/dog';
import {mapState} from 'vuex';
export default {
async fetch({store, params}) {
const json = await dogApi.dogs(params.breed);
store.commit('dog_list_update', json)
},
computed: mapState(['dog_list']),
}
</script>
犬種をクリックした際に、画像一覧ページへ遷移するようリンクを修正します。
pages/index.vue
<template>
<section class="container">
<div class="columns is-multiline">
<div v-for="(item, i) in breed_list" v-bind:key='i' class='column is-2'>
<!-- <a class="button">{{ i }}</a> -->
<nuxt-link :to="{ path: 'dogs/'+ i }" class="button">{{ i }}</nuxt-link>
</div>
</div>
</section>
</template>
犬種をクリックして画面遷移後、Vue.js devtools で dog_list
にデータが入っていることが確認できればOKです。
画像を表示する
APIで取得したデータをもとに画像表示します。
pages/dogs/_breed/index.vue
<template>
<section class="container">
<div class="columns is-multiline">
<!-- 追加 -->
<div v-for="(item, i) in dog_list" v-bind:key="i" class="column is-1">
<img :src="item.url">
</div>
</div>
</section>
</template>
犬種を選択した際、以下のように画像表示されればOKです。
「NEW」ラベルと「いいね!」ボタンを配置する
イヌの画像と合わせて「いいね!」ボタンを配置するようにページを改修します。先頭3件には「NEW」ラベルも表示されるようにしましょう。
pages/_breed/index.vue
<template>
<section class="container">
<div class="columns is-multiline">
<div v-for="(item, i) in dog_list" v-bind:key="i" class="column is-2">
<img v-bind:src="item.url">
<!-- 追加 -->
<span v-if="i < 3" class="tag is-danger">NEW</span>
<a class="button is-warning is-small" v-on:click="item.like += 1">
<span>いいね!{{item.like}}件</span>
</a>
</div>
</div>
</section>
</template>
ページングの追加
時間の都合上、勉強会の内容はここで終了となりましたが今回はページングまで実装してみます。Dog API側でページング処理を行うことはできないようなので、データは全件取得してフロント側でデータをsliceすることで実装します。
最初に、ページ件数を保持するために store を追加します。
store/index.js
const appStore = () => {
return new Vuex.Store({
state: {
breed_list: {},
dog_list: [],
page_count: 1, // 追加
},
mutations: {
breed_list_update(state, payload) {
state.breed_list = {...payload};
},
dog_list_update(state, payload) {
state.dog_list = [...payload];
},
// 追加
page_count_update(state, payload) {
state.page_count = parseInt( payload );
},
}
})
};
store の準備ができたら、画像一覧のページにページング用のコンポーネントと処理を追加します。クエリストリングを使い、?page=1
という感じでページング処理を行うようにします。Nuxt.js では、 コンテキスト の context.query
を参照してクエリストリングを受け取ります。
pages/dogs/_breed/index.vue
<template>
<section class="container">
<div class="columns is-multiline">
<div v-for="(item, i) in dog_list" v-bind:key="i" class="column is-2">
<img v-bind:src="item.url">
<span v-if="i < 3" class="tag is-danger">NEW</span>
<a class="button is-warning is-small" v-on:click="item.like += 1">
<span>いいね!{{item.like}}件</span>
</a>
</div>
</div>
<nav class="pagination" role="navigation" aria-label="pagination">
<ul class="pagination-list">
<li v-for="count in page_count" :key="count">
<nuxt-link class="pagination-link"
:class="{ 'is-current' : current == count }"
:to="{ path: '?page=' + count }" append>
{{ count }}
</nuxt-link>
</li>
</ul>
</nav>
</section>
</template>
<script>
import dogApi from '@/api/dog';
import {mapState} from 'vuex';
export default {
watchQuery: [
'page'
],
validate ({ params }) {
return /^[a-z]+$/.test( params.breed );
},
data: function() {
return {
current: 1,
};
},
asyncData: function( context ) {
return {
current: parseInt( context.query['page'] ) || 1,
}
},
fetch: async function( {store, params, query} ) {
const page = parseInt( query['page'] ) || 1;
const start = 20 * ( page - 1 );
const end = start + 20;
const json = await dogApi.dogs( params.breed );
store.commit( 'page_count_update', Math.ceil( json.length / 20 ) );
store.commit( 'dog_list_update', json.slice( start, end ) );
},
computed: mapState([
'page_count',
'dog_list',
]),
}
</script>
参考になるドキュメントを以下にまとめておきます。
validate | Nuxt.js で動的ルーティングを行う際、値を検証するために使います。ドキュメントは API: validate メソッド を参照。 |
nuxt-link | router-link のラッパーなので、 router-link を参照。 |
watchQuery | クエリストリングの変更を検知します。ページングボタン押下時にコンテンツを再描画するため必須です。ドキュメントは API: The watchQuery Property を参照。 |
asyncData | Nuxt.js のコンテキストを Vueコンポーネントのデータオブジェクトに同期します。ドキュメントは API: asyncData メソッド を参照。 |
ページング用のコンポーネントについては Bulmaのドキュメントを参照してください。
静的ファイルとしてデプロイする
Nuxt.js のデプロイ方法は多様ですが、今回は一番シンプルな静的ファイルでデプロイしてみます。コマンドで npm run generate
を叩くだけです。デフォルトでは dist
ディレクトリに静的ファイルが生成されます。
$ cd path/to/nuxt/project
$ npm run generate
$ cd ./dist
$ php -S localhost:28080 // http://localhost:28080 にアクセス