How to make a client-side search engine with Vue.js and Lunr.js
Posted on January 29, 2019 in
4 min read
This is a little tutorial about making a search/filter Vue.js component using the powerful Lunr.js library.
A little disclaimer: I've purposely avoided any CSS styles also keeping minimal the HTML markup, using Vue without the CLI, to focus only on the logic and integration part for simplicity. I thought Vue.js learners may find this way useful.
A little Vue.js app
Suppose to have a little component that loads a JSON file and creates a items list based on a given array, such as:
Vue.component('mylist', {
template:`<ul>
<li v-for="item in list" :key="item.id">{{item.name}}</li>
</ul>`,
props:['list']
})
Here the working example:
See the Pen vue lunr search 1 by Fabio Franchino (@abusedmedia) on CodePen.
Then, we want to integrate a search/filter capability with an input text field:
Vue.component('mysearchbtn', {
template:`<div>
<input type="text" placeholder="type to search"
v-model="search"
@input="$emit('update:search', $event.target.value)" />
</div>`,
props:['search']
})
and here the updated example:
See the Pen vue lunr search 2 by Fabio Franchino (@abusedmedia) on CodePen.
Now we need to make both the components working together, let's say, when I type into the text field, the list should update according to the search pattern.
Welcome Lunr.js
Instead of reinventing the wheel by implementing a search algorithm, I'm going to exploit Lunr, a very powerful and configurable library that make complex search pretty neat!
Just to give a taste, you can search using some well-known patterns such as:
- using the wildcard, i.e.
Pete*
to find anything that begins withpete
- searching in a specific field object, i.e.
email:*@gmail.com
to find inemail
field the string that ends with@gmail.com
- using operators to include or exclude specific keywords, i.e.
me +you -her
- Lunr handles plurals and articles for us as well
- bonus tip, each result item comes with a score based on search relevance as well as additional useful information related
Setting Lunr up
Lurn requires the creation of an index based on a given dataset, such as:
var searchIndex = lunr(function () {
this.ref('id')
this.field('name')
this.field('body')
this.field('email')
documents.forEach(doc => {
this.add(doc)
})
})
An important thing to consider about Lunr is the result array that is not a filtered version of the original dataset but a new and different array of objects containing specific search result properties, I guess both for performance reasons and to provide additional search information without manipulating the original array.
That means we need to find a way to filter the original array based on the produced Lunr array. This is the function I use; basically, I set a new array on every search keyword change including only the items present in the search result based on the reference field (the id
in this case):
this.list = []
this.resuls.forEach(d => {
this.original.forEach(p => {
if(d.ref == p.id) this.list.push(p)
})
})
I'm still wondering whether that is the best way to update the list from a performance perspective in Vue.js, though.
Now, the final examples looks like this:
See the Pen vue lunr search 3 by Fabio Franchino (@abusedmedia) on CodePen.
Hope this might be helpful to someone. I made it to integrate the functionality on a little tool I'm working on, Presenta.
Have a nice day!