setTimeout and this binding

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 :)

Answers:

Answer

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>

Answer

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); 

  });
});
Answer

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,

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.