Search Filter Javascript - Search for multiple texts inside a cards and

456 views Asked by At

I'm currently trying to make a dynamic search bar with JavaScript which can search inside cards, the title and multiple keywords.

I currently have something but it doesn't seem to work properly. When I try searching for something even though the input value matches with the title or any of the keywords it still doesn't show it.

For example, if one card contains "docker" in the title and the other card contains "docx" in the title, and I'd type 'do' it would show both but when I type 'doc', the docx card gets removed and it only shows the docker card.

Here I show only two cards, but in the project there are more of them:

function search_tool(el) {
  const cards = document.querySelectorAll('.cards .card')
  for (let card of cards) {
    const texts_to_search_for = card.querySelectorAll('.card-title-left a, .card .keywords > span')

    texts_to_search_for.forEach(txt => {
      if (txt.innerText.toUpperCase().indexOf(el.value.toUpperCase()) > -1) {
        card.style.display = ''
      } else {
        card.style.display = 'none'
      }
    })
  }
}
<div class="cards">
  <div class="card">
    <div class="card-title">
      <div class="card-title-left">
        <a href="Card path">Card Title</a>
        <i class="fa-solid fa-heart"></i>
      </div>
    </div>
    <div class="keywords">
      <span>keyword1</span>
      <span>keyword2</span>
      <span>keyword3</span>
      <span>keyword4</span>
      <span>keyword5</span>
    </div>
  </div>
  <div class="card">
    <div class="card-title">
      <div class="card-title-left">
        <a href="Card path">Card 2</a>
        <i class="fa-solid fa-heart"></i>
      </div>
    </div>
    <div class="keywords">
      <span>keyword1</span>
      <span>keyword2</span>
      <span>keyword3</span>
      <span>keyword4</span>
      <span>keyword5</span>
    </div>
  </div>
</div>

It runs every time we type something in the input. el parameter is the input element.

I wanted to know if there's something I could do to make it work while having this code or do I need to remove the code completely.

Thank you in advance.

1

There are 1 answers

1
David Thomas On BEST ANSWER

The problem seems to be:

texts_to_search_for.forEach(
  (txt) => {
    if (...) {
        // if there is a match on one iteration the
        // the card will be shown (supplying an
        // invalid display property-value causes the
        // display property to be unset/removed):
        card.style.display = ''
      } else {
        // any subsequent iteration in which does not
        // satisfy the `if` condition will then hide
        // the whole card:
        card.style.display = 'none'
      }
});

I'd suggest rewriting to use Array methods, as follows – with explanatory comments in the code – specifically using Array.prototype.some():

// defining the search_tool function with an Arrow expression, passing the Event Object from
// EventTarget.addEventListener() to the function body:
const search_tool = (evt) => {
  // retrieving all the .card elements:
  const cards = document.querySelectorAll('.cards .card'),
    // retrieving the input value here, once, for comparison, removing leading/trailing
    // white-space and converting to upper-case:
    value = evt.currentTarget.value.trim().toUpperCase();
    
  // iterating over the cards:
  for (let card of cards) {
    // using an Array literal, with the spread operator, to convert the
    // iterable NodeList into an Array:
    const texts_to_search_for = [...card.querySelectorAll('.card-title-left a, .card .keywords > span')];

    // Array.prototype.some() takes the supplied array-element and returns a Boolean if any element
    // of that Array returns a true value for the supplied test:
    if (texts_to_search_for.some(
        // here we pass in a reference to the Array-element ('txt') to the function body,
        // we find the element's textContent, convert that text to upper-case using
        // String.prototype.toUpperCase(), and then we test to see if that String
        // includes the string contained in the variable of 'value', using
        // String.prototype.includes():
        (txt) => txt.textContent.toUpperCase().includes(value)
      )) {
      // if any of the element node's text-content contains the 'value' String
      // we unset the display property:
      card.style.display = '';
    } else {
      // otherwise we set the display to 'none':
      card.style.display = 'none';
    }
  }
}

// here we use EventTarget.addEventListener() to bind the search_tool() function (note the deliberate
// lack of parentheses) as the event-handler for the 'input' event:
document.querySelector('.search input').addEventListener('input', search_tool);
*, ::before,::after {
  box-sizing: border-box;
  font-family: system-ui;
  font-size: 16px;
  margin: 0;
  padding: 0;
}

main {
  display: grid;
  gap: 1em;
  inline-size: clamp(15em, 70vw, 1000px);
  margin-block: 1em;
  margin-inline: auto;
}

.cards {
  display: flex;
  flex-flow: row wrap;
  gap: 1em;
}

.card {
  border: 1px solid #000;
  border-radius: 1em;
  flex-basis: 30%;
  flex-grow: 1;
  padding: 0.5em;
}

.keywords {
  display: flex;
  flex-basis: fit-content;
  flex-flow: row wrap;
  flex-grow: 1;
  gap: 0.5em;
}

