0

I have a couple of links that shows a number of divs, depending on data-attribute. When I click one of the links, and there is more than one div that has the corresponding attribute, I want the class to be added sequential to the div, making them load one at a time. Just like it does in ready function but I've messed it up in some way and can't figure it out.

$('.filter_link').on('click', function(e) {
    e.preventDefault();
    var linkAttr = $(this).attr('data-related'); 
    $('.blurb-content').each(function() {
        $(this).removeClass('flip');
        if($(this).attr('data-dest') == linkAttr) {
            // this does not work
            setTimeout(function() {
                $(this).addClass('flip'); //add class with delay
            }, 500);
        }
    });
});

fiddle

pistevw
  • 432
  • 4
  • 15
  • The difference is that when I click one of the links, and there is more then one div with corresponding attribute, I want them to get class sequentially. At the moment they all get class at the same time. – pistevw Jul 10 '17 at 18:11

2 Answers2

1

Two Issues:

  1. Inside of a setTimeout(), this will no longer refer to your element. For this reason, you'll need to declare it outside the setTimeout() as a variable, and use that variable inside the setTimeout() instead of $(this). More information on this can be found here.

  2. When you iterate through items using .each() they are all happening essentially simultaneously. Your setTimeout() isn't attaching "sequentially", so they will all delay 500 and then fire at the same time. Change the 500 to be multiplied by the iteration of your .each() by giving the each function a parameter i, and doing 500 * i just as you did in your first each.

EDIT: As you can see, I've re-factored your code quite a bit. It now uses the function showBlurbs() to simply fade each one in (taking the parameter as a list of jQuery objects). It resolves the need for so many .each() loops, and the code is quite a bit cleaner.

A few other notes:

  • You can get a data attribute by using .data("xxx") instead of .attr("data-xxx")

  • Rather than looping through all items and checking their data attribute, you can simply select only ones where it matches by doing:

    var linkAttr = $(this).data('related');
    $blurbsToShow = $('.blurb-content[data-dest=' + linkAttr + ']');
    

$(document).ready(function() {
  
  //Show ALL blurbs at the beginning
  var timeouts = [];
  var $allBlurbs= $(".blurb-content");
  showBlurbs($allBlurbs);


  //On click, show related blurbs
  $('.filter_link').on('click', function(e) {
    e.preventDefault();
    var linkAttr = $(this).data('related');
    var $relatedBlurbs = $('.blurb-content[data-dest=' + linkAttr + ']');
    showBlurbs($relatedBlurbs);
  });


  function showBlurbs(blurbs) {

    //Clear all pending animations
    for (var i = 0; i < timeouts.length; i++) {
        clearTimeout(timeouts[i]);
    }

    //Reset everything to hidden
    timeouts = [];
    $(".blurb-content").removeClass("flip");

    //Sequentially trigger the "fade-in" for each blurb
    blurbs.each(function(i) {
      var $self = $(this);
      timeouts.push(setTimeout(function() {
        $self.addClass("flip");
      }, 500 * i));
    });

  }


});
.blurb-content {
  opacity: 0;
  display: none;
}

.flip {
  display: block;
  -webkit-animation: fade-in-bottom 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000) both;
  animation: fade-in-bottom 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000) both;
}

@-webkit-keyframes fade-in-bottom {
  0% {
    -webkit-transform: translateY(50px);
    transform: translateY(50px);
    op.blurb-content {
      opacity: 0;
      display: none;
    }
    .flip {
      display: block;
      -webkit-animation: fade-in-bottom 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000) both;
      animation: fade-in-bottom 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000) both;
    }
    @-webkit-keyframes fade-in-bottom {
      0% {
        -webkit-transform: translateY(50px);
        transform: translateY(50px);
        opacity: 0;
      }
      100% {
        -webkit-transform: translateY(0);
        transform: translateY(0);
        opacity: 1;
      }
    }
    @keyframes fade-in-bottom {
      0% {
        -webkit-transform: translateY(50px);
        transform: translateY(50px);
        opacity: 0;
      }
      100% {
        -webkit-transform: translateY(0);
        transform: translateY(0);
        opacity: 1;
      }
    }
    acity: 0;
  }
  100% {
    -webkit-transform: translateY(0);
    transform: translateY(0);
    opacity: 1;
  }
}

@keyframes fade-in-bottom {
  0% {
    -webkit-transform: translateY(50px);
    transform: translateY(50px);
    opacity: 0;
  }
  100% {
    -webkit-transform: translateY(0);
    transform: translateY(0);
    opacity: 1;
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#" class="filter_link" data-related="1">link1</a>
<a href="#" class="filter_link" data-related="3">link2</a>
<a href="#" class="filter_link" data-related="3">link3</a>
<a href="#" class="filter_link" data-related="2">link4</a>

<div class="blurb-content" data-dest="1">content 1</div>
<div class="blurb-content" data-dest="1">content 1</div>
<div class="blurb-content" data-dest="2">content 2</div>
<div class="blurb-content" data-dest="3">content 3</div>
Tyler Roper
  • 21,445
  • 6
  • 33
  • 56
  • Thank you so much for the answer and the explanation! Just one question: if I do like you explain, the resulting div is displayed with a delayed depending on which link i click on. For example: link 1 shows the div directly, but link 2 shows the div with a bit of delay. Does that mean that the timeout is increasing for every div? I want the divs to show as soon as the link is clicked but with a delay between them. Not with a delay at first. Hope you understand what I mean. – pistevw Jul 10 '17 at 18:37
  • @jdo Changed it quite a bit, but take a look now. – Tyler Roper Jul 10 '17 at 19:06
  • Thank you again for your help and for the explanations. I've looked through your first edit and I'll look through the other one as well. Really appreciate your time and effort! – pistevw Jul 10 '17 at 19:53
0
$('.filter_link').on('click', function(e) {
e.preventDefault();
var linkAttr = $(this).attr('data-related'); 
$('.blurb-content').each(function(i) {

    var thisBlurb = $(this);

    thisBlurb.removeClass('flip');
    if(thisBlurb.attr('data-dest') == linkAttr) {
        // this work
        setTimeout(function() {
            thisBlurb.addClass('flip'); //add class with delay
        }, 500 * i );
    }
});});

Copy paste your code and changed the $(this) of .blurb to the var thisBlurb assigned at the start of each loop.

The JavaScript keyword "this" is scoped to the function it's in. setTimeout uses a function so any "this" inside the setTimeout is no longer the element .blurb. "this" is now the function used by setTimeout :)

I also added parameter "i" in the function that the "each" loop is using. "i" is the index of each loop and will increase by 1 per iteration of the loop. Multiplying this index to the setTimeout millisecond parameter will make give you the effect you want.

calingasan
  • 805
  • 10
  • 7