0

I have an eventlistener that looks like this:

window.addEventListener('scroll', scroll.throttle(
    triggered, 
    {state: state, wrapper: wrapper, children: children, scroll: scroll},
    50
));

And I have a class that looks like this:

Scroll = class{
    constructor(){
        this.on = true; 
    }
    throttle(fn, v, wait){
        var time = Date.now();
        return () => {
            if ((time + wait - Date.now()) < 0 && this.on) {
                fn(v);
                time = Date.now();
            }
        }
    }
    triggered(o){
        if(o.state.check  !== 0){
            o.scroll.on = false;
            o.wrapper.classList.toggle('flic-down', o.state.check === 1)
            o.wrapper.classList.toggle('flic-up', o.state.check === -1)
            o.state.update();

            o.wrapper.classList.add('flic-transition')
            setTimeout(()=>{this.changeDone(o)}, 1200);
        }
    }
    changeDone(o) {
        o.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
        o.children.setClasses(o.state.state);
        o.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
        o.scroll.on = true;
    }
},

I don't like passing state, wrapper, children and scroll as variables. I would prefer to store them in the class when instantiating them. I understand the problem is that "this" won't be passed correctly and that it can be bound. But because the throttle function I don't understand how to pass this.

Himmators
  • 14,278
  • 36
  • 132
  • 223
  • Just a question: Is this valid JavaScript? If so, I'm all ears! I didn't realize you could use this kind of syntax – ControlAltDel Apr 13 '18 at 13:50
  • @ControlAltDel What makes you think it isn't? – Bergi Apr 13 '18 at 13:59
  • I don't see any issue with `this` - you're using arrow functions everywhere already. Have you just tried to make them instance properties? – Bergi Apr 13 '18 at 14:01
  • What is `triggered`, and why does your `throttle` function take `v` as an argument? – Bergi Apr 13 '18 at 14:02
  • @Bergi if i console.log(this) in triggered, it's undefined. – Himmators Apr 13 '18 at 14:02
  • @Bergi It seems to be a function that calls another function dynamically. – ReyHaynes Apr 13 '18 at 14:03
  • @Bergi Triggered is the actual code that is throttled, maybe it should have a new name. V is all the variables I want to pass to triggered. – Himmators Apr 13 '18 at 14:03
  • @Bergi this Q/A on SO goes to why I am confused. https://stackoverflow.com/questions/1728984/class-keyword-in-javascript. It claims that this functionality was part of JavaScript 2.0, which never rolled out. Can anyone shed some light on this? – ControlAltDel Apr 13 '18 at 14:04
  • 1
    Would changing your method classes like `throttle(fn, v, wait){}` into an arrow method class function help? `throttle = (fn, v, wait) => {}`. Might keep the context of `this` if you change all your method classes to arrows. – ReyHaynes Apr 13 '18 at 14:04
  • @ControlAltDel sure you can! (but only in edge not IE11) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes – Himmators Apr 13 '18 at 14:04
  • 1
    @ReyHaynes you can't do that in classes. Also it's not throttle() that is the problem, it's triggered(). Maybe I have to bind in several instances, i've tried binding to the event listener and the function that throttle responds with etc. – Himmators Apr 13 '18 at 14:08
  • @Himmators I use arrow method classes all the time. – ReyHaynes Apr 13 '18 at 14:08
  • ...and if `throttle` isnt the issue...also try `fn.bind(this, v)` within throttle. – ReyHaynes Apr 13 '18 at 14:10
  • @ControlAltDel You might want to read the second answer to the post you linked. `class` syntax is official since 2015. – Bergi Apr 13 '18 at 15:00

3 Answers3

0

I would recommend to separate the throttling from the scrolling.

class Throttle {
    constructor() {
        this.on = true; 
    }
    get(fn, wait) {
        var time = Date.now();
        return (...args) => {
            if ((time + wait - Date.now()) < 0 && this.on) {
                fn(...args);
                time = Date.now();
            }
        }
    }
}

