Reputation: 3166
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>
Upvotes: 2
Views: 17763
Reputation: 4785
You are only hiding the title (h5.card-title
) and not the whole card (div.card
)
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>
Upvotes: 2
Reputation: 28722
You are hiding the titles. What you want to hide is the card.
You can do this by doing h5[i].parentNode.parentNode since the title is nested 2 layers deep in the card.
If your clients browsers support it you can use Closest ancestor matching selector using native DOM?
or if you have jQuery at your disposal you can search for $(h5[i]).closest('.card');
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++) {
var current = h5[i];
a = current.getElementsByTagName("a")[0];
if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
current.parentNode.parentNode.style.display = "";
} else {
current.parentNode.parentNode.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>
But I would actually recommend you by using getElementsByClassName
to get all the cards, then loop through them, select the h5 element by the class name and then accessing the innerText
of the h5, that way non visible text like a link title="some thing cool here"
won't get in the way.
See the below snippet.
function myFunction() {
var input, filter, myItems, cards, i, current, h5, text;
input = document.getElementById("myFilter");
filter = input.value.toUpperCase();
myItems = document.getElementById("myItems");
cards = myItems.getElementsByClassName("card");
for (i = 0; i < cards.length; i++) {
current = cards[i];
h5 = current.getElementsByClassName('card-title')[0];
text = h5.innerText.toUpperCase();
if (text.indexOf(filter) > -1) {
current.style.display = "";
} else {
current.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>
Upvotes: 0
Reputation: 487
The problem here is that you're only setting display:none
and vice versa to the h5
and not the whole card itself.
See here
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].closest(".card").style.display = "";
} else {
h5[i].closest(".card").style.display = "none";
}
}
}
I have changed it so that it gets its closest parent which has a class of .card
here
h5[i].closest(".card")
Alternatively, if your target browser does not support .closest
you can use
h5[i].parentNode.parentNode.style.display = "none"
See the full Codepen I have forked here: https://codepen.io/anon/pen/gjKgjN
Upvotes: 0
Reputation: 257
You must hide the whole card container instead of hiding the heading (h5) only.
A quick correction would be using parentNode
on the heading, for example
if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
h5[i].parentNode.style.display = "";
} else {
h5[i].parentNode.style.display = "none";
}
Upvotes: 0