Implementing Website Search with Vue.
Tags: vue, css, javascript
Published: 2021-03-07 20:50:05

---
title: Implementing Website Search with Vue.
published: true
description: Implementing a robust and responsive website search feature with VueJS and CSS.
tags: vue, css, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sqz474eis9na0s21582a.png
canonical_url: https://jamesinkala.com/blog/implementing-website-search-with-vue
---

In this article I'm going to show how you can implement a responsive search for your web project with the help VueJs and CSS. The search feature is going to be responsive to both user input  and screen size without the use of media queries in the later.

As noted above this tutorial is broken into the responsivity of the layout of the search component and the responsivity to user input.

## The Layout ##
I'll place more focus on the search itself and a bit on the page layout rather than the rest of the project setup since you can plug Vue into virtually any front-end project.

If you are familiar with Vue components and/or Vue's single file components then you'll know how to go about what I just said above, if not go do some reading on that.

### The Page Layout ###
The following is the layout for our page.

```vue
<template>
  <div id="app">
    <div class="page-layout">
      <search></search>
      <div class="extra-content">
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
      </div>
    </div>
  </div>
</template>

<script>
import Search from './components/Search'
export default {
  name: "layout",
  components: {
    Search
  }
}
</script>

<style scopped>
.page-layout{
  display: flex;
  width: 100%;
  height: 100vh;
  flex-direction: column;
}
.search-page > div:first-child{
  height: 50px;
}
.extra-content{
  background: #ededed;
  flex: 1;
  display: grid;
  display: grid;
  grid-gap: 10px;
  padding: 10px;
  grid-template-columns: 1fr;
  grid-template-rows: 100px;
}
.extra-content > div{
  background-color: #00d1b2;
  border-radius: 5px;
}
</style>
```

The important things to note in the above code is that we have our page container __.page-layout__ that contains the __search__ component on top of the rest of the page, in a project you'll likely have this on your navbar and have your logo and some links on either side of the search component.
It's important that the first child of the page layout (the search box) is given an explicit height so that on the next part as the results will be displayed and likely extend the height of the search wrapper the page layout isn't distorted. We want our results to appear floating on top of the rest of the page content bar the search box's input. 

It's good practice to break down our code into smaller reusable components that we can 're-use' in other projects that need same features instead of re-inventing the wheel each time thus saving valuable time, applying this thought process is why we have the __search__ component on it's own, imported into the project's layout as an external component.

### The Search Component ###
The following is the search component's layout:
```vue
<template>
  <div class="s-container">
    <div class="s-input-wrapper">
      <input type="text" v-model="query" placeholder="search">
    </div>
    <div class="s-results">
      <div class="s-result-placeholder" v-if="loading">
        <div class="placeholder-item" v-for="item in 3" :key="item">
          <div></div>
        </div>
      </div>
      <div class="s-result-items" v-if="!loading && results.length" v-for="(item, key) of results" :key="key">
        <div>
          <div>{{ item.title }}</div>
          <div class="category">{{ `in ${item.category}` }}</div>
        </div>
      </div>
      <div class="s-status-message" v-if="!loading && message">
        <p>{{ message }}</p>
      </div>
    </div>
  </div>
</template>

<style scopped>
  .s-container, .s-results{
    position: relative;
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: max-content;
  }
  .s-container{
    margin: 10px 40px;
    grid-gap: 5px;
  }
  .s-input-wrapper > input{
    height: 50px;
    width: 100%;
    background-color: #efefef;
    font-size: 1.5rem;
    padding: 2px 5px;
  }

  .s-result-items > div, .placeholder-item, .s-status-message{
    font-size: 1rem;
    background-color: rgb(255, 255, 255, .9);
    backdrop-filter: blur(10%);
    padding: 4px 5px;
    min-height: 30px;
    display: flex;
    justify-content: space-between;
  }
  .s-result-items > div:nth-child(n+2){
    border-top: 2px solid #d8d8d8;
  }
  .category{
    font-style: italic;
    color: rgb(158, 158, 158);
    font-size: medium;
    font-weight: 600
  }

  .placeholder-item > div {
    position: relative;
    width:100%;
    height: 22px;
    padding: 5px;
    border-radius: 4px;
    background: rgb(225,225,225);
    background: linear-gradient(90deg, rgba(227,227,227,1) 0%, rgba(207,207,207,1) 7%, rgba(207,207,207,1) 13%, rgba(227,227,227,1) 25%);
    background-size:900%;
    background-position: 100% 0%;
    animation: placeholder-animation 1s;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  .s-status-message > p{
    width: 100%;
    text-align: center;
  }

  @keyframes placeholder-animation {
    0% {
    background-position: 100% 0%;
    }
    50% {
    background-position: 50% 0%;
    }
    100% {
    background-position: 0% 0%;
    }
  }
</style>
```

