0

I am not very familiar with using jquery other than toggling classes. That said, I will try to explain my problem as good as possible. I have three divs. After clicking one of them, the other two should should flip by 90degs and AFTERWARDS reduce their height to 0

I uploaded a short animation to youTube to show you how the final animation is meant to look like https://youtu.be/4ImmHJ04d0w

So my overly complicated script looks like that at the moment

// Add Temporarily Class
(function($){

    $.fn.extend({ 

        addTemporaryClass: function(className, duration) {
            var elements = this;
            setTimeout(function() {
                elements.removeClass(className);
            }, duration);

            return this.each(function() {
                $(this).addClass(className);
            });
        }
    });

})(jQuery);



$('.review--1').on('click', function() {
   $('[class^=review--]').not(this).addClass('review__hidden review__square'); 
   $('.review--2 ,.review--3, .review--4').removeClass('review__hidden');     
  });

  // Animation
  $('.review--1').on('click', function() {
   
   $('.review--2 ,.review--3, .review--4').addTemporaryClass('animate-in', 500);
   setTimeout(function() {
          $('.review--2 ,.review--3, .review--4').addClass('flip')
       }, 500); 
        
  });


  $('.review--2 ,.review--3, .review--4').on('click', function() {
   $(this).removeClass('review__square');
   $('.review--2 ,.review--3, .review--4').not(this).addTemporaryClass('animate-out', 500);
   var that = $(this);
     setTimeout(function() {
          $('.review--2 ,.review--3, .review--4').not(that).removeClass('flip').addClass('review__hidden')
       }, 500); 

          
   });
.review--button {
 overflow: hidden;
 color: #aa7f6f;
 width: 100%;
 float: left;
 height: 50px;
 background-color: lightgrey;
}

.review__square {
 margin: 6px 3px;
 width: 190px;
 height: 190px;
 text-align: center;
 transform: rotateY(90deg);
 perspective: 80px;
 -webkit-perspective: 80px;
 /* transition: height .5s ease, transform .3s ease; */
}
.review__hidden {
 height: 0;
 margin: 0;
 transform: rotateY(90deg);
}
.animate-in {
 animation: flip-in .5s forwards;
}



@keyframes flip-in {
 from {transform: rotateY(90deg);}
 to {transform: rotateY(0);}
}
.animate-out {
 animation: flip-out .5s forwards;
}



