Selecting previous element in DOM

Tags: ,



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>

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>


Source: stackoverflow