class Scroll {
    constructor(state, wrapper, children) {
        this.state = state;
        this.wrapper = wrapper;
        this.children = children;
        this.throttle = new Throttle();
    }
    triggered() {
        if (this.state.check !== 0) {
            this.throttle.on = false;
            this.wrapper.classList.toggle('flic-down', this.state.check === 1)
            this.wrapper.classList.toggle('flic-up', this.state.check === -1)
            this.state.update();

            this.wrapper.classList.add('flic-transition')
            setTimeout(()=>{this.changeDone()}, 1200);
        }
    }
    changeDone() {
        this.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
        this.children.setClasses(this.state.state);
        this.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
        this.throttle.on = true;
    }
}

You then would do

const scroll = new Scroll(state, wrapper, children);
window.addEventListener('scroll', scroll.throttle.get(() => scroll.triggered(), 50));

Notice the arrow function being passed to throttle.get that calls triggered on the scroll instance, instead of passing the method without a context.


If you want to mix them both in the same class, I don't see why throttle would take a fn and v as parameters. You're only using it to call triggered anyway:

class ThrottledScroll {
    constructor(state, wrapper, children) {
        this.state = state;
        this.wrapper = wrapper;
        this.children = children;
        this.throttle = new Throttle();
        this.on = true; 
    }
    get(wait) {
        var time = Date.now();
        return () => {
            if ((time + wait - Date.now()) < 0 && this.on) {
                this.triggered();
                time = Date.now();
            }
        }
    }
    triggered() {
        if (this.state.check !== 0) {
            this.on = false;
            this.wrapper.classList.toggle('flic-down', this.state.check === 1)
            this.wrapper.classList.toggle('flic-up', this.state.check === -1)
            this.state.update();

            this.wrapper.classList.add('flic-transition')
            setTimeout(()=>{this.changeDone()}, 1200);
        }
    }
    changeDone() {
        this.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
        this.children.setClasses(this.state.state);
        this.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
        this.on = true;
    }
}

with

const scroll = new ThrottledScroll(state, wrapper, children);
window.addEventListener('scroll', scroll.get(50));
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

In your class constructor, you can bind your class methods' context to the class with this.methodName = this.methodName.bind(this). ReyHaynes is right, this can also be accomplished by defining class methods as arrow functions, but I believe that's still an uncomfirmed ES7 feature, so you might not want to use that.

If that's unclear or unhelpful, you might find some helpful context here. It's a React resource, but, if I understand correctly, your problem is one dealt with in React all the time.

wassona
  • 319
  • 1
  • 9
0

Adding the bind to the constructor worked for the class, that handles the binding issue when called out of context:

Scroll = class{
    constructor(){
        this.on = true; 
        this.triggered = this.triggered.bind(this)
        this.changeDone = this.changeDone.bind(this)
    }
    throttle(fn, v, wait){
        var time = Date.now();
        return () => {
            if ((time + wait - Date.now()) < 0 && this.on) {
                fn(v);
                time = Date.now();
            }
        }
    }
    triggered(o){
        if(o.state.check  !== 0){
            o.scroll.on = false;
            o.wrapper.classList.toggle('flic-down', o.state.check === 1)
            o.wrapper.classList.toggle('flic-up', o.state.check === -1)
            o.state.update();

            o.wrapper.classList.add('flic-transition')
            setTimeout(()=>{this.changeDone(o)}, 1200);
        }
    }
    changeDone(o) {
        o.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
        o.children.setClasses(o.state.state);
        o.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
        o.scroll.on = true;
    }
}

The event listener needed to have scroll.triggered vs triggered unless you deconstructed (const { triggered } = scroll) before it and we didn't see that:

window.addEventListener('scroll', scroll.throttle(
    scroll.triggered, 
    {state: state, wrapper: wrapper, children: children, scroll: scroll},
    50
))
ReyHaynes
  • 2,932
  • 1
  • 15
  • 22