I’m having a hard time with some JS DOM traversal. I’m stuck with html that’s something like this:
<h2>Header 1</h2> <div class="some-content"> <div class="inner-content"> <h4 class="person-name">John Smith</h4> </div> </div> <h2>Header 2</h2> <div class="some-content"> <div class="inner-content"> <h4 class="person-name">Emily Jones</h4> </div> </div>
This is all hidden by default. I’m trying to use a text field so that if it matches an h4 person-name, it displays the some-content container, as well as the preceding h2. I can make it work for the some-content bit, but I’m having trouble targeting the h2 that’s above it. I’ve tried various combinations of jQuery parent(), siblings(), and prev(). I do not have the ability to add additional class names.
Edit: here is the script I have for the text field event:
$('#text-field').keyup(function() { var nameSearch = $(this).val().toUpperCase(); $('.person-name').each(function() { var x = $(this).text().toUpperCase(); if (x.includes(nameSearch)) { $(this).prev('h2').show(); $(this).closest('.some-content').show(); } }) });
Edit 2:
I apologize, my code example was oversimplified. Some very good answers by the way. If for example there was a search done for Emily Jones in this bit, would there need to be something extra done?
<div class="container"> <h2>Header 1</h2> <div class="some-content"> <div class="inner-content"> <h4 class="person-name">John Smith</h4> </div> <div class="inner-content"> <h4 class="person-name">Emily Jones</h4> </div> </div> </div>
Advertisement
Answer
If the header/content is not nested withing a wrapping div, you will need to step over every two child nodes and toggle class.
const triggerEvent = (el, eventName) => { var event = document.createEvent('HTMLEvents'); event.initEvent(eventName, true, false); el.dispatchEvent(event); }; const search = document.querySelector('.search'), container = document.querySelector('.container'); const onSearch = (e) => { const searchValue = e.target.value, nodes = container.children; for (let i = 0; i < nodes.length; i += 2) { const h2 = nodes[i], someContent = nodes[i + 1], matches = someContent.querySelector('.person-name').textContent === searchValue; h2.classList.toggle('hidden', !matches); someContent.classList.toggle('hidden', !matches); } }; search.addEventListener('change', onSearch); triggerEvent(search, 'change');
.hidden { color: #DDD; /* Replace with -: display: none */ }
<input type="text" class="search" value="Emily Jones" /> <div class="container"> <h2>Header 1</h2> <div class="some-content"> <div class="inner-content"> <h4 class="person-name">John Smith</h4> </div> </div> <h2>Header 2</h2> <div class="some-content"> <div class="inner-content"> <h4 class="person-name">Emily Jones</h4> </div> </div> </div>
Alternatively, you can start with the names and work your way back to the corresponding h2
.
const h2 = child .closest('.inner-content') .closest('.some-content') .previousElementSibling; // h2
const triggerEvent = (el, eventName) => { var event = document.createEvent('HTMLEvents'); event.initEvent(eventName, true, false); el.dispatchEvent(event); }; const search = document.querySelector('.search'), container = document.querySelector('.container'); const onSearch = (e) => { const searchValue = e.target.value; [...container.querySelectorAll('.person-name')].forEach(child => { const matches = child.textContent === searchValue, h2 = child.closest('.inner-content') .closest('.some-content').previousElementSibling; [child, h2].forEach(el => el.classList.toggle('hidden', !matches)); }); }; search.addEventListener('change', onSearch); triggerEvent(search, 'change');
.hidden { color: #DDD; /* Replace with -: display: none */ }
<input type="text" class="search" value="Emily Jones" /> <div class="container"> <h2>Header 1</h2> <div class="some-content"> <div class="inner-content"> <h4 class="person-name">John Smith</h4> </div> </div> <h2>Header 2</h2> <div class="some-content"> <div class="inner-content"> <h4 class="person-name">Emily Jones</h4> </div> </div> </div>
Edit
Here is an altered version of the first example. If you have multiple names within .some-content
you will have to find all the names that match and keep the parent, if at least one child matches.
const triggerEvent = (el, eventName) => { var event = document.createEvent('HTMLEvents'); event.initEvent(eventName, true, false); el.dispatchEvent(event); }; const search = document.querySelector('.search'), container = document.querySelector('.container'); const onSearch = (e) => { const searchValue = e.target.value, nodes = container.children; for (let i = 0; i < nodes.length; i += 2) { const h2 = nodes[i], someContent = nodes[i + 1], names = [...someContent.querySelectorAll('.person-name')], found = names.filter(name => name.textContent === searchValue); h2.classList.toggle('hidden', found.length === 0); names.forEach(name => { const matches = name.textContent === searchValue; name.closest('.inner-content').classList.toggle('hidden', !matches); }); } }; search.addEventListener('change', onSearch); triggerEvent(search, 'change');
.hidden { color: #DDD; /* Replace with -: display: none */ }
<input type="text" class="search" value="Emily Jones" /> <div class="container"> <h2>Header 1</h2> <div class="some-content"> <div class="inner-content"> <h4 class="person-name">John Smith</h4> </div> <div class="inner-content"> <h4 class="person-name">Emily Jones</h4> </div> </div> <h2>Header 2</h2> <div class="some-content"> <div class="inner-content"> <h4 class="person-name">John Doe</h4> </div> <div class="inner-content"> <h4 class="person-name">Erica Jones</h4> </div> </div> </div>