For “Meet the Team” pages, clients often want to have filtering functions that allow a user to search and find people that focus on a specific skill, or people that are in a specific location. This page will cover how to set up those types of pages.
If you also want to alphabetize the team members within their cards, you can find instructions on how to do that here.
Bootstrap 5 (JavaScript)
Method One
How To
Scripts (script.js)
Add this within your script.js file, inside of the DOMContentLoaded function (Scripts when the DOM is fully propagated). There are some commented-out console.logs which can be uncommented for debugging if desired.
Make sure to change the bodyTag class name on the first line to be what you need it to be! This makes it so that the script doesn’t run everywhere and cause errors on pages that DON’T have a search/filter like this.
if (bodyTag.classList.contains('template-news-resource') || bodyTag.classList.contains('template-branches')) {
const filterNewsForm = document.getElementById('filterNewsForm');
const filterInput = document.getElementById('keywordFilter');
const keywordSubmit = document.getElementById('keywordSubmit');
const refreshForm = document.getElementById('refreshForm');
let newsBoxes = document.querySelectorAll('.textCard__card');
const noResults = document.getElementById('noResults');
var filterNewsPristine = new Pristine(filterNewsForm);
let countVisibleNews = 0;
function searchNews() {
let filter = filterInput.value.toLowerCase();
for (i = 0; i < newsBoxes.length; i++) {
if (newsBoxes[i].innerText.toLowerCase().includes(filter)) {
newsBoxes[i].style.display = "block";
noResults.classList.add('d-none');
// console.log('includes ' + filter + ' within ' + newsBoxes[i].innerText)
countVisibleNews++;
// console.log(countVisibleNews + ' is visible news after searchnews function run');
} else {
newsBoxes[i].classList.add('d-none');
// console.log('hidden non-matches');
}
}
}
filterNewsForm.addEventListener('submit',function(e) {
e.preventDefault();
e.stopPropagation();
if(filterNewsPristine.validate() === true) {
// console.log('submit');
e.stopPropagation();
e.preventDefault();
searchNews();
filterNewsForm.classList.add('needsRefresh');
filterInput.setAttribute('disabled',true);
filterInput.blur();
if (countVisibleNews < 1) {
noResults.classList.remove('d-none');
// console.log('shown no results');
} else {
noResults.classList.add('d-none');
// console.log('hide no results');
}
} else {
e.stopPropagation();
return false;
}
});
refreshForm.addEventListener('click',function(e) {
filterNewsForm.classList.remove('needsRefresh');
noResults.classList.add('d-none');
filterInput.removeAttribute('disabled');
filterInput.focus();
// console.log('refreshed');
newsBoxes.forEach((el) => {
// console.log('display all');
el.classList.remove('d-none');
});
countVisibleNews = 0;
});
}
CSS/Sass
The most important thing about this CSS is that it hides/shows the refresh and submit buttons as needed.
NOTE, you may also need to wrap this CSS in a purgecss thing. /* purgecss start ignore */ and /* purgecss end ignore */
#filterNewsForm {
#refreshForm {
pointer-events: none;
display: none;
}
&.needsRefresh {
#keywordSubmit {
pointer-events: none;
display: none;
}
#refreshForm {
pointer-events: all;
display: block;
}
}
}
HTML
The basic search form is as follows. Note the LACK of data-pristine-validate (we’re doing this manually with the script above), and the required attribute on the input.
<form id="filterNewsForm">
<div class="form-group d-sm-flex align-items-center justify-content-center js-disabled js-slideup">
<label for="keywordFilter">Search Articles</label>
<input type="text" class="form-control" id="keywordFilter" required>
<div class="buttons-wrapper">
<button type="reset" class="btn btn-default w-100 js-no-arrow" id="refreshForm"><svg class="bs-icon" aria-hidden="true"><use href="#arrow-repeat"/></svg><span class="sr-only">Refresh Search form</span></button>
<button class="btn btn-default w-100 js-no-arrow" id="keywordSubmit" type="submit" aria-label="Search by keyword"><svg class="bs-icon" aria-hidden="true"><use href="#custom--search"/></svg></button>
</div>
</div>
</form>
The setup for the cards is below.
<div class="container">
<div class="news__container row justify-content-center">
<div class="col-xl-4 col-lg-6 textCard__card my-15 remove-blank">
<div class="textCard__card__inner">
<div data-content-block="text1img" data-content="content" data-editable="editable" class="content remove-blank">
{{#page.text1img}}
{{& content}}
{{/page.text1img}}
{{#dev_environment}}
<div><img src="https://dummyimage.com/360x200" alt=""></div>
{{/dev_environment}}
</div>
<div class="textCard__card__text p-3">
<div data-content-block="text1date" data-content="content" data-editable="editable" class="content date remove-blank">
{{#page.text1date}}
{{& content}}
{{/page.text1date}}
{{#dev_environment}}
<div>MM/DD/YYYY</div>
{{/dev_environment}}
</div>
<div data-content-block="text1body" data-content="content" data-editable="editable" class="content">
{{#page.text1body}}
{{& content}}
{{/page.text1body}}
{{#dev_environment}}
<div><p class="big">"Blog" Title Example Lorem</p><p>Optional intro text lorem ipsum dolor sit amet consectetur.</p><p class="small"><a href="/">Link to Subpage</a></p></div>
{{/dev_environment}}
</div>
</div>
</div>
</div>
{{! repeat as many cards as you need }}
</div>
<div id="noResults" class="d-none">
<h2>No results.</h2>
</div>
</div>
Example Sites
- United Bank of Zebulon 2023 remake (does not use exact code above, missing pristine)
- Newtown Savings Bank 2023 remake ATM locator (does not use exact code above, missing pristine)
- F&M Bank (Timberville) (link may break, it’s on Branches and News/Resource layouts. Uses code above involving pristine.)
Method 2
Pulled directly from MyFirst/Carmi.
JS
Example Sites
Bootstrap 4 and down (uses jquery)
This example is pulling from Yellowstone Bank.
How To
HTML
Within the HTML, you’ll need a form with your filter items in it. This has been simplified from the example, but there are two pieces to this example–one is a select dropdown, and the other is a text input field. Finally, a Submit button is included–this is the preferred way rather than having things change on click or on keypress for ADA reasons.
<form id="filterTeamForm">
<div class="form-group">
<label for="locationFilter" class="sr-only">Location</label>
<select id="locationFilter" class="form-control">
<option value="" selected>Location</option>
<option value="Absarokee">Absarokee</option>
<option value="Billings Downtown">Billings Downtown</option>
<option value="Billings Homestead">Billings Homestead</option>
<option value="Billings Shiloh">Billings Shiloh</option>
<option value="Bozeman">Bozeman</option>
<option value="Columbus">Columbus</option>
<option value="Directors">Directors</option>
<option value="Laurel">Laurel</option>
<option value="Operations">Operations</option>
<option value="Sidney">Sidney</option>
</select>
</div>
<div class="form-group inline">
<label for="nameFilter" class="sr-only">Name</label>
<input type="text" class="form-control" id="nameFilter" placeholder="Search by name">
<button class="btn btn-default" type="submit">Search<span class="sr-only"> by name</span></button>
</div>
</form>
Then, you’ll have a grid of people with the possibilitly of several content areas for each. The most basic will have only one content area for both the image and text, but others get more complicated. This one includes an area for an image, the name of the person, information about them, and a hidden CMS-only content area where clients can enter other information that they want a person to be searchable by.
The grid is enclosed in a team__profiles div, which allows it to be targeted for searching.
You’ll also notice that each team member is inclosed in their own col-sm-6/col-md-4 div. This is important to ensure that the space completely hides when left empty, and any other people put in after that will “bump up” and fill the space without leaving a hole.
<div class="teams__profiles">
<div class="row remove-blank">
<div class="col-sm-6 col-md-4 teams__profiles-block remove-blank">
<div class="inside row">
<div class="col-xs-5 col-sm-4 col-md-5 teams__profiles-image">
<div data-content-block="lender1image" data-content="content" data-editable="editable" class="content">
{{#page.lender1image}}
{{& content}}
{{/page.lender1image}}
<!-- @if NODE_ENV!='production' -->
<div><img src="https://picsum.photos/id/1019/350/520" alt=""></div>
<!-- @endif -->
</div>
</div>
<div class="col-xs-7 col-sm-8 col-md-7 teams__profiles-content">
<div data-content-block="lender1name" data-content="content" data-editable="editable" class="content teams__profiles-name">
{{#page.lender1name}}
{{& content}}
{{/page.lender1name}}
<!-- @if NODE_ENV!='production' -->
<h3>Ron Burgundy</h3>
<!-- @endif -->
</div>
<div data-content-block="lender1content" data-content="content" data-editable="editable" class="content">
{{#page.lender1content}}
{{& content}}
{{/page.lender1content}}
<!-- @if NODE_ENV!='production' -->
<h5>Regional President</h5>
<div>(713) 485-8317</div>
<div> </div>
<div><a href="mailto:mbraly@tcbssb.com">mbraly@tcbssb.com</a></div>
<div> </div>
<!-- @endif -->
</div>
<div data-content-block="lender1filter" data-content="content" data-editable="editable" class="content teams__profiles-filter">
{{#page.lender1filter}}
{{& content}}
{{/page.lender1filter}}
<!-- @if NODE_ENV!='production' -->
Gallatin, Billings Downtown, Mortgage Lending
<!-- @endif -->
</div>
</div>
</div>
</div>
<!-- insert as many people as you need-->
</div>
</div>
At the very end, after the closing teams__profiles div, there is another div for a “no results” message.
<div class="no-results">
<h2>No matches found. Please try again.</h2>
</div>
Script
The script also has many different sections. The below is simplified from what Yellowstone actually has, but defines each piece and why it’s necessary for the functionality.
This first section begins the fuction and sets variables for each input field being used.
function teamSearch() {
// Set variables
var nameFilter = $('#nameFilter').val(),
locationFilter = $('#locationFilter').val(),
These next two if statements define what happens if either of the filter fields are filled out. The location filter has a different setup from the “name” filter due to the location being a select dropdown and the name being an input field.
// If the Location field was filled out...
if (locationFilter !== null) {
// Loop through all team members...
$('.teams__profiles-block.countyFiltered').each(function () {
// If there isn't a match it'll return -1 so hide it. All matches will remain visible.
if ($(this).find('.teams__profiles-filter').text().search(new RegExp(locationFilter, 'i')) < 0) {
$(this).hide();
$(this).removeClass('nameFiltered');
$(this).removeClass('locationFiltered');
} else {
$(this).show();
$(this).addClass('locationFiltered');
}
});
} else {
$('.teams__profiles-block.countyFiltered').each(function () {
$(this).addClass('locationFiltered');
});
}
// If the name field was filled out...
if (nameFilter.length) {
// Loop through all team members...
$('.teams__profiles-block.productFiltered').each(function () {
// If there isn't a match it'll return -1 so hide it. All matches will remain visible.
if ($(this).find('.teams__profiles-name').text().search(new RegExp(nameFilter, 'i')) < 0) {
$(this).hide();
$(this).removeClass('nameFiltered');
$(this).removeClass('locationFiltered');
} else {
$(this).show();
$(this).addClass('nameFiltered');
}
});
} else {
$('.teams__profiles-block.productFiltered').each(function () {
$(this).addClass('nameFiltered');
});
}
Lastly, if there are no results found, this defines the way the “no results” div should show.
if($('.teams__profiles .teams__profiles-block.nameFiltered').length == 0) {
$('.no-results').fadeIn();
}
}
This JS is how to run the function above when the submit button is clicked:
$('#filterTeamForm').on('submit', function(e) {
e.preventDefault();
$('.no-results').fadeOut();
teamSearch();
})
$("#countyFilter, #locationFilter, #productFilter").on("change", function (e) {
if(e.keyCode == 13) {
return false;
} else {
$('.no-results').fadeOut();
teamSearch();
}
});
Example Sites
- United Bank of Iowa (Keyword/text search and select dropdown to filter by location)
- Yellowstone Bank (Keyword/text search and select dropdown to filter by location. Also alphabetizes by last name.)
- The National Bank of Indianapolis (Keyword/text search only. The dropdown doesn’t actually filter the search–it just has links to other pages.)
Tags: meet the team, filter, js, jquery, javascript, html, mustache, cards, BS3, BS4, BS5, form, parsley, search