0

Ok, so I've been struggling with something I think it's pretty simple. Not sure if it's the Babel compiler or what. So I've got this class:

export default class animateFormClass {

    constructor(formSelector, buttonElement = null, currentCard = null, prevCard = null, nextCard = null) {
        this.form = document.querySelector(formSelector);
        this.direction = 'forward';
        this.progressBar = document.getElementById('progressbar');
        this.opacity = this.opacityLeft = 0;
        this.buttonElement = buttonElement;
        this.currentCard = currentCard;
        this.prevCard = prevCard;
        this.nextCard = nextCard;
    }

    animateForm() {
        if (this.direction === 'forward') {

            if (this.nextCard !== null) {
                this.fadeOut = setInterval(this.fadeOutAnimation, 10);
                this.updateProgressBar;
            }
        } else if (this.direction === 'back') {

            if (this.prevCard !== null) {
                this.fadeOut = setInterval(this.fadeOutAnimation, 10);
                this.updateProgressBar;
            }
        }

    }
    fadeOutAnimation() {
        this.opacity = this.opacity - 0.05;
        console.log(this.opacity, this.currentCard);
        if (this.opacity >= 0) {
            this.currentCard.style.opacity = this.opacity;
        } else {
            this.currentCard.classList.add('d-none');
            this.fadeIn = setInterval(this.fadeInAnimation, 10);
            clearInterval(this.fadeOut);
        }
    }

    fadeInAnimation() {
        this.opacityLeft = this.opacityLeft + 0.1;
        let cardElement;

        if (this.direction === 'forward') {
            cardElement = this.nextCard;
        } else if (this.direction === 'back') {
            cardElement = this.prevCard;
        }

        cardElement.classList.remove('d-none');

        if (this.opacityLeft <= 1) {
            cardElement.style.opacity = this.opacityLeft;
        } else {
            clearInterval(this.fadeIn);
        }
    }

    updateProgressBar() {
        let activeElement = this.progressBar.querySelector('.active');
        let nextListElement = activeElement.nextElementSibling;
        let prevListElement = activeElement.previousElementSibling;

        if (this.direction === 'forward') {
            if (nextListElement !== null) {
                activeElement.classList.remove('active');
                activeElement.classList.add('step-completed');
                nextListElement.classList.add('active');
            }
        } else if (this.direction === 'back') {
            if (prevListElement !== null) {
                activeElement.classList.remove('active');
                prevListElement.classList.remove('step-completed');
                prevListElement.classList.add('active');
            }
        }
    }
}

and from another file I'm importing the class like this:

import animateFormClass from './animateForm';

Ok so in this file I've got some code in which I update some of the object properties but in the animateForm() method the properties seem to be OK BUT when those same object properties are used by the other method called fadeOutAnimation() I've got this message of undefined in the this.currentCard property.

Here's the code in which I make the class instance:

if (document.getElementById('register')) {
    let animate = new animateFormClass('#register');
    let currentCard, prevCard, nextCard;
    document.getElementById('register').addEventListener('click', function(e) {
        if (e.target.classList.contains('next')) {
            animate.direction = 'forward';
            animate.currentCard = e.target.closest('fieldset');
            animate.nextCard = animate.currentCard.nextElementSibling;
            animate.animateForm();
        } else if (e.target.classList.contains('prev')) {
            animate.direction = 'back';
            animate.animateForm(e.target);
        }
    });
}

I get the error when the animate.animateForm() method is called. My environment is working on a PHP web app using Laravel and using the watch task that Laravel has out of the box.

EDIT:

