Search code examples
javascriptdomsearchselector

How will I target the nearest child element aspect of on click area in vanilla JS?


Based on this question, another question raised in my mind. While both questions have almost similar, they are different.

how will i target nearest child element aspect of on click area in vanilla js means: i will click on outer side of li element and inner side of ul element and nearest li will be the target ..below Example Code..

Note: Jquery find method can not resolve the issue. I tried it, but it repeats the same element all time.

What I tried to achieve

From below picture red mark area: when I click on red mark area nearest li will be the target element. That is what I want... I did not resolve the issue. I think everything is possible ..there are so many selectors available in css & js...

enter image description here

Example Code

$(".amenities-filters-inner").each(function(i, el) {
  $(el).on("click", function(event) {
    event.preventDefault()
    var element = null;
    element = $(event.target).closest(".amenity_id");
    element = $(element).children().children().next();

    if ($(element).checked == true)
      $(element).checked = false;
    else {
      $(element).checked = true;
    }

    let dataId = null;
    if (element.hasClass('amenity_data')) {
      dataId = $(element).data('index');

    } else {
      element = $(event.target).find(".amenity_id");
      element = $(element).children().children().next();
      dataId = $(element).data('index');

    }

    console.log('$ el:' + element)
    console.log('$ index:' + dataId)
  })
});

document.querySelectorAll(".amenities-filters-inner ").forEach(function(item) {
  item.addEventListener("click", function(e) {
    e.preventDefault();

    let element = null;
    element = e.target.closest(".amenity_id");
    // element = e.target.querySelector(".amenity_id");
    element = element.children[0].children[1];
    element.checked == true ? element.checked = false : element.checked = true;

    let dataId = element.getAttribute('data-index');
    console.log('js el' + element)
    console.log('js index ' + dataId)
  })
})
.amenities-filters-inner {
  border: 2px dashed royalblue;
  max-width: 300px;
  padding: 1rem;
}

ul {
  list-style: none;
  width: 100%;
  margin: 0;
  padding: 0;
}

li {
  border: 2px solid rgb(133, 129, 129);
  padding: 3px;
}

.amenities-filters-inner {
  border: 2px dashed royalblue;
  max-width: 300px;
  padding: 1rem;
}

ul {
  list-style: none;
  width: 100%;
  margin: 0;
  padding: 0;
}

li {
  border: 2px solid rgb(133, 129, 129);
  padding: 3px;
}

.amenities-filters-inner:last-child {
  margin-bottom: 0;
}

.amenities-filters-inner ul li {
  margin-bottom: 20px;
}

.amenities-filters-inner ul li:last-child {
  margin-bottom: 0;
}

.check {
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: relative;
  padding-left: 31px;
  margin-bottom: 0;
  padding-right: 0;
  cursor: pointer;
  font-size: 14px;
  color: #646464;
}

.check strong {
  color: #969696;
  font-size: 14px;
  font-weight: 400;
}

.check input {
  position: absolute;
  opacity: 0;
  cursor: pointer;
}

.checkmark {
  position: absolute;
  top: 0;
  left: 0;
  height: 17px;
  width: 17px;
  background-color: #fff;
  border-color: #646464;
  border-style: solid;
  border-width: 1px;
  border-radius: 2px;
}

.check input:checked~.checkmark {
  background-color: #fff;
  border-color: #007FEB;
}

.checkmark:after {
  content: "";
  position: absolute;
  display: none;
}

.check input:checked~.checkmark:after {
  display: block;
}

