I'm transitioning my old website using Bootstrap 4, so it's definitely been a process of learning.
I've got the site layout working fine, but I realize that my left-sided vertical nav bar has a lot of links. Many of these grouped in collapsed nests.
I think it would be nice to add a search bar at the top of my nav so that I can filter the links based on partial strings entered in the search bar. This works for links that are not hiding inside a hidden div (or class=collapsed
boostrap 4 ul).
I'd appreciate assistance in modifying my code to show filtered results that include any links hiding inside the collapsed ul?
Here's a fiddle
$('.search-filter').on('keyup', function() {
var input = $('.search-filter').val();
var filter = input.toLowerCase();
if (filter.length == 0) { // show all if filter is empty
$('a').each(function() {
$(this).show(); // show links
});
return;
} else {
$('a').removeClass('collapsed');
$('a').each(function() {
$(this).hide(); // hide all links once search is begun
});
$('a:contains("' + filter + '")').each(function() {
$(this).removeClass('collapsed'); // remove bootstrap 4 collapsed class designation
$(this).show(); // show only matched links to search string?
});
}
});
@import url('.0.0-alpha.6/css/bootstrap.min.css');
.navbar-nav.sidebar-nav {
position: absolute;
left: 0;
top: 0;
margin-top: 56px;
padding-bottom: 56px;
height: 100vh;
background: #292b2c;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
overflow: auto;
}
.navbar-brand {
display: inline-block;
padding-top: .25rem;
padding-bottom: .25rem;
margin-right: 1rem;
font-size: 1.25rem;
line-height: inherit;
white-space: nowrap;
color: #fff;
}
.navbar-nav .nav-link {
color: rgba(255, 255, 255, .5);
}
<div id="link-content">
<ul class="sidebar-nav navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#"><i class="fa fa-fw fa-home"></i> Home</a>
</li>
<li class="nav-item">
<label for="nav-search" class="col-2 col-form-label sr-only">Search links</label>
<div class="col p-2">
<input class="form-control form-control-sm search-filter" type="search" id="nav-search" placeholder="Search for tools">
</div>
</li>
<li class="nav-item">
<span class="navbar-brand">Popular tools</span>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-fw fa-calculator"></i> Calculator</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-fw fa-battery-3"></i> Battery </a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-fw fa-database"></i> Pancake Batter</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-fw fa-clock-o"></i> Marzipan</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-fw fa-tags"></i> Cakes and Muffins</a>
</li>
<li class="nav-item">
<span class="navbar-brand">Categories</span>
</li>
<li class="nav-item">
<a class="nav-link nav-link-collapse collapsed" data-toggle="collapse" href="#collapseComponents"><i class="fa fa-fw fa-flask"></i> Cars</a>
<ul class="sidebar-second-level collapse" id="collapseComponents">
<li>
<a class="nav-link-collapse collapsed" data-toggle="collapse" href="#collapseMulti2">American</a>
<ul class="sidebar-third-level collapse" id="collapseMulti2">
<li>
<a href="#">Ford</a>
</li>
<li>
<a href="#">GMC</a>
</li>
</ul>
</li>
<li>
<a class="nav-link-collapse collapsed" data-toggle="collapse" href="#collapseMulti3">European</a>
<ul class="sidebar-third-level collapse" id="collapseMulti3">
<li>
<a href="#">BMW</a>
</li>
<li>
<a href="#">Audi</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
<script src=".2.4/jquery.min.js"></script>
<script src=".4.0/js/tether.min.js"></script>
<script src=".0.0-alpha.6/js/bootstrap.min.js"></script>
I'm transitioning my old website using Bootstrap 4, so it's definitely been a process of learning.
I've got the site layout working fine, but I realize that my left-sided vertical nav bar has a lot of links. Many of these grouped in collapsed nests.
I think it would be nice to add a search bar at the top of my nav so that I can filter the links based on partial strings entered in the search bar. This works for links that are not hiding inside a hidden div (or class=collapsed
boostrap 4 ul).
I'd appreciate assistance in modifying my code to show filtered results that include any links hiding inside the collapsed ul?
Here's a fiddle
$('.search-filter').on('keyup', function() {
var input = $('.search-filter').val();
var filter = input.toLowerCase();
if (filter.length == 0) { // show all if filter is empty
$('a').each(function() {
$(this).show(); // show links
});
return;
} else {
$('a').removeClass('collapsed');
$('a').each(function() {
$(this).hide(); // hide all links once search is begun
});
$('a:contains("' + filter + '")').each(function() {
$(this).removeClass('collapsed'); // remove bootstrap 4 collapsed class designation
$(this).show(); // show only matched links to search string?
});
}
});
@import url('https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css');
.navbar-nav.sidebar-nav {
position: absolute;
left: 0;
top: 0;
margin-top: 56px;
padding-bottom: 56px;
height: 100vh;
background: #292b2c;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
overflow: auto;
}
.navbar-brand {
display: inline-block;
padding-top: .25rem;
padding-bottom: .25rem;
margin-right: 1rem;
font-size: 1.25rem;
line-height: inherit;
white-space: nowrap;
color: #fff;
}
.navbar-nav .nav-link {
color: rgba(255, 255, 255, .5);
}
<div id="link-content">
<ul class="sidebar-nav navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#"><i class="fa fa-fw fa-home"></i> Home</a>
</li>
<li class="nav-item">
<label for="nav-search" class="col-2 col-form-label sr-only">Search links</label>
<div class="col p-2">
<input class="form-control form-control-sm search-filter" type="search" id="nav-search" placeholder="Search for tools">
</div>
</li>
<li class="nav-item">
<span class="navbar-brand">Popular tools</span>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-fw fa-calculator"></i> Calculator</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-fw fa-battery-3"></i> Battery </a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-fw fa-database"></i> Pancake Batter</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-fw fa-clock-o"></i> Marzipan</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-fw fa-tags"></i> Cakes and Muffins</a>
</li>
<li class="nav-item">
<span class="navbar-brand">Categories</span>
</li>
<li class="nav-item">
<a class="nav-link nav-link-collapse collapsed" data-toggle="collapse" href="#collapseComponents"><i class="fa fa-fw fa-flask"></i> Cars</a>
<ul class="sidebar-second-level collapse" id="collapseComponents">
<li>
<a class="nav-link-collapse collapsed" data-toggle="collapse" href="#collapseMulti2">American</a>
<ul class="sidebar-third-level collapse" id="collapseMulti2">
<li>
<a href="#">Ford</a>
</li>
<li>
<a href="#">GMC</a>
</li>
</ul>
</li>
<li>
<a class="nav-link-collapse collapsed" data-toggle="collapse" href="#collapseMulti3">European</a>
<ul class="sidebar-third-level collapse" id="collapseMulti3">
<li>
<a href="#">BMW</a>
</li>
<li>
<a href="#">Audi</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
Share
Improve this question
edited Sep 8, 2017 at 7:55
K Scandrett
16.5k4 gold badges42 silver badges68 bronze badges
asked Apr 30, 2017 at 4:05
user1837608user1837608
9602 gold badges11 silver badges37 bronze badges
3 Answers
Reset to default 9 +25The approach I've used is to remember which items match, and then use that list to un-hide the parents.
Updated Fiddle: https://jsfiddle.net/j2gpann3/1/
The advantage of this approach is that the hidden sub-menu items (like 'BMW' etc.) will now show up in the search and won't have huge gaps above them from the other hidden items. The regex search searches for occurences of each word, so even if they are out of order to the menu item text, it will still match.
$('.search-filter').on('keyup', function() {
var matches = [];
var input = $.trim($('.search-filter').val());
var val = '^(?=.*\\b' + input.split(/\s+/).join('\\b)(?=.*\\b') + ').*$'; // using individual word matching filter from http://stackoverflow.com/a/9127872/1544886
var filter = RegExp(val, 'i');
if (input.length === 0) { // show all if filter is empty
$('.collapse').removeClass('show').addClass('collapsed'); // hide collapsable items fast.
$('.hide').removeClass('hide'); // remove any hidden elements from previous searches
} else {
$('.collapse').addClass('show'); // show all collapsed items
$('ul.sidebar-nav a:not(".home")').filter(function() { // skip home <li> so it shows permanently
$this = $(this);
// test for a match to search string
text = $this.text().replace(/\s+/g, ' ');
var isMatch = filter.test(text);
// store match so we can unhide parents of this item
if (isMatch) matches.push($this);
return !isMatch;
}).parent().addClass('hide'); // this hides any <li> that doesn't match search terms. Hiding <a> results in large gaps in the output
$.each(matches, function() { // unhide parents of our matches
this.parentsUntil(".sidebar-nav", ".hide").removeClass('hide');
});
}
});
The demo requires a home
class added to the Home link to prevent it from being hidden by the search:
<a class="nav-link home" href="#"><i class="fa fa-fw fa-home"></i> Home</a>
and a CSS class for hide
added:
.hide {
display: none;
}
Okay, I think i've figured it out on my own (with a little help from this post for a case insensitive filter).
1) I updated the links by wrapping them in a div with id #link-content
, to separate my filter from the home and search input.
2) I added the case insensitive method referenced above
My HTML:
<ul class="sidebar-nav navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#"><i class="fa fa-fw fa-home"></i> Home</a>
</li>
<li class="nav-item">
<label for="nav-search" class="col-2 col-form-label sr-only">Search links</label>
<div class="col p-2">
<input class="form-control form-control-sm search-filter" type="search" id="nav-search" placeholder="Search for tools">
</div>
</li>
<div id="link-content"><!-- added this to wrap the links-->
<li class="nav-item">
<span class="navbar-brand">Popular tools</span>
</li>
<li class="nav-item filter">
<a class="nav-link" href="#"><i class="fa fa-fw fa-calculator"></i> Calculator</a>
</li>
<li class="nav-item filter">
<a class="nav-link" href="#"><i class="fa fa-fw fa-battery-3"></i> Battery </a>
</li>
<li class="nav-item filter">
<a class="nav-link" href="#"><i class="fa fa-fw fa-database"></i> Pancake Batter</a>
</li>
<li class="nav-item filter">
<a class="nav-link" href="#"><i class="fa fa-fw fa-clock-o"></i> Marzipan</a>
</li>
<li class="nav-item filter">
<a class="nav-link" href="#"><i class="fa fa-fw fa-tags"></i> Cakes and Muffins</a>
</li>
<li class="nav-item filter">
<span class="navbar-brand">Categories</span>
</li>
<li class="nav-item ">
<a class="nav-link nav-link-collapse collapsed" data-toggle="collapse" href="#collapseComponents"><i class="fa fa-fw fa-flask"></i> Cars</a>
<ul class="sidebar-second-level collapse" id="collapseComponents">
<li>
<a class="nav-link-collapse collapsed" data-toggle="collapse" href="#collapseMulti2">American</a>
<ul class="sidebar-third-level collapse" id="collapseMulti2">
<li>
<a href="#">Ford</a>
</li>
<li>
<a href="#">GMC</a>
</li>
</ul>
</li>
<li>
<a class="nav-link-collapse collapsed" data-toggle="collapse" href="#collapseMulti3">European</a>
<ul class="sidebar-third-level collapse" id="collapseMulti3">
<li>
<a href="#">BMW</a>
</li>
<li>
<a href="#">Audi</a>
</li>
</ul>
</li>
</ul>
</li>
</div>
</ul>
My updated code:
// Case insensitive method for filter
jQuery.expr[':'].casecontains = (a, b, c) => jQuery(a).text().toUpperCase().indexOf(c[3].toUpperCase()) >= 0;
$('.search-filter').on('keyup', function () {
var input = $('.search-filter').val();
console.log('input: '+input);
if (input.length != 0) {
// first hide the div #link-content lists from view
$('#link-content li').hide();
// but secretly unhide the collapsed links
// using .show, so the nested uls can be viewed
$('#link-content li.nav-item ul').show();
// then filter in the matching links only
$('#link-content li:casecontains("'+input+'")').show();
} else {
// secretly unhide the collapsed links
$('#link-content li.nav-item ul').hide();
// if search is empty, show the div and reset columns
$('#link-content li').show();
}
});
Here is a fiddle with the working example that opens any hidden links (collapsed class in boostrap 4).
If you remove the:
toLowerCase();
Everything matches perfectly if you type it perfectly. The issue is that you make the search term lower case, but it's not matching against lower case words. So when you type calculator, it doesn't match because the actual nav item shows as Calculator, with a capital c.
So you can either make the nav items lower case as well, via HTML or JavaScript, or you go about it in a different way, i.e. using IDs, but that would cause issues if the site is dynamic.
Bit of a weird work around may be to capitalize the first letters of each word so it actually matches the HTML. Take a look at How to capitalize first letter of each word, like a 2-word city?