Search code examples
javascriptsearch-form

How to filter elements using JavaScript filter?


I'm struggling to get this filter to work properly.

I have it set to filter and display only what's searched, based on the card titles (h5). It's filtering the unwanted titles out, but not the rest of the card.

To better explain, there's a demo here - JS Element Filter

Here's the Code:

function myFunction() {
    var input, filter, card, h5, a, i;
    input = document.getElementById("myFilter");
    filter = input.value.toUpperCase();
    card = document.getElementById("myItems");
    h5 = card.getElementsByTagName("h5");
    for (i = 0; i < h5.length; i++) {
        a = h5[i].getElementsByTagName("a")[0];
        if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
            h5[i].style.display = "";
        } else {
            h5[i].style.display = "none";
        }
    }
}
.container {
  padding: 10px;
}

ul li {
  list-style: none;
  
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div class="container">
  <div class="row">
    <div class="col-sm-12 mb-3">
      <input type="text" id="myFilter" class="form-control" onkeyup="myFunction()" placeholder="Search for names..">
    </div>
  </div>
  <div class="row" id="myItems">
    <div class="col-sm-12 mb-3">
      <div class="card">
        <div class="card-body">
          <h5 class="card-title"><a href="#">Card One</a></h5>
          <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
          <p class="card-text">Some text.</p>
        </div>
      </div>

      <div class="card">
        <div class="card-body">
          <h5 class="card-title"><a href="#">Card Two</a></h5>
          <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
          <p class="card-text">Some text.</p>
        </div>
      </div>

      <div class="card">
        <div class="card-body">
          <h5 class="card-title"><a href="#">Card Three</a></h5>
          <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
          <p class="card-text">Some text.</p>
        </div>
      </div>      
    </div>    
  </div>
</div> 


Solution

  • Cause

    You are only hiding the title (h5.card-title) and not the whole card (div.card)

    Solution

    First get a reference to the whole card. Then hide that element instead of just the title.

    Implementation A

    A quick and dirty solution to get a reference to the whole card would be to access it via the parentElement property. Since the parent of your <h5> is the card-body and it's parent is the entire card you acces it via h5[i].parentElement.parentElement.

    So change h5[i].style.display to h5[i].parentElement.parentElement.style.display like this:

    function myFunction() {
        var input, filter, card, h5, a, i;
        input = document.getElementById("myFilter");
        filter = input.value.toUpperCase();
        card = document.getElementById("myItems");
        h5 = card.getElementsByTagName("h5");
        for (i = 0; i < h5.length; i++) {
            a = h5[i].getElementsByTagName("a")[0];
            if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
                h5[i].parentElement.parentElement.style.display = "";
            } else {
                h5[i].parentElement.parentElement.style.display = "none";
            }
        }
    }
    .container {
      padding: 10px;
    }
    
    ul li {
      list-style: none;
      
    }
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <div class="container">
      <div class="row">
        <div class="col-sm-12 mb-3">
          <input type="text" id="myFilter" class="form-control" onkeyup="myFunction()" placeholder="Search for names..">
        </div>
      </div>
      <div class="row" id="myItems">
        <div class="col-sm-12 mb-3">
          <div class="card">
            <div class="card-body">
              <h5 class="card-title"><a href="#">Card One</a></h5>
              <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
              <p class="card-text">Some text.</p>
            </div>
          </div>
    
          <div class="card">
            <div class="card-body">
              <h5 class="card-title"><a href="#">Card Two</a></h5>
              <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
              <p class="card-text">Some text.</p>
            </div>
          </div>
    
          <div class="card">
            <div class="card-body">
              <h5 class="card-title"><a href="#">Card Three</a></h5>
              <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
              <p class="card-text">Some text.</p>
            </div>
          </div>      
        </div>    
      </div>
    </div> 

    Implementation B

    A more robust solution would be to iterate over the cards instead of over the titles. That way you directly have a reference to the card and don't have to fiddle with parentElements. When you only want to search on text it might also be useful to use the innerText property to acces the string of text inside your card titles.

    function myFunction() {
        var input, filter, cards, cardContainer, h5, title, i;
        input = document.getElementById("myFilter");
        filter = input.value.toUpperCase();
        cardContainer = document.getElementById("myItems");
        cards = cardContainer.getElementsByClassName("card");
        for (i = 0; i < cards.length; i++) {
            title = cards[i].querySelector(".card-body h5.card-title a");
            if (title.innerText.toUpperCase().indexOf(filter) > -1) {
                cards[i].style.display = "";
            } else {
                cards[i].style.display = "none";
            }
        }
    }
    .container {
      padding: 10px;
    }
    
    ul li {
      list-style: none;
    }
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <div class="container">
      <div class="row">
        <div class="col-sm-12 mb-3">
          <input type="text" id="myFilter" class="form-control" onkeyup="myFunction()" placeholder="Search for names..">
        </div>
      </div>
      <div class="row" id="myItems">
        <div class="col-sm-12 mb-3">
          <div class="card">
            <div class="card-body">
              <h5 class="card-title"><a href="#">Card One</a></h5>
              <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
              <p class="card-text">Some text.</p>
            </div>
          </div>
    
          <div class="card">
            <div class="card-body">
              <h5 class="card-title"><a href="#">Card Two</a></h5>
              <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
              <p class="card-text">Some text.</p>
            </div>
          </div>
    
          <div class="card">
            <div class="card-body">
              <h5 class="card-title"><a href="#">Card Three</a></h5>
              <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
              <p class="card-text">Some text.</p>
            </div>
          </div>      
        </div>    
      </div>
    </div>