.check .checkmark:after {
  left: 5px;
  top: 1px;
  width: 5px;
  height: 10px;
  border: solid;
  border-color: #007FEB;
  border-width: 0 2px 2px 0;
  -webkit-transform: rotate(45deg);
  -ms-transform: rotate(45deg);
  transform: rotate(45deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="amenities-filters-inner">
  <ul>
    <li class="amenity_id">
      <label class="check ">One <strong>One</strong>
        <input type="checkbox" class="amenity_data" data-index="1" name="is_name">
        <span class="checkmark"></span>
      </label>
    </li>
    <li class="amenity_id">
      <label class="check ">Two <strong>Two</strong>
        <input type="checkbox" class="amenity_data" data-index="2" name="is_name">
        <span class="checkmark"></span>
      </label>
    </li>
    <li class="amenity_id">
      <label class="check ">Three <strong>Three</strong>
        <input type="checkbox" class="amenity_data" data-index="3" name="is_name">
        <span class="checkmark"></span>
      </label>
    </li>
  </ul>
</div>


Solution

  • I think this achieves what you're after:

    • Note that clicking close to one checkbox and then close to another checkbox will check both checkboxes, but you can change that of course.

    document.querySelectorAll(".amenities-filters-inner").forEach(function(item) {
      item.addEventListener("click", function(e) {
        e.preventDefault();
        let element = null;
        var lis = document.getElementsByClassName("amenity_id");
        for (var i = 0; i < lis.length; i++) {
            var current = lis[i];
          var next = lis[i+1];
          var currentBox = current.getBoundingClientRect();
          var nextBox = next && next.getBoundingClientRect();
          if (clickedBeforeFirstCheckbox(e, i == 0, currentBox) ||  
                clickedAfterLastCheckbox(e, i == lis.length-1, currentBox) ||
              clickedInBetween(e, currentBox, nextBox)) {
              setCheckboxByDistance(e, current, next, currentBox, nextBox);
                break;
          }
        }
      })
    });
    
    function clickedBeforeFirstCheckbox(e, isFirst, currentBox) {
        return isFirst && e.clientY < currentBox.y;
    }
    function clickedAfterLastCheckbox(e, isLast, currentBox) {
        return isLast && e.clientY > currentBox.y;
    }
    function clickedInBetween(e, currentBox, nextBox) {
        return e.clientY > currentBox.y && 
              e.clientY < nextBox.y;
    }
    
    function setCheckboxByDistance(e, current, next, currentBox, nextBox) {
      var diffToCurrent = 
        Math.abs(e.clientY - (currentBox.y + (currentBox.height / 2)));
      var diffToNext =  nextBox ? 
        Math.abs(e.clientY - (nextBox.y + (nextBox.height / 2))) : 
        Number.MAX_SAFE_INTEGER;
      if (diffToCurrent < diffToNext) {
        var checkbox = current.querySelector('input');
        checkbox.checked = !checkbox.checked;
      } else {
        var checkbox = next.querySelector('input');
        checkbox.checked = !checkbox.checked;
      }
    }
    .amenities-filters-inner {
      border: 2px dashed royalblue;
      max-width: 300px;
      padding: 1rem;
    }
    
    ul {
      list-style: none;
      width: 100%;
      margin: 0;
      padding: 0;
    }
    
    li {
      border: 2px solid rgb(133, 129, 129);
      padding: 3px;
    }
    
    .amenities-filters-inner {
      border: 2px dashed royalblue;
      max-width: 300px;
      padding: 1rem;
    }
    
    ul {
      list-style: none;
      width: 100%;
      margin: 0;
      padding: 0;
    }
    
    li {
      border: 2px solid rgb(133, 129, 129);
      padding: 3px;
    }
    
    .amenities-filters-inner:last-child {
      margin-bottom: 0;
    }
    
    .amenities-filters-inner ul li {
      margin-bottom: 20px;
    }
    
    .amenities-filters-inner ul li:last-child {
      margin-bottom: 0;
    }
    
    .check {
      display: flex;
      align-items: center;
      justify-content: space-between;
      position: relative;
      padding-left: 31px;
      margin-bottom: 0;
      padding-right: 0;
      cursor: pointer;
      font-size: 14px;
      color: #646464;
    }
    
    .check strong {
      color: #969696;
      font-size: 14px;
      font-weight: 400;
    }
    
    .check input {
      position: absolute;
      opacity: 0;
      cursor: pointer;
    }
    
    .checkmark {
      position: absolute;
      top: 0;
      left: 0;
      height: 17px;
      width: 17px;
      background-color: #fff;
      border-color: #646464;
      border-style: solid;
      border-width: 1px;
      border-radius: 2px;
    }
    
    .check input:checked~.checkmark {
      background-color: #fff;
      border-color: #007FEB;
    }
    
    .checkmark:after {
      content: "";
      position: absolute;
      display: none;
    }
    
    .check input:checked~.checkmark:after {
      display: block;
    }
    
    .check .checkmark:after {
      left: 5px;
      top: 1px;
      width: 5px;
      height: 10px;
      border: solid;
      border-color: #007FEB;
      border-width: 0 2px 2px 0;
      -webkit-transform: rotate(45deg);
      -ms-transform: rotate(45deg);
      transform: rotate(45deg);
    }
    <div class="amenities-filters-inner">
      <ul>
        <li class="amenity_id">
          <label class="check ">One <strong>One</strong>
            <input type="checkbox" class="amenity_data" data-index="1" name="is_name">
            <span class="checkmark"></span>
          </label>
        </li>
        <li class="amenity_id">
          <label class="check ">Two <strong>Two</strong>
            <input type="checkbox" class="amenity_data" data-index="2" name="is_name">
            <span class="checkmark"></span>
          </label>
        </li>
        <li class="amenity_id">
          <label class="check ">Three <strong>Three</strong>
            <input type="checkbox" class="amenity_data" data-index="3" name="is_name">
            <span class="checkmark"></span>
          </label>
        </li>
      </ul>
    </div>