Weighted Keyword Search with JavaScript

Posted by admin at January 10, 2021

I previously wrote an article on how to build a search engine in javascript that allow the user filter contents on a table using the onKeyUp event Listener. The search process only determined if the needle string has a strict match in the haystack string.


                // pull the needle string from the input provided
                let needleString = event.target.value;

                // pull the haystack from the innerHTML of the row
                let haystackString = row.innerHTML;

                // hide all rows
                row.hidden = true;

                // strict search : searches for a copy of the
                // needle string in the haystack
                if (haystackString).inlcudes(needleString)) {
                    // ...
                    row.hidden = false;
                }

The article provided details on how to do this and can be found here Instantaneous search with DOM API and JS.

However, what if the user wants to return rows that contain any of the words entered into the search input box. This is similar to the popular search engines like Google. The needle string entered into the search box is broken down into the constituent keywords and each keyword is searched in the haystack string.

In this article we are going to be building a JavaScript-based search engine for filtering tables contents. Using the String.split() method, each search string will be across a specified delimiter, returning an array of keywords. Each keyword is searched against the haystack using the String.includes() method. In order to automate the visibility of the table rows, (showing rows when they match, hiding them when they don’t), we will be relying on the provisions of the VueJS framework.

To start off we include the Vue framework to our base HTML

<script type="javascript" scr="./path/vue.js" />

Next, we define the search box, the text input to collect user searches. We set the v-model property to point to a node in the data store named “keywords”, this is where the search string will be stored.

<!--  -->
   <input
      type="text" class="input"
      id="search-box" name="search-box"
      placeholder="enter search keywords..."
      v-model="keywords"
      />
<!--  -->

Then we define the table which will iterate through a list of items and list them in rows.

<!--  -->
    <div id="app">
        <table>

            <!-- table header -->
            <tr>
                <td v-html="'#'"></td>
                <td v-html="'Name'"></td>
                <td v-html="'Type'"></td>
                <td v-html="'Specialty'"></td>
           </tr>

           <!-- the list of items -->
           <tr v-for="(item, idx) in list" >
                <td v-html="idx"></td>
                <td v-html="item.name"></td>
                <td v-html="item.type"></td>
                <td v-html="item.specialty"></td>
           </tr>
       </table>
     </div>
     <!--  -->

Next, we setup the Vue component.

let app = new Vue({
    el: "app",

    // the data store
    data: {

        // stores the keywords entered into the search box
        keywords: "",

        // stores the data items to be listed
        list: {
            {
                "name": "Yurich Manakov",
                "specialty": "Airworthiness",
                "type": "Management",
            },
            {
                "name": "Hank Fracas",
                "specialty": "Air Traffic Services",
                "type": "Inspector",
            },
            {
                "name": "Magellan VanLandingham",
                "specialty": "Flight Operations",
                "type": "Management",
            },
            {
                "name": "Carl Discotheque",
                "specialty": "Aerodromes",
                "type": "Inspector",
            },
            {
                "name": "Teddy Arugula",
                "specialty": "Meteorology and Aviation Security",
                "type": "Inspector",
            },
            {
                "name": "Ludwig Von Instagram",
                "specialty": "Aeronautical Telecommunications",
                "type": "Inspector",
            },
            {
               "name": "Carlos Waterside",
               "specialty": "Aeronautical Information Services",
               "type": "Inspector",
            }
        },

     },

     // the methods
     method: {

         /**
         * Keyword Search
         * This will perform the keyword search using the contents
         * of the keywords node in the data store
         * 
         * @param object
         * @return bool
         */ 
         keywordSearch: (object) {

         },
    },

    // computed
    computed: {

        /**
        * Filtered List
        * This is a Vue computed method that watches for changes in the keywords
        * and modified the filtered list items to be displayed in the DOM
        * 
        * @param null
        * @return Object Array
        */ 
        filteredList: () {

        },
    },
})

Next we define the keyword search method.

