Search code examples
javascriptjquerytraversal

nextUntil & prevUntil on a combined set of elements


I have a dynamically created html like this (simplified)

<div class="timeslots-container">
  <div class="morning">
    <h1 class="lined-h">Morning</h1>
    <div class="row">
      <div class="timeslot" id="timeslot-0">10:30</div>
      <div class="timeslot" id="timeslot-1">11:00</div>
      <div class="timeslot" id="timeslot-2">11:30</div>
   </div>
  </div>
  <div class="day">
    <h1 class="lined-h">Day</h1>
    <div class="row">
      <div class="timeslot" id="timeslot-3">12:00</div>
      <div class="timeslot" id="timeslot-4">12:30</div>
      <div class="timeslot timeslot-disabled" id="timeslot-5">13:00</div>
      <div class="timeslot timeslot-disabled" id="timeslot-6">13:30</div>
      <div class="timeslot timeslot-disabled" id="timeslot-7">14:00</div>
      <div class="timeslot" id="timeslot-8">14:30</div>
      <div class="timeslot" id="timeslot-9">15:00</div>
      <div class="timeslot" id="timeslot-10">15:30</div>
      <div class="timeslot" id="timeslot-11">16:00</div>
      <div class="timeslot timeslot-disabled" id="timeslot-12">16:30</div>
      <div class="timeslot timeslot-disabled" id="timeslot-13">17:00</div>
      <div class="timeslot timeslot-disabled" id="timeslot-14">17:30</div>
    </div>
  </div>
</div>

It is a form, which has several lists of div-buttons, some of them have disabled class. I want to highlight some of the buttons when mouse hovers above a div. Number of divs to be highlighted is transferred from server. I can do that like that:

$(".timeslot").hover(function() {
  if(!$(this).hasClass('timeslot-disabled')) {
    $(this).nextUntil(".timeslot-disabled").slice(0,#{additional_slots}).addClass('timeslot-active');
    var count = $(this).siblings('.timeslot-active').length;
    $(this).prevUntil(".timeslot-disabled").slice(0,#{additional_slots}-count).addClass('timeslot-active');
  }
});
$(".timeslot").mouseleave(function() {
  $(this).siblings().removeClass('timeslot-active');
});

Where #{additional_slots} is the number of additional divs to be highlighted.

It works, but only within the parent of $(this) element. I need to have different containers due to styling and positioning issues, but I want to highlight timeslot divs from all containers as one set.

Can I do that like

$timeslots = $('.timeslot);

and after that traverse in that set from $(this) with my nextUntil and prevUntil? Maybe there is another, more correct solution?

P.S. Exact number of elements and which are disabled is a dynamically assigned stuff, transferred from server via Ajax, I can't rely on that.

P. P. S. If I manage to solve this, I have another question: how to determine the first of the timeslots highlighted when mouse clicks? Potentially I can parse ids for the minimal value, but I doubt it is the right thing to do.


Solution

  • Can I do that like $timeslots = $('.timeslot); and after that traverse in that set from $(this) with my nextUntil and prevUntil?

    Sure, you can implement the nextUntil and prevUntil for this situation:

    var additional_slots = 1;
    
    $(".timeslot").hover(function () {
      var $this = $(this);
      if (!$this.hasClass('timeslot-disabled')) {
        $this.addClass('timeslot-active');
        var $timeslots = $('.timeslot');
        var $nexts = nextUntil(".timeslot-disabled", $this, $timeslots).slice(0, additional_slots);
        $nexts.addClass('timeslot-active');
        var count = $nexts.length;
        var $prevs = prevUntil(".timeslot-disabled", $this, $timeslots).slice(0, additional_slots - count);
        $prevs.addClass('timeslot-active');
      }
    
      function nextUntil(selector, $this, $timeslots) {
        var index = $timeslots.index($this) + 1;
        var result = [];
        while (index < $timeslots.length && !$($timeslots[index]).hasClass('timeslot-disabled')) {
          result.push($timeslots[index]);
          index++;
        }
        return $(result);
      }
    
      function prevUntil(selector, $this, $timeslots) {
        var index = $timeslots.index($this) - 1;
        var result = [];
        while (index >= 0 && !$($timeslots[index]).hasClass('timeslot-disabled')) {
          result.push($timeslots[index]);
          index--;
        }
        return $(result);
      }
    });
    
    $(".timeslot").mouseleave(function () {
      $(".timeslot").removeClass('timeslot-active');
    });