Vue.js Search Keywords Highlight in API Response
Date Published: 10/11/2019
This is a small demo of how to use vue-text-highlight package to highlight the search keywords in the HTTP API response after applying a few rules to avoid highlighting certain text i.e. stopwords ('a', 'an', 'and' etc.).

https://github.com/fullstackhub-io/vue-text-highlight-example

Introduction

In this tip, we will quickly look into Vue.js vue-text-highlight package to highlight the search text/keywords in the API response. We will also implement fewer rules e.g. ignore the stopwords (e.g. and, an, or, a, with, etc.) and divide the keywords further in case of the comma-separated text. Here is the final page we will be developing, you can see the search ignores the and, the and comma(,) in search text:

Let's Start

  1. I am not going into basic how to install the Vue.js, please follow the https://cli.vuejs.org guidelines.
  2. After successfully installing and verifying the Vue.js, let's create a new Vue.js project by command: vue create texthighlight or vue ui to create the project through web page UI. Make sure you have the latest Vue.js CLI version in order to use vue create command. Select the default options while creating the project.
  3. let's delete the default HelloWorld.vue file, create two new files and update the App.vue:
    1. SearchPage.vue: This component will have one textbox where you can enter any text to highlight and search button next to it. Please remember, we are not going to perform an actual search, the backend API would always return the same result, this demo will only perform text highlight functionality. 
    2. SearchResult.vue: This component will use vue-text-highlight package to highlight the search text. There would be few rules to avoid highlighting the certain text that we will look later.
    3. App.vue: App component will have an updated code to act as a bridge between SearchPage and SearchResult components, it will take the search text from SeachPage and pass it to the SearchResult component. 
  4. We need two node packages to work with this demo, one is vue-text-highlight for whom I am writing this post, second is vue-axios to make an HTTP call. Go to View -> Terminal (if you are using Visual Studio Code), make sure your project folder is selected, run the following command: npm install vue-Axios vue-text-highlight --save
  5. Before creating the component, let's add the Bootstrap CSS and JS for better UI. edit the public -> index.html file and replace the code with following, only update is bootstrap.min.css and bootstrap.min.js CDN references in the head section:   
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width,initial-scale=1.0">
      <link rel="icon" href="<%= BASE_URL %>favicon.ico">
      <script src="https://code.jquery.com/jquery-2.2.4.min.js"
      integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
        integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
      <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
        integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
        crossorigin="anonymous"></script>
    
    
      <title>texthighlight</title>
    </head>
    
    <body>
      <noscript>
        <strong>We're sorry but texthighlight doesn't work properly without JavaScript enabled. Please enable it to
          continue.</strong>
      </noscript>
      <div id="app"></div>
      <!-- built files will be auto injected -->
    </body>
    
    </html>
  6. Next, in the component folder, create the SearchPage.vue file and add the following code in it:
    <template>
      <div class="content">
        <div class="row">
          <div class="col-md-3"></div>
          <div class="col-md-3">
            <input
              type="text"
              class="form-control TextBox"
              placeholder="Search for..."
              v-model="searchString"
            />
          </div>
          <div class="col-md-1">
            <input type="submit" class="btn btn-primary" value="Search" v-on:click="handleSubmit" />
          </div>
          <div class="col-md-4">
            <div style="width:400px" class="alert alert-info" role="alert"><b>Example:</b> amygdala, insula, and the ventral</div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: "search-page",
      data() {
        return {
          searchString: ""
        };
      },
      methods: {
        handleSubmit() {
          this.$emit("searchstring", this.searchString);
        }
      }
    };
    </script>
    
    <style scoped>
    .TextBox {
      width: 450px;
    }
    .content {
      padding-top: 20px;
      padding-bottom: 20px;
    }
    </style>
  7. As we know, there are three basic sections in the Vue.js file; template, script and style. In the template, we are adding our HTML that has one textbox and a search button with hint text for this demo. The script has one variable searchString having user-entered search text and handleSubmit() event that is bonded to the submit button. The searchString variable is two ways bounded to textbox through v-model directive. In handleSubmit() function, we are only emitting the user-entered text as an output variable searchstring. In the App component, we will get value from searchstring variable and send it to SearchResult component, pretty simple. 
  8. Next, in the same component folder, create a file SearchResult.vue and add the following code: 
    <template>
      <div v-if="searchString != ''" class="maincontent">
        <div class="row" style="padding-bottom:10px">
          <div class="col-md-2"></div>
          <div class="col-md-8" style="text-align:left">
            <b>Keywords:</b> &nbsp;
            <span
              v-for="item of stringToCSV()"
              :key="item.title"
              class="keywords badge badge-pill badge-info"
            >{{item}}</span>
          </div>
          <div class="col-md-2"></div>
        </div>
        <div class="row">
          <div class="col-md-2"></div>
          <div class="card col-md-8">
            <div class="container">
              <h4>
                <b>
                  Title:
                  <text-highlight :queries="stringToCSV()">{{posts.title}}</text-highlight>
                </b>
              </h4>
              <p>
                <span class="abstractText">Abstract:</span>
                <br />
                <text-highlight :queries="stringToCSV()">{{posts.abstract}}</text-highlight>
              </p>
            </div>
          </div>
          <div class="col-md-2"></div>
        </div>
      </div>
    </template>
    
    <script>
    import axios from "axios";
    import Vue from "vue";
    import TextHighlight from "vue-text-highlight";
    
    Vue.component("text-highlight", TextHighlight);
    
    export default {
      name: "search-result",
      props: {
        searchString: String
      },
      data() {
        return {
          posts: [],
          errors: []
        };
      },
      methods: {
        stringToCSV() {
          var stopWords = [
            "a",
            "an",
            "and",
            "are",
            "as",
            "at",
            "be",
            "but",
            "by",
            "for",
            "if",
            "in",
            "into",
            "is",
            "it",
            "no",
            "not",
            "of",
            "on",
            "or",
            "such",
            "that",
            "the",
            "their",
            "then",
            "there",
            "these",
            "they",
            "this",
            "to",
            "was",
            "will",
            "with"
          ];
    
          this.searchString = this.searchString.replace(",", " ");
          var inputKeywords = this.searchString.split(" ");
          var finalKeywords = inputKeywords.filter(el => !stopWords.includes(el));
          return finalKeywords;
        }
      },
      created() {
        axios
          .get(
            `https://api.federalreporter.nih.gov/v1/Projects/search?query=orgName%3ABOSTON%20UNIVERSITY&limit=1`
          )
          .then(response => {
            this.posts = response.data.items[0];
          })
          .catch(e => {
            this.errors.push(e);
          });
      }
    };
    </script>
    
    <style scoped>
    .abstractText {
      font-size: 14pt;
      font-weight: bold;
    }
    .maincontent {
      padding-top: 20px;
    }
    .card {
      box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
      transition: 0.3s;
    }
    
    .card:hover {
      box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
    }
    
    .container {
      padding: 2px 16px;
      width: 90%;
    }
    .keywords {
      font-size: 12pt;
      height: 30px;
      padding-top: 6px;
      margin-right: 5px;
    }
    </style>
  9. In SearchResult.vue file, we are making the HTTP GET request to one of NIH reporter API for an example, you can replace it with your API with search text as well (we are not doing any kind of search to keep the things simple).
    1. In the template section, we are checking if there is no search text entered, don't show anything otherwise call stringtoCSV() method to show the user-entered keywords. There are some small business rules implemented in stringToCSV() function; e.g. we don't want to highlight stop words e.g. 'a', 'an', 'at' etc. Second, if a user searches with comma-separated text, we are also taking it as a separate keyword by splitting it into multiple words. 
    2. Once the keywords are filtered, we are passing the CSV to the text-highlight component (from vue-text-highlight package) on posts.title and post.abstract properties. The posts object is returned by a created() event where we are making a HTTP call to NIH API through AXIOS HTTP client, you can use any HTTP client. 
    3. We defined the searchString property that is coming from SearchPage.vue component through App.vue. It contains the search text.
    4. The style has self-explanatory CSS to make a reasonable UI.
  10. Edit the App.vue and replace its content with the following: 
    <template>
      <div id="app">
        <search-page @searchstring="getkeywords" />
        <search-result :searchString="searchString" />
      </div>
    </template>
    
    <script>
    import SearchPage from "./components/SearchPage.vue";
    import SearchResult from "./components/SearchResult.vue";
    
    export default {
      name: "app",
      components: {
        SearchPage,
        SearchResult
      },
      data() {
        return {
          searchString: ""
        };
      },
      methods: {
        getkeywords(keywords) {
          this.searchString = keywords;
        }
      }
    };
    </script>
    
    <style>
    #app {
      font-family: "Avenir", Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
  11. Here, we are importing both SearchPage and SearchResult components, getting the search text from SearchPage component in getkeywords() functions and assigning it to the local searchString variable, later we are passing this searchString variable to SearchResult component as an input property.
  12. That's pretty much it, run the npm run serve command in a terminal, copy the URL from App running at: in the terminal and test the application. 


Keywords: vue js text highlight, text highlight in vuejs, vue-text-highlight example,vue keywords highlight