/**
   * Keyword Search
   * This will perform the keyword search using the contents
   * of the keywords node in the data store
   * 
   * @param object
   * @return bool
   */ 
   keywordSearch: (object) {

       // define the haystack
       let haystack = Object

          // fetch all the values in the object
          .values(object)

          // merge the array into a string, the separator here does not matter
          .join("~")

          // convert to lowercase
          .toLowerCase()

       // initialize the separator, which for words in a sentence is a space, " "
       let sep = " "

       // if the search box is empty then return true
       // so that the object row can be shown
       if (
          (app.keywords === undefined)
          || (app.keywords === null)
          || (app.keywords === "")
       ) { return true}

          // prepare the needle
          let keywordList = app.keywords.toLowerCase().split(sep)

          // the total number of matches
          let matches = 0

          // iterate through the words in the search 
          Array.from(keywordList).forEach((word, wid) => {

             // perform the search
             if (haystack.includes(word)) {

                // if a match is made, return true and end the method
                matches++;
             }
          })

          // matches were found for all the keyword items sent
          if (matches === keywordList.length) {
             // return true
             return true
          }

          // non or incomplete  matches, return false
          return false
},

Then is the Filtered List method. Instead of creating eventListeners for user input in the search box, we will be relying on the Vue Computed property. This makes it easier to automate as Vue handles the row visibility of each row in the table based on a match to the keyword string.

/**
    * Filtered List
    * This is a Vue computed method that watches for
    * changes in the keywords
    * and modified the filtered list items to be displayed in the DOM
    * 
    * @param null
    * @return Object Array
    */ 
    filteredList: () {
        // initialize the list to be returned
        filtered = []

        // if the search box is empty
        if (this.keywords.length <= 0) {

            // return the entire list
            return this.list
                            
        }
                        
         // iterate through the data store
         Array
             .from(this.list)
             .forEach(
                 (item, iid) => {

                 // if the search has a match
                 if (this.keywordSearch(item)) {

                     // put the matched item to the returned list
                     filtered.push(item)

                 }
          })
      }

      return filtered
   },

After this is done, our Vue instance will look like this:

let app = new Vue({
    el: "app",

    // the data store
    data: {

       // stores the keywords entered into the search box
       keywords: "",

       // stores the data items to be listed
       list: {
          {
             "name": "Ulrich Manakov",
             "specialty": "Airworthiness",
             "type": "Management",
          },
          {
             "name": "Hank Fracas",
             "specialty": "Air Traffic Services",
             "type": "Inspector",
          },
          {
             "name": "Magellan VanLandingham",
              "specialty": "Flight Operations",
              "type": "Management",
          },
          {
              "name": "Carl Discotheque",
              "specialty": "Aerodromes",
              "type": "Inspector",
          },
          {
              "name": "Teddy Arugula",
              "specialty": "Meteorology and Aviation Security",
              "type": "Inspector",
          },
          {
              "name": "Ludwig Von Instagram",
              "specialty": "Aeronautical Telecommunications",
              "type": "Inspector",
          },
          {
              "name": "Carlos Waterside",
              "specialty": "Aeronautical Information Services",
              "type": "Inspector",
          },
       },
    },

    // the methods
    method: {

        /**
        * Keyword Search
        * This will perform the keyword search using the contents
        * of the keywords node in the data store
        * 
        * @param object
        * @return bool
        */ 
        keywordSearch: (object) {

           // define the haystack
           let haystack = Object

               // fetch all the values in the object
               .values(object)

               // merge the array into a string, thge seoarator here does not matter
              .join("~")

              // convert to lowercase
              .toLowerCase()

           // initialize the separator, which for words in a sentence is a space, " "
           let sep = " "

          // if the search box is empty then return true
          // so that the object row can be shown
        if (
           (app.keywords === undefined)
           || (app.keywords === null)
           || (app.keywords === "")
        ) { return true}

           // prepare the needle
           let keywordList = app.keywords.toLowerCase().split(sep)

           // the total number of matches 
           let matches = 0

           // iterate through the words in the search 
           Array.from(keywordList).forEach((word, wid) => {

              // perform the search
              if (haystack.includes(word)) {

                 // if a match is made, return true and end the method
                 matches++;
              }
          })

          // matches were found for all the keyword items sent
          if (matches === keywordList.length) {
             // return true
             return true
          }

          // non or incomplete  matches, return false
          return false
        },
    },

    // computed
    computed: {

        /**
        * Filtered List
        * This is a Vue computed method that watches for
        * changes in the keywords
        * and modified the filtered list items to be displayed in the DOM
        * 
        * @param null
        * @return Object Array
        */ 
        filteredList: () {
           // initialize the list to be returned
           filtered = []

           // if the search box is empty
           if (this.keywords.length <= 0) {

               // return the entire list
               return this.list
                       
           }

           // iterate through the data store
           Array
              .from(this.list)
              .forEach(
                 (item, iid) => {

                 // if the search has a match
                 if (this.keywordSearch(item)) {

                     // put the matched item to the returned list
                     filtered.push(item)

                }
            })
            // return the filtered list
            return filtered
        },
    },
})