.keywords span {
  border: 1px solid #000;
  border-radius: 0.5em;
  padding: 0.25em;
}
<main>
  <div class="search">
    <label>
      <span class="labelText">Search:</span>
      <input type="text">
    </label>
  </div>
  <div class="cards">
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 1</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>odio laborum</span>
        <span>dignissimos sint</span>
        <span>iusto error</span>
        <span>aliquid eligendi</span>
        <span>sit mollitia</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 2</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>sint id</span>
        <span>vitae mollitia</span>
        <span>sit amet,</span>
        <span>dignissimos Deserunt</span>
        <span>laborum eligendi</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 3</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>esse adipisicing</span>
        <span>Tempore laborum</span>
        <span>quia elit</span>
        <span>eligendi amet,</span>
        <span>doloribus doloribus</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 4</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>Lorem aliquid</span>
        <span>eligendi deleniti</span>
        <span>dignissimos error</span>
        <span>iste Lorem</span>
        <span>Deserunt aliquid</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 5</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>Deserunt minus</span>
        <span>odio minus</span>
        <span>Tempore explicabo</span>
        <span>Deserunt amet,</span>
        <span>deleniti amet,</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 6</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>Deserunt dolor</span>
        <span>quia odio</span>
        <span>esse esse</span>
        <span>odio vel</span>
        <span>sint explicabo</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 7</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>doloribus error</span>
        <span>mollitia sint</span>
        <span>Tempore aliquid</span>
        <span>amet, vitae</span>
        <span>amet, explicabo</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 8</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>eligendi dignissimos</span>
        <span>error minus</span>
        <span>dignissimos mollitia</span>
        <span>Tempore amet,</span>
        <span>veritatis iusto</span>
      </div>
    </div>
  </div>
</main>

JS Fiddle demo.

However, I would personally rewrite the above to the following, with explanatory comments in the code:

// defining the search_tool function with an Arrow expression, passing the Event Object from
// EventTarget.addEventListener() to the function body:
const search_tool = (evt) => {
  // retrieving all the .card elements:
  const cards = document.querySelectorAll('.cards .card'),
    // retrieving the input value here, once, for comparison, removing leading/trailing
    // white-space and converting to upper-case:
    value = evt.currentTarget.value.trim().toUpperCase();

    // iterating over the NodeList of .card elements:
  cards.forEach(
    // using an Arrow function, and passing a reference to the
    // current Array-element into the function body:
    (el)=>
        // here we use the HTMLElement.hidden property to determine whether to hide,
      // or show, each ,card element in turn.
            // first we convert the iterable result of Element.querySelectorAll() into
      // an Array, using the spread operator with an Array literal, and then
      // call Array.prototype.map() to create a new Array based on the first Array.
      // When the final Array is passed to Array.prototype.some(), that method
      // will return a Boolean, which we then invert using the NOT operator
      // (because assigning true will hide the element, and we want to show the
      // elements that include the value we're looking for and hide those
      // that don't):
        el.hidden = ![...el.querySelectorAll('a, span')].map(
        // here we trim the text-content of the elenebt, and convert it to
        // uppercase:
        (el) => el.textContent.trim().toUpperCase()
      // we then use Array.prototype.some() to return a Boolean result based
      // on the Array-elements:
      ).some(
        // here, if the current String includes the input-value, we return
        // true; if that value is not contained this test returns false;
        // if any array-element (String) includes the value then Array.prototype.some()
        // returns true:
        (string) => string.includes(value)
      )
    )
}

// here we use EventTarget.addEventListener() to bind the search_tool() function (note the deliberate
// lack of parentheses) as the event-handler for the 'input' event:
document.querySelector('.search input').addEventListener('input', search_tool);
*,
::before,
::after {
  box-sizing: border-box;
  font-family: system-ui;
  font-size: 16px;
  margin: 0;
  padding: 0;
}

main {
  display: grid;
  gap: 1em;
  inline-size: clamp(15em, 70vw, 1000px);
  margin-block: 1em;
  margin-inline: auto;
}

.cards {
  display: flex;
  flex-flow: row wrap;
  gap: 1em;
}

.card {
  border: 1px solid #000;
  border-radius: 1em;
  flex-basis: 30%;
  flex-grow: 1;
  padding: 0.5em;
}

.keywords {
  display: flex;
  flex-basis: fit-content;
  flex-flow: row wrap;
  flex-grow: 1;
  gap: 0.5em;
}

.keywords span {
  border: 1px solid #000;
  border-radius: 0.5em;
  padding: 0.25em;
}
<main>
  <div class="search">
    <label>
      <span class="labelText">Search:</span>
      <input type="text">
    </label>
  </div>
  <div class="cards">
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 1</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>odio laborum</span>
        <span>dignissimos sint</span>
        <span>iusto error</span>
        <span>aliquid eligendi</span>
        <span>sit mollitia</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 2</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>sint id</span>
        <span>vitae mollitia</span>
        <span>sit amet,</span>
        <span>dignissimos Deserunt</span>
        <span>laborum eligendi</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 3</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>esse adipisicing</span>
        <span>Tempore laborum</span>
        <span>quia elit</span>
        <span>eligendi amet,</span>
        <span>doloribus doloribus</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 4</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>Lorem aliquid</span>
        <span>eligendi deleniti</span>
        <span>dignissimos error</span>
        <span>iste Lorem</span>
        <span>Deserunt aliquid</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 5</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>Deserunt minus</span>
        <span>odio minus</span>
        <span>Tempore explicabo</span>
        <span>Deserunt amet,</span>
        <span>deleniti amet,</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 6</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>Deserunt dolor</span>
        <span>quia odio</span>
        <span>esse esse</span>
        <span>odio vel</span>
        <span>sint explicabo</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 7</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>doloribus error</span>
        <span>mollitia sint</span>
        <span>Tempore aliquid</span>
        <span>amet, vitae</span>
        <span>amet, explicabo</span>
      </div>
    </div>
    <div class="card">
      <div class="card-title">
        <div class="card-title-left">
          <a href="Card path">Card 8</a>
          <i class="fa-solid fa-heart"></i>
        </div>
      </div>
      <div class="keywords">
        <span>eligendi dignissimos</span>
        <span>error minus</span>
        <span>dignissimos mollitia</span>
        <span>Tempore amet,</span>
        <span>veritatis iusto</span>
      </div>
    </div>
  </div>
</main>

JS Fiddle demo.

References: