user84296
user84296

Reputation: 361

How to access a Stimulus JS controller method from inside a nested function?

I have a Stimulus controller inside which I have a setSegments function and then this code in the connect() method:

connect() {
        const options = {
            overview: {
                container: document.getElementById('overview-container'),
                waveformColor: 'blue',
            },
            mediaElement: document.querySelector('audio'),
            dataUri: {
                arraybuffer: document.getElementById('normal-audio-button').dataset.waveform
            },
            emitCueEvents: true,
        };

        Peaks.init(options, function (err, peaks) {
            window.instance = peaks;
            window.speed = "normal";
            setSegments()
            instance.on('segments.enter', function (segment) {
                const segmentCard = document.getElementById(segment.id)
                segmentCard.focus({preventScroll: true})
                window.currentSegment = segment
            });
        });

    }

setSegments() {

  alert("segment set up)

}

I'm tryng to call setSegments() inside the Peaks.init function but it doesn't work because of the function's scope. I'm just not sure how to get around this. I tried calling this.setSegments() instead but it doesn't help.

What's the correct way of accessing the function in this case?

Thanks

Upvotes: 1

Views: 6473

Answers (2)

LB Ben Johnston
LB Ben Johnston

Reputation: 5186

The problem is that this is a bit confusing when working with JavaScript, however a way to think about it that it is the current context.

For example, when your code is running in the browser console or not in another function this is the global window object. When you are directly in the controller's connect method this is the controller's instance.

However, when you pass a function to Peaks.init that function creates it's own new context where this is the function's context and no longer the controller instance.

There are three common workarounds to calling this.setSegments;

1. Set a variable that is outside the function scope

As per your solution, const setSegments = this.setSegments; works because you are creating a reference outside the function scope and functions have access to this.

connect() {
        const options = {}: // ... Options
        // this is the controller's instance
        const setSegments = this setSegments;

        Peaks.init(options, function (err, peaks) {
            // this is the peaks init handler context
            window.instance = peaks;
            // this.setSegments(): - will not work
           setSegments();
            instance.on('segments.enter', function (segment) {
                // this is the event (usually)
            });
        });

    }

2. Use bind to override the function'sthis

You can pull your function out to a variable and then add .bind(this) to it so that when the function is called it will use the this from the controller instance instead.

connect() {
        const options = {}: // ... Options
        // this is the controller's instance
       const myFunction = function (err, peaks) {
            // this is the BOUND this provided by the bind command and will be the controller's instance
            window.instance = peaks;
            this.setSegments():
            instance.on('segments.enter', function (segment) {
                // this is the event (usually)
            });
        };
        myFunction.bind(this);

        Peaks.init(options, myFunction);

    }

3. Use an arrow function (easiest)

You should be able to use an arrow function in modern browsers or if you have a build tool running it may transpiled this for older browsers.

Instead of function() {} you use () => {} and it grabs the this from the parent function context instead.

connect() {
        const options = {}: // ... Options
        // this is the controller's instance

        Peaks.init(options, (err, peaks) => {
            // this is now the controller's instance and NOT the peak handler context
            window.instance = peaks;
            this.setSegments():
            instance.on('segments.enter', function (segment) {
                // this is the event (usually) and is NOT the controller instance as arrow function not used here.
            });
        });

    }

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this for more details

Upvotes: 3

user84296
user84296

Reputation: 361

I don't know if it's the best way to do it but adding the following right after the beginning of the connect method did the trick:

let setSegments = this.setSegments

Upvotes: 0

Related Questions