I found code online that filters elements by their text content.
How can I display a message when there is no match?
$("button").click(function() {
var value = $(this).data('value').toUpperCase();
$("div").filter(function(index) {
$(this).toggle($(this).text().indexOf(value) > -1)
});
});
<button>example</button>
You're using filter()
to toggle each item based on state, like using each()
. But one advantage of filter()
is that you can return a reduced selection and count the items it contains. That value can determine whether a "no match" message should be displayed.
... the .filter() method constructs a new jQuery object from a subset of the matching elements. The supplied selector is tested against each element; all elements matching the selector will be included in the result. -- filter().
For each element, if the function returns true (or a "truthy" value), the element will be included in the filtered set; otherwise, it will be excluded. -- Using a Filter Function
So, instead of toggling items directly from the filter call, consider returning a Boolean measure of whether the current item is a match. Save the resulting filtered selection in a variable. After filtering, you can toggle that selection as a whole:
var $filtered = $items.filter(function() {
return $(this).text().indexOf(value) > -1;
});
$items.toggle(false);
$filtered.toggle(true);
This hides all items and then shows only the filtered items.
You might even consider some fading animation:
$items.hide(250);
$filtered.stop(true,false).show(250);
Then you can reference the filtered selection's length
.
If it's zero, show the "not found" message:
var hasMatches = $filtered.length;
if (hasMatches) {
// there were matches.
} else {
// no matches.
}
You can also pass a selector to a filter. jQuery's :contains()
selector selects "all elements that contain the specified text", which makes a nice choice.
Working Example:
var $items = $('.item');
var $none = $('#none');
var fade = 250;
function filterContent() {
// get word from value of clicked button.
var word = this.value;
// hide items; filter; show filtered; count matches
var hasMatches = $items
.hide(fade)
.filter(':contains(' + word + ')')
.stop(true, false)
.show(fade)
.length;
// if no matches, show message.
if (hasMatches) {
$none.hide(fade);
} else {
$none.show(fade);
}
}
$('button').on('click', filterContent);
#none {
display: none;
color: darkred;
}
#buttons {
margin: 1em 0 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="item">Here is some text.</div>
<div class="item">Here is some other text.</div>
<div class="item">Here is some other different text.</div>
<div class="item">Here is something else.</div>
<div class="item">Here is some additional text.</div>
<div id="none">No matches found.</div>
<nav id="buttons">
<button type="button" value="">all</button>
<button type="button" value="text">text</button>
<button type="button" value="other">other</button>
<button type="button" value="additional">additional</button>
<button type="button" value="bazooka">bazooka</button>
</nav>
Another way:
If you prefer, you can toggle inside the filter as long as you still return the state Boolean from the function. I suggest making a separate function to pass to the filter. In this case, toggleItem()
determines the state of an item (match or non-match), toggles the item according to that state, and returns the state.
var $items = $('.item');
var $none = $('#none');
function toggleItem(word) {
return function(k, el) {
var $item = $(el);
var state = $item.text().indexOf(word) > -1;
$item.toggle(state);
return state;
}
}
function filterContent() {
// get word from value of clicked button.
var word = this.value;
// filter while toggling and count result.
var hasMatches = $items
.filter(toggleItem(word))
.length;
// if no matches, show message.
$none.toggle(!hasMatches);
}
$('button').on('click', filterContent);
#none {
display: none;
color: darkred;
}
#buttons {
margin: 1em 0 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="item">Here is some text.</div>
<div class="item">Here is some other text.</div>
<div class="item">Here is some other different text.</div>
<div class="item">Here is something else.</div>
<div class="item">Here is some additional text.</div>
<div id="none">No matches found.</div>
<div id="buttons">
<button type="button" value="">all</button>
<button type="button" value="text">text</button>
<button type="button" value="other">other</button>
<button type="button" value="additional">additional</button>
<button type="button" value="bazooka">bazooka</button>
</div>
In my opinion, this is a bit harder to read and not as clear as the chained "hide,filter,show,length" commands, but that's somewhat a matter of style. You can see that there are many ways to bake this cake!
This one's pretty short and sweet:
var $none = $("#none");
var $items = $(".item");
$("button").click(function() {
var value = $(this).data('value');
$items.each(function() {
$(this).toggle($(this).text().indexOf(value) > -1);
});
$none.toggle(!$items.filter(':visible').length);
});
#none {
display: none;
color: darkred;
}
#buttons {
margin: 1em 0 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="item">Here is some text.</div>
<div class="item">Here is some other text.</div>
<div class="item">Here is some other different text.</div>
<div class="item">Here is something else.</div>
<div class="item">Here is some additional text.</div>
<div id="none">No matches found.</div>
<nav id="buttons">
<button type="button" data-value="">all</button>
<button type="button" data-value="text">text</button>
<button type="button" data-value="other">other</button>
<button type="button" data-value="additional">additional</button>
<button type="button" data-value="bazooka">bazooka</button>
</nav>