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>