はじめに
Algolia で検索しながら無限スクロールがしたかったので、algolia に Vue.js + TypeScript で入門します。最終的に以下の gif のようなものができます。
それとなくできたのですが、細かい部分が意図した動きにならず困っています。が、細かい部分なので今は無視しています。
公式サンプルはこちら infinite scroll なのですが、これも意図した動きをしてくれません。
何が困っているかというと、一度スクロールをすると data の page が this.page++;
によって大きな数になるのですが、それ以降の検索でうまいことリセットされず、意図した検索結果が返ってこないのです。(最初の20件とかしか返さなくなる..)
僕の場合はこの困りごとを、以下のような方法でお茶を濁しています。これも完璧ではないのですが。
e.addEventListener("input", () => {
this.page = 1 // まずはリセットして
this.resetPage() // さらに足りなければ +1 するようの独自メソッド
})
とはいえある程度うごくものなので、まとめておきます。
Step1. Vue CLI でアプリをつくります
まずはおもむろに、以下コマンドです。TypeScrip を選びましたので、以降 TS です。
$ vue create study-algolia
$ yarn serve
これで、めでたく無事エラーがでます。前は出なかったのになー。
https://github.com/webpack/webpack/issues/8768#issuecomment-462090153
仕方ないので、言われるままに以下
Step2. ふつうに algolia をつかってみる
vue でフロントを書くときに便利なツールとして vue-instantsearch が用意されていますので、これを使います。
$ yarn add vue-instantsearch
main.ts に以下を追記します。
+ import InstantSearch from 'vue-instantsearch';
+ Vue.use(InstantSearch);
これで、 yarn serve
すると以下のように怒られます。
Could not find a declaration file for module 'vue-instantsearch'.
Vue-instantsearch.d.ts
を書きます。これで、解消します。
declare module 'vue-instantsearch'
App.vue を以下のようにすることでひとまず動きます。
cf. Create your first search experience
<template>
<ais-index
app-id="latency"
api-key="3d9875e51fbd20c7754e65422f7ce5e1"
index-name="bestbuy"
>
<ais-search-box></ais-search-box>
<ais-results>
<template slot-scope="{ result }">
<h2>
<ais-highlight :result="result" attribute-name="name"></ais-highlight>
</h2>
</template>
</ais-results>
</ais-index>
</template>
こんな感じ。
ここまでで、ふつうに algolia を使うところまで出来ました。
Step3. 自分が用意したデータでインデックスをつくってみる
動作確認をしたいので、index データを自分でつくってみます。go で書きます。
import (
"encoding/json"
"github.com/algolia/algoliasearch-client-go/algoliasearch"
"net/http"
)
func main() {
client := algoliasearch.NewClient(
"{Your Application ID}",
"{Admin API Key}",
)
index := client.InitIndex("test_notes")
str := `[{"id": 1, "content": "Hello world"}, {"id": 2, "content": "I like Vue.js"}]`
var posts []algoliasearch.Object
d := json.NewDecoder(strings.NewReader(str))
d.Decode(&posts)
index.AddObjects(posts)
}
$ go run main.go
これで、インデックスがつくられてダッシュボードからも確認が可能になります。
これを Vue.js 側から呼び出すには以下の部分を修正します。
// 抜粋
<div id="app" class="container-fluid">
<ais-index
id="main"
app-id="自分の" // <- ここ
api-key="自分の" // <- ここ
index-name="test_notes" // <- ここ
// 抜粋
<ais-highlight :result="result" attribute-name="content"></ais-highlight> // <- ここ
良い感じに呼び出せています。
大量にデータを入れておきたいので、json ファイルから index を作れるように go のコードを変更しておきます。
func main() {
client := algoliasearch.NewClient(
"{Your Application ID}",
"{Admin API Key}",
)
index := client.InitIndex("test_notes")
jsonFile, _ := os.Open("notes.json")
var posts []algoliasearch.Object
byteValue, _ := ioutil.ReadAll(jsonFile)
json.Unmarshal([]byte(byteValue), &posts)
index.AddObjects(posts)
}
notes.json
に json データを大量に書いて置いてください。無限スクロールが楽しめるように。
Step4. 無限スクロールに対応する
cf. Infinite scroll
$ yarn add vue-observe-visibility
+ import VueObserveVisibility from 'vue-observe-visibility'
+ Vue.use(VueObserveVisibility)
index-name="test_notes"
+ :query-parameters="{'page': page}"
>
<ais-search-box></ais-search-box>
- <ais-results>
+ <ais-results :stack="true">
<template slot-scope="{ result }">
<h2>
<ais-highlight :result="result" attribute-name="content"></ais-highlight>
</h2>
</template>
</ais-results>
+ <div v-observe-visibility="loadMore">Loading more...</div>
</ais-index>
</template>
以上で、無限スクロールに対応できました。
Step5. 検索のたびに page = 1 にリセットしてみる
調整をした App.vue の全体を記載しておきます。 mounted
と resetPage
を追加しています。
これで冒頭の gif が得られます。
import Vue from 'vue'
import App from './App.vue'
import InstantSearch from 'vue-instantsearch';
import VueObserveVisibility from 'vue-observe-visibility'
Vue.use(InstantSearch);
Vue.use(VueObserveVisibility)
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
<template>
<div id="app" class="container-fluid">
<ais-index
id="main"
app-id="自分の"
api-key="自分の"
index-name="test_notes"
:query-parameters="{'page': page}"
>
<div class="row">
<ais-search-box>
<div class="input-group">
<ais-input
id="input"
placeholder="Search product by name or reference..."
:class-names="{
'ais-input': 'form-control'
}"
></ais-input>
</div>
</ais-search-box>
</div>
<div class="row">
<ais-results :stack="true">
<template scope="{ result }">
<div class="content">
<ais-highlight :result="result" attribute-name="content"></ais-highlight>
</div>
</template>
</ais-results>
<ais-no-results></ais-no-results>
<div id="loadMore" v-observe-visibility="loadMore">Loading more...</div>
</div>
</ais-index>
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
@Component
export default class App extends Vue {
page = 1;
loadMore(isVisible: any) {
if (isVisible) {
this.page++;
}
}
mounted() {
const i = document.querySelector("#input");
i!.addEventListener("input", () => {
this.page = 1;
this.resetPage();
});
}
// page = 1 にして 20 件を表示するが、
// それでも画面に div id="loadMore" が表示されている場合は page++ するため。
// ここのロジックが中途半端...
resetPage() {
const e = document.querySelector("#loadMore");
const topPosition = e!.getBoundingClientRect().top;
const windowHeight = window.innerHeight;
if (topPosition <= windowHeight) {
this.page++;
}
}
}
</script>
<style>
#app {
text-align: center;
}
#input {
margin: 20px;
}
.content {
height: 40px;
}
</style>
以上です。
algolia に入門してみましたが、とても使いやすい気がします。細かなところは、もっと深い部分をちゃんと読まないと。🍋