@keyframes flip-out {
 from {transform: rotateY(0);}
 to {transform: rotateY(90deg);}
}
.flip {
 transform: rotateY(0);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="review--button review--1">
    <i>1</i>
   </div>

   <div class="review--button review__square review--2 review__hidden">
    <i>2</i>
   </div>

   <div class="review--button review__square review--3 review__hidden">
    <i>3</i>
   </div>

   <div class="review--button review__square review--4 review__hidden">
    <i>4</i>
   </div>

   <div class="review--button review__square review--5 review__hidden">
    <i>5</i>
   </div>

   <div class="review--button review__square review--6 review__hidden">
    <i>6</i>
   </div>

   <div class="review--button review__square review--7 review__hidden">
    <i>7</i>
   </div>

The problem (beside my poor code) is that the .not(this) doesn't work within the Timeout. Can somebody please tell me how to do this? Or even better tell me how my crappy code could be eased :)

Sinan Theuvsen
  • 193
  • 1
  • 3
  • 14
  • So, when I click one `div` the other two just animate themselves so that they are no longer visible? – Scott Marcus Dec 11 '16 at 22:07
  • Read the MDN on setTimeout. You will see that this inside a setTimeout is changed. Either use ES6 arrow functions to bind the context, manually bind it with .bind or alias it. – ste2425 Dec 11 '16 at 22:15

3 Answers3

4

The this object binding is volatile in JavaScript...that is, it doesn't always point to the same object and its binding can change from one line of code to the very next. How you invoke the code that contains the word this determines what object it will bind to.

Had you cached your this object reference prior to the setTimeout like this:

var self = this;

you could then refer to self in the setTimeout and it would have worked.

Here's a checklist that you can follow to know what this will bind to (your scenario is #3 and you can read more about it here under the "The "this" problem" section)...

If the code that contains this is invoked:

  1. As a method or property of an object instance (through an instance variable):

    var o = new Object(); 
    
    // "this" will be bound to the "o" object instance
    // while "someProperty" and "someMethod" code executes
    o.someProperty = someValue;
    o.someMethod();
    
  2. Via a .call(), .apply(), .bind() or Array.prototype.fn invocation:

    // "this" will be bound to the object suppled as the "thisObjectBinding"
    someFunction.call(thisObjectBinding, arg, arg);
    someFunction.apply(thisObjectBinding, [arg, arg]);
    var newFunc = someFunction.bind(thisObjectBinding, arg, arg);
    

    Note: When a callback function is invoked (i.e. event handler), there is an implicit call to the handler when the event is triggered. In these cases, the object responsible for triggering the event becomes the object bound to this.

    Additionally, several Array.prototype methods allow for a thisObject to be passed which will alter the binding for the duration of the method call:

    Array.prototype.every( callbackfn [ , thisArg ] )
    Array.prototype.some( callbackfn [ , thisArg ] )
    Array.prototype.forEach( callbackfn [ , thisArg ] )
    Array.prototype.map( callbackfn [ , thisArg ] )
    Array.prototype.filter( callbackfn [ , thisArg ] )
    
  3. If none of the other scenarios apply, Default binding occurs.

    3a. With "use strict" in effect: this is undefined

    3b. Without "use strict" in effect: this binds to the Global object

** NOTE: this binding can also be affected by using eval(), but as a general best practice, the use of eval() should be avoided.

Having said all that, I'm not sure why you need a setTimeout at all (if I understood your scenario correctly):

var divs = document.querySelectorAll("div:not(#parent)");

divs.forEach(function(div){
  div.addEventListener("click", function(){
    
    var self = this;
    
    // get other two divs, not this one
    var $otherDivs = $(divs).not(this);
    
    // Fade them out:
    $otherDivs.fadeOut(function(){
       // JQuery animations accept a callback function to run when the animation is complete
       $(self).addClass("clickedDiv");
    });
  });
});
#parent { width:350px; border: 0; background:inherit;}

div {
  width:100px;
  height:100px;
  background:#ff0;
  text-align:center;
  float:left;
  border:1px solid black;
}

.clickedDiv {
  background:#f99;
  width:100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="parent">
   <div>I'm DIV 1</div>
   <div>I'm DIV 2</div>
   <div>I'm DIV 3</div>
</div>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • Good write up, but you forgot to mention the new arrow functions `() => {}`. How it binds context to the calling scope. (Essentially the same as manually calling `.bind()`. – ste2425 Dec 12 '16 at 07:45
  • Great answer, thanks. I need the timeout because the staying div also transforms to width:100%. So if all animations happen at the same time the staying div forces the fading divs to switch the row before they leave. That looks very messy. So my idea was to have a class that flips the leaving divs by 90degs and after this animation is finished the geht the height:0 class so that they don't block coming divs. Do you get what I mean? – Sinan Theuvsen Dec 12 '16 at 13:01
  • @SinanAl-Kuri Even so, you don't need a `setTimeout` since the JQuery animations accept a callback function to run when the animation is complete. See my updated answer for an example. – Scott Marcus Dec 12 '16 at 15:31
  • @ste2425 I didn't forget. Arrow functions don't actually cause any binding at all (so they are not like `bind`). In actuality, they just don't affect `this` binding, so they are not added to my checklist. – Scott Marcus Dec 12 '16 at 15:35
  • @ScottMarcus yup your right about it not binding a `this` however they are still worth a mention. They would perfectly solve the OP's issue (Which is the point after all) assuming he is in a compliant environment. Without needing all the sub standard alternatives. (Every time i see `var self = this` i cry a little :p) – ste2425 Dec 12 '16 at 15:41
  • @ste2425 I understand your point of view, but (as with so many things about JavaScript), "one man's garbage is another man's treasure". The volatile nature of `this` can be reviled and therefore the non-`this` binding nature of arrow functions would be embraced as a "saving grace". But, the flexibility of `this` binding can be embraced as a a feature and caching it in another variable can also be looked at as leveraging that language feature. There is nothing inherently "sub-standard" about `var self = this'`. To the contrary, it is a common JavaScript convention. – Scott Marcus Dec 12 '16 at 15:45
  • @ste2425 In point of fact, there are going to be many instances where arrow functions may wind up not being used because the desired effect is for `this` to change its binding while still preserving the original `this` value, meaning that the `var self = this;'` pattern isn't going anywhere - even though we now have arrow functions. – Scott Marcus Dec 12 '16 at 15:48
  • @ScottMarcus I understand that, which is why i complemented your write up so. However arrow functions do solve the OP's problem in a much neater way or at least to the extent the pseudo code provides. It also provides protection against the volatile nature of `this` which i presume the OP wants to avoid. However i could be completely wrong on that assumption. Either way I fear I'm derailing your answer. Also your first comment states how you invoke it determines what `this` will be. Which in it self should cover arrow functions to be complete. – ste2425 Dec 12 '16 at 15:53
  • @ste2425 *"arrow functions do solve the OP's problem **in a much neater way** or at least to the extent the pseudo code provides"*. My comments have been to explain that is merely your opinion and not the general consensus. `var self = this` also solves the OPs problem and many would say it does it in a very self (no pun intended) explanatory way, whereas with arrow functions, you just have to know that they don't affect binding. – Scott Marcus Dec 12 '16 at 16:16
  • @ScottMarcus Thank you again for your detailed answer. UZnfortunatelly your discussions are WAY beyond my understanding of JavaScript. So I try it with some kind of intellectual reset :) Since all of my animations work with css classes instead of jquery functions, jquery doesn't know the length of my animations. Therefor I need the Timeout to trigger all animations at the right moment. So can you give me a simple example of a code containing two or more different class that are added to a div one after another with different delays? I really appreciate your help :) – Sinan Theuvsen Dec 12 '16 at 19:07
  • @SinanAl-Kuri Can you update your question to include your relevant CSS and HTML? That way I can show an example that uses your code. FYI We can tell JQuery the length of your animations, which may (again) make this whole process simpler. – Scott Marcus Dec 12 '16 at 19:16
0

May be, you should use link to this? var self= $(this)

$('.review--2 ,.review--3, .review--4').on('click', function() {
  $(this).removeClass('review__square');
  $('.review--2 ,.review--3, .review--4').not(this).addTemporaryClass('animate-out', 500);

  var self = $(this);

  setTimeout(function() {
      $('.review--2 ,.review--3, .review--4').not(self).removeClass('flip').addClass('review__hidden')
  }, 500); 

  });
});
Alex Shtorov
  • 236
  • 1
  • 7
  • It's a bit dangerous to set `self = $(this)` as opposed to `self = this` because caching `this` in a variable called `self` is a common convention in JavaScript. Your suggestion would mean that `self` is actually referencing a JQuery wrapped set, not the actual `this` object. Another common convention is that if you are going to cache a JQuery wrapped set, you should qualify your variable with a prefix of `$` to remind you and others that the object is a JQuery object, so `var $self = $(this)` would be fine, except that is does mean that you have to "unpack" `this` out of the wrapped set. – Scott Marcus Dec 11 '16 at 22:34
