Creating and integrating a Searchbar and a Dropdown list with JS

Felipe KoshinoFelipe Koshino
5 min read
  1. HTML page skeleton

    First post here on hashnode. For those interesting in JavaScript codes hope I can help you have some ideas for your own projects or even learn something out of this article. That said, let’s get to the point.

    Here is the following code that I used as skeleton to start:

     <div class="row">
         <!-- Searchbar -->
         <section class="col s4 dropDown">
             <input id="sInput" type="text" placeholder="Search for register:"/>
             <ul id="ulid"></ul>
         </section>
         <button class="col s4 sbtn" id="s1btn">Search</button>
    
     <!-- <select id="cat0" .. -->
         <!-- Dropdown List-->
         <select id="dropDown2" class="col s2 browser-default transparent consu">
             <option selected>Category:</option>
         </select>
         <button class="col s2 sbtn" id="s2btn">Search</button>
     </div>
    
     <table id="tbid"></table>
    

    Next, we have the format of the array used as data source:

     [{date:'20-06-2024', conta:'House', budgeted: '', paid: '', installments: '', category: 'Rent'},
     {date:'29-06-2024', conta:'MaxCompany', budgeted: '', paid: '', installment: '', category: 'Factory'},
     //..
     ]
    
  2. Search-bar script

    On this first component I wanted that for whatever character the user type in

    the search-input it will immediately find for matches on the array-data.

     const inBox = document.getElementById("sInput")
    
     inBox.addEventListener('change', () => {
         let search = inBox.value
         const found = dados.filter(item => item["bill"].toLowerCase().startsWith(busca))
     // ..
     }
    

    ps: The startsWith(string) method is case-sensitive so I used toLowerCase() on the filter function loop over the data-array element.

    To eliminate the repeated values in the search I used the following unique() function :

     const arr = []
         found.forEach(el => arr.push(el['bill']))
    
         const foundUnique = arr.filter((val,idx,ar) => {
             return ar.indexOf(val) === idx
         })
    

    Then I built the list of <li>’s so I can see the matched results of the search on a <li> dropdown list :

     const ulElement = document.getElementById("ulid")
    
     if(inBox.value != ""){
    
     if(busca.length == 0){
         ulElement.innerHTML = '<li class="item" style="cursor: pointer"></li>';
     } else {
         let liList = ulElement.innerHTML;
         for(const el in foundUnique){
             liList += '<li>' + foundUnique[el] + '</li>'
         }
         ulElement.innerHTML = comOpt
         inBox.textContent = ""
    
         let its = document.querySelectorAll("li")
         its.forEach(i => i.addEventListener("click", () => {
             ulElement.value = i.textContent
         }))
      }
     }
    

    At this stage we need to finish the Searchbar script building the ‘click’ event on the selected <li> that the user chose to click, removing all li’s inside the <ul>

    and updating the input search to the value selected after the <li> is clicked .

    Below I created the click-event over the <ul> so I can get the

    right <li> child-node clicked with the method .target, emptying the innerHTML property of <ul> and <table> at the end of the addEventListener() :

     const table = document.getElementById("tbid")
     let nodes = Array.from(ulElement.children);
     // nodes: all "li HTML Elements"
    
     let selected = -1;
     ulElement.addEventListener("click",function(e) {
         if (selected !== nodes.indexOf(e.target)) {
             selected = nodes.indexOf(e.target);
             inBox.value = e.target.innerText
    
             ulElement.innerHTML = ""
             table.innerHTML = ""
     } else {
         console.log("Already selected");
     }
    
     });
    

    Last to finished the Searchbar script it has to be a event added to the button-1 Search so the table can be render on the screen with the data already filtered.

    In this stage we must consider that maybe, before we search for a key word in the Searchbar, we had already used the Dropdown-List to search for information and we still have selected the option that was choose inside the tag <select>.

    Because both buttons have similar click-events I’ve created one function to handle both buttons and build the at the end the final table filtered.

     const btn1 = document.getElementById("s1btn")
     const btn2 = document.getElementById("s2btn")
    
     function fun() {
       table.innerHTML = ""
       let wcat = "" 
       let filt0 = []
         // Buttom 1  -  SearchInput
         if(inBox.value !== ""){
             wcat = inBox.value
    
             filt0 = dados.filter(el => {
         return el["bill"] === wcat
         })
    
     // Keep only the selected option on searchbar
     for (var i = 0, l = select0Opts.length; i < l; i++) {
         select0Opts[i].selected = select0Opts[i].defaultSelected;
     }
    
     } else {
         // Buttom 2  -  Dropdown List
         wcat = select0.value
    
         filt0 = dados.filter(el => {
             return el["category"] === wcat
         })
     }
    
     btn2.addEventListener("click",fun)
     btn1.addEventListener("click", fun)
    
  3. Drop-down-List script

    So, for the button-2 we already create the click-event above. But still we need to create the simple <select> structure with dynamic <option>’s came from

    our array-data. I need to filtered the category values so I can get no repeated values to insert as innerText inside each option tag.

     const unique = (item, idx, ar) => {
         return ar.indexOf(item) === idx
     }
    
     let categs0 = []
     let categs1 = []
             list0.forEach(el => {
                 categs0.push(el["category"])
             })
             categs1 = categs0.filter(unique)
    
             categs1.forEach(elem => {
                 const op0 = document.createElement("option")
                 op0.value = elem
                 op0.innerText = elem
    
                 select0.append(op0)
             });
    
  4. Filtering the data to the current month

    This part of the code can be located at the beginning of the file. Below I just built some regular expressions to handle cases where the getMonth method gives me a two character number, like November(11) or December(12).

     const list0 = dados.filter(el => {
                 const hj = new Date()
                 const mont = hj.getMonth() + 1
    
                 const rgx1 = /-[0-9]{2}-/
                 const rgx2 = /\d+/
                 const rgx3 = /0/
                 const rgx4 = /^0/
    
                 // 21-10-2024 <-- ..-10-...
                 let el1 = el["date"].match(rgx1)[0]
                 //deleting the minus charact..
                 let el2 = el1.match(rgx2)[0]
    
                 //months with 2 characteres: 10 to 12
                 if(el2.match(rgx4) === null){
                     let el3 = el2
                 } else {
                     //months with 1 character: 1 to 9
                     let el3 = el2.replace(rgx3,"")[0]
                 } 
                 if(mont === parseInt(el3)){
                     return el
                 }
             })
    
  5. Building the table with filtered rows

    As mention before, the callback function fun will be used for both buttons(1 and 2) then at the end of the function fun I write a nested forEach loop where the

    first forEach loop the row is built and in the second, the forEach creates the columns for each row.

     // Inside the function fun()
         function fun() {
             table.innerHTML = ""
    
         // ....
    
             filt0.forEach(e => {
                 const row0 = document.createElement("tr")
                 const ar = [e['bill'], e['paid'], e['date'], e['category']]
    
                 ar.forEach(item => {
                     const col0 = document.createElement("td")
                     col0.textContent = item
                     row0.append(col0)
                     row0.style.fontSize = "0.8rem"
                    })
    
                 table.append(row0)
                 table.className = "tabList"
              })
    

    And here we have finished our two components that work independently and also you can edit adding your own preferable css style. Feel free to contact me if you have any doubts, wish you tudo de bom. =]

0
Subscribe to my newsletter

Read articles from Felipe Koshino directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Felipe Koshino
Felipe Koshino