In the layout above we have our search input encapsulated in a div that's also the first row of a grid layout by the search wrapper __.s-container__ . When the user types in the search query the search results will be the sibling rows to the input wrapper thus appear below it.

We also have the content placeholders inside the __.s-content-placeholder__ wrapper that will be displayed as the search results are being fetched.

And the last child of these rows is a div __.s-status-message__ which will be displaying a status message depending on the response we get from our servers.

Applying the grid layout on the search container __.s-container__ and making sure each of it's children will occupy the full width with `grid-template-columns: 1fr;` this will enable us to achieve responsivity in respect to the viewport.
![responsivity to viewport](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0pv7ehcppcd2l8gloxd.gif)

## User Input ##
The javascript part of our component is going to deal mainly with performing actions in reponse to the user input.
Add the following javascript to the Search component.
```vue
<script>
import axios from 'axios'
export default {
  name: "Search",
  data(){
    return {
      query: "",
      results: [],
      loading: false,
      message: ""
    }
  },
  watch: {
    query() {
      if(this.query.length >= 3){
        this.searchItems()
      }
      if(this.query.length < 3){
        this.results = []
      }
    }
  },
  methods: {
    searchItems(){
      this.loading = true
      this.message = ""
      axios.get('/api-endpoint',
        { 
          data: {
            query: this.query
          }
        }
      )
      .then(response => {
        this.loading = false
        this.results = response.data
        this.message = response.someStatusMessage
      })
      .catch(error => {
        this.loading = false
        this.message = error.someStatusMessage
        console.log(error)
      })
    }
  }
}
</script>
```

As observable in the code above we use [axios](https://github.com/axios/axios) to make HTTP calls to our API.

Let's look at the reponsive data that we have declared on the data object:
__query__: This is our input model that will carry the string being typed by the user.
__results__: This is the results array that will be populated by the data that will be returned from the API request.
__loading__: This is a boolean variable that will hold the status of our request. 
__message__: This will hold a status message if there exists one after our API request.

As the user is typing we watch the __query__ variable to listen to the changes that are taking place, to get reasonable results and also not overwork our servers we wait until the search query is about three characters long before we send the API request. At the same time we clear the results when the search query is less than three characters long.

When we call the __searchItems()__ method to initiate the API request we assign true to the ___loading___ variable so that the content placeholder animations are displayed to inform the user that something is happening behind the scenes. After we get our response we populate the ___results___ and ___message___ variables depending on the type of response we receive, also we update the ___loading___ variable to false to stop the placeholder animations since we have the data to display at hand.

Next we display the data as subsequent grid rows to the search input which will appear as follows.
![search results with VueJs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/senjlovjq0t2mhdasxq1.png)

#### Some Optonal Tips to Apply in a Search in a VueJs Project ####
Here are some few tips that I might write an article on in the future but would just go brief on them currently.

 - When you apply this search feature in a VueJs project the search results will likely have links to other pages just as you would on any other web project, to dismiss them as you navigate to the other page you'll need to listen to the changes happening to [vue-router](https://router.vuejs.org)'s ___$route.path___ property as follows:

```vue
<script>
...
  watch: {
    '$route.path'(){
      // Dismiss search results
    }
  }
...
</script>
```

 - On the second tip you would query your server for all the items that exist in it and store them locally with the help of [vuex](https://vuex.vuejs.org) and a persistence plugin such as [vuex-persistedstate](https://github.com/robinvdvleuten/vuex-persistedstate) so that when the user reloads the website the data wont be lost.
 Afterwards for every subsequent search that will be performed by users the queries would be made to the item data pre-fetched and locally stored on the browser instead of making a new API call to your server on each search request.
 A point to be noted here is that this is a sound application if your database isn't being updated throughout the day, otherwise your users won't be able to query for the new added data.