0

Whilst Scott Marcus provides a very good explanation of how invoking a method can change its this object for some reason he doesn't want to update his answer to include how invoking a method using arrow functions affects the this object.

Quoting directly from MDN

An arrow function does not create its own this context, so this has the original meaning from the enclosing context.

So to extend Marcus's very good answer

var divs = document.querySelectorAll("div:not(#parent)");

divs.forEach(function(div){
  div.addEventListener("click", function(){
    // get other two divs, not this one
    var $otherDivs = $(divs).not(this);

    // Fade them out:
    $otherDivs.fadeOut(() => {
       // JQuery animations accept a callback function to run when the animation is complete
       $(this).addClass("clickedDiv");
    });
  });
});

As Marcus has pointed out in the comments Arrow functions are not the tool to use where you wish to have dynamic binding of this however if you find your self having to use var self = this often because your callback is having its context re-bound for you when you don't want it to then arrow functions may be useful.

Either way its another tool to the tool belt and all that,

Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
ste2425
  • 4,656
  • 2
  • 22
  • 37
  • *"for some reason he doesn't want to update his answer to include how invoking a method using arrow functions affects the this object."* Full discussion on this in the comments of my answer above....But, I didn't include it because, in fact (and as your quote from MSDN states), arrow functions simply don't affect `this` binding and my answer focuses on what *does* affect `this` binding. Arrow functions don't make that list. – Scott Marcus Sep 16 '17 at 20:45