業務で携わっている案件でvue-infinite-loadingが使われていたので、自分でも実装できるように勉強しました。
今回作成したもの
検索したいワードを入れてボタンを押すと、
ヒットした図書・雑誌のタイトルと著者名を表示する簡単なアプリです。
前提
・Node.jsはインストール済(バージョンは現時点でv11.12.0)
・Vue.js/Nuxt.jsはある程度理解している
(私はVue.js & Nuxt.js超入門で勉強していました。また、こちらの記事も参考にしていただければと思います)
作成の手順
(1)Nuxt.jsのプロジェクトを作成する
(2)vue-infinite-loadingをインストールする
(3)templateを作成する
(4)vue-infinite-loadingを使わずに検索結果を表示してみる
(5)vue-infinite-loadingを使って無限スクロールにする
(6)ローディング時のspinnerを変更してみる
(7)読み込むデータがない場合の表示を変更してみる
(1)Nuxt.jsのプロジェクトを作成する
プロジェクトを作成したいディレクトリに移動し、以下のコマンドを実行します。
私はDesktopのpracticeというフォルダにsample-vue-infinite-loading-app
という名前のプロジェクトを作成しました。
$ cd Desktop/practice
$ npx create-nuxt-app sample-vue-infinite-loading-app
ProjectNameなど確認する表示が出てきますが、
Choose features to install
以外は初期設定のままEnterを押していって大丈夫です。
Choose features to install
についてはAxios
を選択してEnterを押してください。
installが完了したら、以下のように作成したプロジェクトに移動して実行します。
$ cd sample-vue-infinite-loading-app
$ npm run dev
localhost:3000にアクセスして、以下の画面になっていればプロジェクトの作成は完了です。
(2)vue-infinite-loadingをインストールする
Ctrl + C
で一旦実行しているプロジェクトを終了させて、
以下のコマンドを実行してvue-infinite-loading
をインストールします。
$ npm install vue-infinite-loading -S
インストールが完了したら、npm run dev
コマンドで再度プロジェクトを起動します。
(3)templateを作成する
作成したプロジェクトのpagesフォルダのindex.vueファイルを開いて、template部分を作成します。
<template>
<section class="container">
<div class="input-area">
<input type="text" />
<button>検索</button>
</div>
<div class="result-display-area">
<p>---ここに検索結果を表示---</p>
</div>
</section>
</template>
<script>
</script>
(4)vue-infinite-loadingを使わずに検索結果を表示してみる
まずはvue-infinite-loadingは使わず、単純に検索結果を表示してみます。
<template>
<section class="container">
<div class="input-area">
<p>{{message}}</p>
<input type="text" v-model="inputWord" @focus="focus"/>
<button @click="getData">検索</button>
</div>
<div class="result-display-area">
<div v-for="(item, $index) in list" :key="$index">
{{item.title}} ( {{item['dc:creator']}} )
</div>
</div>
</section>
</template>
<script>
import axios from 'axios';
let url = 'https://ci.nii.ac.jp/books/opensearch/search?title=';
export default {
data() {
return {
inputWord: '',
page: 1,
list: [],
message: ''
}
},
methods: {
getData() {
if(!this.inputWord) {
this.message = 'Please input some word.'
return;
}
axios.get((url + this.inputWord), {
params: {
p: this.page,
format: 'json'
}
}).then(({data}) => {
const items = data['@graph'][0]['items'];
this.list.push(...items)
}).catch((error) => {
this.message = 'error'
})
},
focus() {
this.inputWord = '';
this.page = 1;
this.list = [];
this.message = '';
}
}
}
</script>
エラーが出た場合
もしModule not found
のエラーが出たら、ターミナルで$npm install axios
コマンドを実行して、
再度$npm run dev
してみてください。
また、Access to XMLHttpRequest at〜
のようなエラーが出たら、一旦Chromeを終了して、
$open /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir
コマンドでChromeを開いてください。
(こちらの記事でクロスオリジン問題について少し書いてます)
実装の流れ
dataプロパティにinputWord
を設定して、入力されたワードとバインドし
ボタンが押されたらgetData
メソッドを呼び出して、検索結果をlist
に追加します。
検索結果はresult-display-area
の部分で本のタイトル
と著者名
を表示します。
また、検索結果が表示されている状態で入力欄をクリックした場合は、
focus
メソッドでdataプロパティを初期化しています。
CiNii Books 図書・雑誌検索OpenSearch
CiNii Booksは、全国の大学図書館等が所蔵する本(図書や雑誌等)の情報を検索できるサービスです。
登録不要で使用することができます。
今回はCiNii BooksのOpenSearchを使用しています。OpenSearchは、
https://ci.nii.ac.jp/books/opensearch/search?title=検索したいワード&format=json
にアクセスすると検索結果がjson形式で取得できます。
また、p=数字
と付け加えるとページ番号も指定できます。
例えばhttps://ci.nii.ac.jp/books/opensearch/search?title=vue.js&format=json&p=1
にアクセスすると、vue.js
というワードで検索した結果がこのように表示されます。
今回のアプリでは、配列items
をdataプロパティのlist
に追加し、title
dc:creator
を検索結果として表示しています。
(5)vue-infinite-loadingを使って無限スクロールにする
vue-infinite-loadingを使って、検索結果をページ毎に表示していきます。
<template>
<section class="container">
<div class="input-area">
<p>{{message}}</p>
<input type="text" v-model="inputWord" @focus="focus"/>
<button @click="changeButtonStatus">検索</button>
</div>
<div class="result-display-area">
<div v-for="(item, $index) in list" :key="$index">
{{item.title}} ( {{item['dc:creator']}} )
</div>
</div>
<infinite-loading @infinite="infiniteHandler" v-if="buttonStatus"></infinite-loading>
</section>
</template>
<script>
import axios from 'axios';
import InfiniteLoading from 'vue-infinite-loading';
let url = 'https://ci.nii.ac.jp/books/opensearch/search?title=';
export default {
components: {
InfiniteLoading
},
data() {
return {
inputWord: '',
buttonStatus: false,
page: 1,
list: [],
message: ''
}
},
methods: {
changeButtonStatus() {
if(!this.inputWord) {
this.message = 'Please input some word.'
return;
}
this.buttonStatus = true
},
infiniteHandler($state) {
axios.get((url + this.inputWord), {
params: {
p: this.page,
format: 'json'
}
}).then(({data}) => {
const items = data['@graph'][0]['items'];
if(items) {
this.page += 1;
this.list.push(...items)
$state.loaded();
} else {
$state.complete();
}
}).catch((error) => {
this.message = 'error'
})
},
focus() {
this.inputWord = '';
this.buttonStatus = false,
this.page = 1;
this.list = [];
this.message = '';
}
}
}
</script>
実装の流れ
(4)ではボタンを押したときにgetData
メソッドを呼び出していましたが、ボタンを押したらまず
changeButtonState
メソッドを呼び出して、dataプロパティのbuttonStatus
をfalse→trueに変更します。
そして、buttonStatus
がtrue(ボタンが押された)の場合にinfiniteHandler
メソッドを呼び出し、
データを取得していきます。
infiniteHandler
メソッドでは、データが取得できたら
取得データの中の配列items
をlist
に追加・ページ番号を1つ増やしていきます。
これでvue-infinite-loadingを使って無限ローディングにすることができました。
(6)ローディング時のspinnerを変更してみる
vue-infinite-loadingでは、spinner(ローディングしているときのくるくる)を変更することができます。
spinnerがわかりやすいように、setTimeOut
メソッドを使って1秒毎にデータを読み込むようにしています。
<template>
<section class="container">
<div class="input-area">
<p>{{message}}</p>
<input type="text" v-model="inputWord" @focus="focus"/>
<button @click="changeButtonStatus">検索</button>
</div>
<div class="result-display-area">
<div v-for="(item, $index) in list" :key="$index">
{{item.title}} ( {{item['dc:creator']}} )
</div>
</div>
<infinite-loading spinner="spiral" @infinite="infiniteHandler" v-if="buttonStatus"></infinite-loading>
</section>
</template>
<script>
import axios from 'axios';
import InfiniteLoading from 'vue-infinite-loading';
let url = 'https://ci.nii.ac.jp/books/opensearch/search?title=';
export default {
components: {
InfiniteLoading
},
data() {
return {
inputWord: '',
buttonStatus: false,
page: 1,
list: [],
message: ''
}
},
methods: {
changeButtonStatus() {
if(!this.inputWord) {
this.message = 'Please input some word.'
return;
}
this.buttonStatus = true
},
infiniteHandler($state) {
axios.get((url + this.inputWord), {
params: {
p: this.page,
format: 'json'
}
}).then(({data}) => {
const items = data['@graph'][0]['items'];
setTimeout(() => {
if(items) {
this.page += 1;
this.list.push(...items)
$state.loaded();
} else {
$state.complete();
}
}, 1000)
}).catch((error) => {
this.message = 'error'
})
},
focus() {
this.inputWord = '';
this.buttonStatus = false,
this.page = 1;
this.list = [];
this.message = '';
}
}
}
</script>
infinite-loading
タグの中に、spinner="spinnerの種類"
を加えることで変更できます。
spinnerの種類は以下の通りです。
(7)読み込むデータがない場合の表示を変更してみる
読み込むデータがこれ以上ない場合はNo more data :)
、検索結果がなかった場合はNo results :(
と
defaultで表示されますが、slot
を指定して変更することができます。
<template>
<section class="container">
<div class="input-area">
<p>{{message}}</p>
<input type="text" v-model="inputWord" @focus="focus"/>
<button @click="changeButtonStatus">検索</button>
</div>
<div class="result-display-area">
<div v-for="(item, $index) in list" :key="$index">
{{item.title}} ( {{item['dc:creator']}} )
</div>
</div>
<infinite-loading spinner="spiral" @infinite="infiniteHandler" v-if="buttonStatus">
<span slot="no-more">----- 検索結果は以上です-----</span>
<span slot="no-results">----- 検索結果はありません-----</span>
</infinite-loading>
</section>
</template>
<script>
import axios from 'axios';
import InfiniteLoading from 'vue-infinite-loading';
let url = 'https://ci.nii.ac.jp/books/opensearch/search?title=';
export default {
components: {
InfiniteLoading
},
data() {
return {
inputWord: '',
buttonStatus: false,
page: 1,
list: [],
message: ''
}
},
methods: {
changeButtonStatus() {
if(!this.inputWord) {
this.message = 'Please input some word.'
return;
}
this.buttonStatus = true
},
infiniteHandler($state) {
axios.get((url + this.inputWord), {
params: {
p: this.page,
format: 'json'
}
}).then(({data}) => {
const items = data['@graph'][0]['items'];
setTimeout(() => {
if(items) {
this.page += 1;
this.list.push(...items)
$state.loaded();
} else {
$state.complete();
}
}, 1000)
}).catch((error) => {
this.message = 'error'
})
},
focus() {
this.inputWord = '';
this.buttonStatus = false,
this.page = 1;
this.list = [];
this.message = '';
}
}
}
</script>
nuxt.js
と検索すると3件のデータのあとに----- 検索結果は以上です-----
、
aaaaaaaaaaaaa
と検索すると----- 検索結果はありません-----
と表示されると思います。
おわりに
引き続き勉強がんばりたいです。
もし間違った記載などあればご指摘いただけると幸いです。
参考
・vue-infinite-loading
・Nuxt.jsとvue-infinite-loadingを使って無限スクロールを実装する
↑今回初めてvue-infinite-loadingを勉強するにあたって、こちらの記事がとてもわかりやすかったです。
・CiNii Books - メタデータ・API - CiNii Books 図書・雑誌検索のOpenSearch
・Vue.js & Nuxt.js超入門
・【Nuxt.js】todoアプリを作成してみた①
・【Nuxt.js】todoアプリを作成してみた②