Then we go back to the table HTML and edit the iterated object, replacing the raw list from the data store with the computed filteredList

<!--  -->
    <!-- the list of items -->
    <tr v-for="(item, idx) in filteredList" >
        <!-- ... -->
    </tr>
    <!--  -->

And we are done. Putting it all together, we will have the following:

<!-- include Vue -->
    <script type="javascript" scr="./path/vue.js" />


    <!-- search box -->
    <input
       type="text" class="input"
       id="search-box" name="search-box"
       placeholder="enter search keywords..."
       v-model="keywords"
       />


    <!-- table HTML -->
    <div id="app">
        <table>

        <!-- table header -->
        <tr>
            <td v-html="'#'"></td>
            <td v-html="'Name'"></td>
            <td v-html="'Type'"></td>
            <td v-html="'Specialty'"></td>
        </tr>

        <!-- the list of items -->
        <tr v-for="(item, idx) in filteredList" >
            <td v-html="idx + 1"></td>
            <td v-html="item.name"></td>
            <td v-html="item.type"></td>
            <td v-html="item.specialty"></td>
        </tr>
    </table>
    <!--  -->
</div>
let app = new Vue({
   el: "app",

   // the data store
   data: {

      // stores the keywords entered into the search box
      keywords: "",

      // stores the data items to be listed
      list: {
         {
             "name": "Ulrich Manakov",
             "specialty": "Airworthiness",
             "type": "Management",
         },
         {
             "name": "Hank Fracas",
             "specialty": "Air Traffic Services",
             "type": "Inspector",
         },
         {
             "name": "Magellan VanLandingham",
              "specialty": "Flight Operations",
              "type": "Management",
         },
         {
              "name": "Carl Discotheque",
              "specialty": "Aerodromes",
              "type": "Inspector",
         },
         {
              "name": "Teddy Arugula",
              "specialty": "Meteorology and Aviation Security",
              "type": "Inspector",
         },
         {
              "name": "Ludwig Von Instagram",
              "specialty": "Aeronautical Telecommunications",
              "type": "Inspector",
         },
         {
              "name": "Carlos Waterside",
              "specialty": "Aeronautical Information Services",
              "type": "Inspector",
         },
      },
   },

   // the methods
   method: {

      /**
      * Keyword Search
      * This will perform the keyword search using the contents
      * of the keywords node in the data store
      * 
      * @param object
      * @return bool
      */ 
      keywordSearch: (object) {

         // define the haystack
         let haystack = Object

            // fetch all the values in the object
            .values(object)

            // convert to lowercase
            .toLowerCase()

        // initialize the separator, which for words in a sentence is a space, " "
        let sep = " "

         // prepare the needle
         let keywordList = this.keywords.split(sep)

         foreach(word in keywordList) {

            // perform the search
            if (haystack.includes(word)) {
                return true;
            }
         }

         return false
      },
   },

   // computed
   computed: {

      /**
      * Filtered List
      * This is a Vue computed method that watches for changes in the keywords
      * and modified the filtered list items to be displayed in the DOM
      * 
      * @param null
      * @return Object Array
      */ 
      filteredList () {
          // initialize the list to be returned
          filtered = []

          // if the search box is empty
          if (this.keywords.length <= 0) {

              // return the entire list
              return this.list
                            
          }

          // iterate through the data store
          Array
              .from(this.list)
              .forEach(
                  (item, iid) => {

                      // if the search has a match
                      if (this.keywordSearch(item)) {

                          // put the matched item to the returned list
                          filtered.push(item)

                      }
           })

           // return the filtered list
           return filtered
        },

      },

   },
})
   0 likes

Suggested Read