DEV Community

Adam Miedema
Adam Miedema

Posted on

Create a keyboard navigable site search with Nuxt/Content and Aloglia vue-instantsearch

This is a continuation of Getting started with NuxtJS / Content module.


Let's get right down into it and add an Algolia search component to our Nuxt/VueJS documentation site project.

Install Algolia vue-instantsearch

In the package.json file, let's add the algoliasearch and vue-instantsearch dependencies.

And, since we're here, let's also add nuxt-content-algolia and v-outside-click as we'll also use these packages while crafting the search functionality.

"dependencies": {
    ...
    "algoliasearch": "^4.8.3",
    "vue-instantsearch": "^3.4.3",
    "nuxt-content-algolia": "^0.2.0",
    "v-click-outside": "^3.1.2",
    "remove-markdown": "^0.3.0"
  },
Enter fullscreen mode Exit fullscreen mode

Next, in the project's root directory, run the yarn install command to install the newly defined packages.

We need to now make some updates to the nuxt.config.js file.

plugins: [
    '~/plugins/vue-instantsearch'
  ],
build: {
    transpile: ['vue-instantsearch', 'instantsearch.js/es']
  },
nuxtContentAlgolia: {
    appId: process.env.ALGOLIA_APP_ID,
    apiKey: process.env.ALGOLIA_API_KEY,
    paths: [
      {
        name: 'documentation',
        index: 'docs',
        fields: ['title', 'description', 'bodyPlainText']
      }
    ]
  },
hooks: {
    'content:file:beforeInsert': (document) => {
      const removeMd = require('remove-markdown');
      if (document.extension === '.md') {
        document.bodyPlainText = removeMd(document.text);
      }
    },
  },

Enter fullscreen mode Exit fullscreen mode

We added quite a few new items to the Nuxt config file, so let's go through what they are.

plugins/vue-instantsearch is Algolia's library of pre-built Vue components that we will leverage.

transpile is needed in order for the Vue/Nuxt to understand the javascript version that Algolia uses. Make sure you define this, as you'll get a bunch of console errors otherwise!

nuxtContentAlgolia is a plugin that will index the defined contents folders and send corresponding tag data to Algolia during yarn generate - which is the command that will run when the site it build for static production hosting.

hooks have been added to send the markdown file plain body text with markdown characters stripped out to Algolia so that we can display body search results without any ugly html tags or markdown characters.

Now, we'll add the vue-instantsearch.js file to the plugins directory.

import vue from 'vue';
import InstantSearch from 'vue-instantsearch'

vue.use(InstantSearch);
Enter fullscreen mode Exit fullscreen mode

Alrighty, we now have our setup complete so we can start creating the search component!

Display search results

In the Search.vue file, we'll drop in vue-instantsearch components to handle search queries and display results.

We'll first want to import the necessary module.

import algoliasearch from 'algoliasearch/lite';
Enter fullscreen mode Exit fullscreen mode

Then, define searchClient and include the app id and search api id for our Algolia account.

data () {
    return {
      searchClient: algoliasearch('appID', 'searchApiID'),
    }
  },
Enter fullscreen mode Exit fullscreen mode

Now, we can drop the vue-instantsearch components into the <template> section.

Check out the Search.vue file on GitHub and watch the above video to view the components that are being added and to learn what they do. There are quite a few and might be easier to view than read in this case. πŸ˜‰

Focus search box with 'command + k'

Add a keyboard command to put the search box in focus is all the rage nowadays.

Luckily, javascript is making it easier to detect and we don't have to worry about figuring out key values to refer to anymore. πŸŽ‰

On the search input field, we have a reference - ref="searchInput" that we will use to put into focus if certain keys are pressed.

To define the keys, we'll need to add an event listener to pick up the events. Let's add this as a mounted function.

mounted: function () {
    this.$nextTick(function () {
      window.addEventListener('keydown', event => {
        if(event.metaKey && event.key === 'k') {
          this.$refs.searchInput.focus()
          event.preventDefault()
        }
      })
    })
  },
Enter fullscreen mode Exit fullscreen mode

You may have noticed that we also want to prevent default actions. Some browser, ahem Firefox, will put the browser search into view if you press command + k on your keyboard.

You'll also notice the metaKey. This is either the command key if you are using a Mac device or the windows key if you're using a Windows device.

We can even detect what device type a user is using and define placeholder text based on that.

computed: {
    searchPlaceholder () {
      if (navigator.appVersion.indexOf('Mac') !== -1 ) {
        return 'Search - ⌘k to focus'
      }
      else if (navigator.appVersion.indexOf('Win') !== -1 ) {
        return 'Search - Win + k to focus'
      } else {
        return 'Search'
      }
    }
  },
Enter fullscreen mode Exit fullscreen mode

Close search results on outside click

We installed a package earlier to help us detect outside clicks.

Let's use that to close the search results dropdown when clicked outside of the search area.

First, we'll import the package to the search.vue file.

import vClickOutside from 'v-click-outside'
Enter fullscreen mode Exit fullscreen mode

And then, add it as a directive.

export default {
  directives: {
    ClickOutside: vClickOutside.directive
  },
}
Enter fullscreen mode Exit fullscreen mode

We'll add this to the ais-autocomplete component and then refer to a method on outside click events.

<ais-autocomplete v-click-outside="onClickOutside">
Enter fullscreen mode Exit fullscreen mode

We have a data item labeled showResults which is tied to v-show on the search results dropdown. This will determine if the results display or not. On an outside click, we want to set this to false.

methods: {
    onClickOutside () {
      this.showResults = false
    },
}
Enter fullscreen mode Exit fullscreen mode

Add keyboard commands to move through results and to go to result page on click of enter key

Let's add some more delights for our keyboard-first users.

We'll add some more click handlers on the <input> tag.

<input
            ...
            @keydown.up.prevent="highlightPrevious"
            @keydown.down.prevent="highlightNext(indices[0].hits.length)"
            @keydown.enter="goToDoc(indices)"
          >
Enter fullscreen mode Exit fullscreen mode

We also want to add a highlight effect if a user clicks the up/down arrows as well as if the user hovers over a search result.

Let's add :class="{ 'bg-blue-900' : isCurrentIndex(index) }" to <nuxt-link> tag that highlights the current selected index.

And, add @mouseover="highlightedIndex = index" to the child div of <nuxt-link>, which will handle the mouse over and change index based on the index of the result that is currently being moused over.

Lastly, the we'll add the logic in the form of new methods to the <script> section.

highlightPrevious () {
      if (this.highlightedIndex > 0 ){
        this.highlightedIndex -= 1
      }
    },
    highlightNext (resultsCount) {
      if (this.highlightedIndex < resultsCount - 1) {
        this.highlightedIndex += 1
      }
    },
    isCurrentIndex (index) {
      return index === this.highlightedIndex
    },
    goToDoc (indices) {
      this.$nuxt.$router.push(indices[0].hits[this.highlightedIndex].objectID)
    }
Enter fullscreen mode Exit fullscreen mode

You now have a fully functional site documentation search using Algolia and VueJS! πŸ”₯


Following along? View the GitHub Repo to view the code for this tutorial.

You can also view the full video tutorial series playlist on YouTube.

Looking for a tool to manage your VPS servers and app deployments? Check us out at cleavr.io


Full tutorial series on creating a documentation site using Vue/Nuxt, Tailwind, and Algolia.

Top comments (1)

Collapse
 
dikaio profile image
Donovan Dikaio

Excellent tutorial, thanks for sharing Adam!