Thank you for your help, I was able to fix it. If anyone was wondering, I changed all the methods of the class as properties and used the arrow function syntax. So thanks again!!!

  • 1
    Does this answer your question? [How to access the correct \`this\` inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – VLAZ Jan 03 '20 at 21:55
  • Does the error say `Cannot read property 'opacity' of undefined`? – Michael Rodriguez Jan 03 '20 at 21:56
  • @MichaelRodriguez I get ``` Cannot read property 'classList' of undefined ``` making reference to the ```this.currentCard.classList.add('d-none');``` line. – Miss Sandwich Jan 03 '20 at 22:08
  • @VLAZ I think you are right, it has to do with the this keywork usage. Because I just checked and the value of "this" in the fadeOut method is "window". OK I still have not solved it but I least I know where the issue is coming from, thanks! – Miss Sandwich Jan 03 '20 at 22:17
  • @MissSandwich the `this` context is lost because of the way you're passing the callback. JavaScript uses delayed context instantiation, so *basically* `this` is determined at the time you invoke a function. Also, *basically* it will be the object that you're invoking a function *from*, so `a.f()` will set `this` in `f` to `a` (the part before the `.` while if you have `a.b.c.f()` the context will be `a.b.c`. If you do `g = a.f` and then execute `g()` then there is no object you're executing the function from (no `.`, so nothing before it) and thus `this` will be `undefined`. – VLAZ Jan 03 '20 at 22:34
  • That is essentially what happens when you pass `this.someFunction` to be executed later. There are special rules for that event but in your case `window` is automatically assigned as `this`. [You can check for more info on how the `this` keyword works here](https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work). To see how to avoid the problem, check the other question I've linked – VLAZ Jan 03 '20 at 22:34
  • @VLAZ Thank you very much. I will have to take a look at this lol. Thanks again for helping me :) – Miss Sandwich Jan 04 '20 at 04:17
  • @VLAZ I fixed it! I used arrow functions, thank you very much, sir! :D – Miss Sandwich Jan 04 '20 at 21:29

2 Answers2

0

When you're doing setInterval(this.fadeOutAnimation, 10), 'this' inside 'fadeOutAnimation' is binded by window. Use setInterval(() => { this.fadeOutAnimation() }, 10); It preserves 'this' context.

sasha
  • 21
  • 2
0

Thank you for your help, I was able to fix it. If anyone was wondering, I changed all the methods of the class as properties and used the arrow function syntax. So thanks again!!!

Here's the new code:

export default class animateFormClass {

constructor(formSelector) {
    this.form = document.querySelector(formSelector);
    this.direction = null;
    this.progressBar = document.getElementById('progressbar');
    this.opacity = this.opacityLeft = 0;
    this.buttonElement = null;
    this.currentCard = null;
    this.prevCard = null;
    this.nextCard = null;

    this.animateForm = (buttonElement) => {
        this.buttonElement = buttonElement;
        this.currentCard = this.buttonElement.closest('fieldset');
        this.nextCard = this.currentCard.nextElementSibling;
        this.prevCard = this.currentCard.previousElementSibling;

        if(this.direction === 'forward') {          
            if(this.nextCard !== null) {
                this.fadeOut = setInterval(() => { this.fadeOutAnimation() }, 10);
                this.updateProgressBar();               
            }            
        }
        else if(this.direction === 'back') {    
            if(this.prevCard !== null) {
                this.fadeOut = setInterval(() => { this.fadeOutAnimation() }, 10);
                this.updateProgressBar();
            }
        }
    }

    this.fadeOutAnimation = () => {
        this.opacity = this.opacity - 0.05; 
        if(this.opacity >= 0) {
            this.currentCard.style.opacity = this.opacity;
        }
        else {
            this.currentCard.classList.add('d-none');
            this.fadeIn = setInterval(this.fadeInAnimation, 10);
            clearInterval(this.fadeOut);
        }
    }

    this.fadeInAnimation = () => {
        this.opacityLeft = this.opacityLeft + 0.1;
        let cardElement;

        if(this.direction === 'forward') {
            cardElement = this.nextCard;            
        }
        else if(this.direction === 'back') {
        cardElement = this.prevCard;
        }

        cardElement.classList.remove('d-none');

        if(this.opacityLeft <= 1) {
            cardElement.style.opacity = this.opacityLeft;
        }
        else {
            clearInterval(this.fadeIn);
        }
    }

    this.updateProgressBar = () => {
        let activeElement = this.progressBar.querySelector('.active');
        let nextListElement = activeElement.nextElementSibling;
        let prevListElement = activeElement.previousElementSibling;

        if(this.direction === 'forward') {
            if(nextListElement !== null) {
                activeElement.classList.remove('active');
                activeElement.classList.add('step-completed');
                nextListElement.classList.add('active');
            }
        }
        else if(this.direction === 'back') {
            if(prevListElement !== null) {
                activeElement.classList.remove('active');
                prevListElement.classList.remove('step-completed');
                prevListElement.classList.add('active');
            }
        }       
    }
}

}