4

I have issues getting my code to work as I try to break it down into smaller methods because of the this referencing. My code is as follow:

const pageObject = {

    /* set listener to get id of the radio input and then do something with it*/
    onChange() {

        // `this` here refers to the pageObject
        console.log(this);

        $('.radio input[type="radio"]').on('click', function() {

              // `this` here refers to the radio input
              console.log(this);

              let $id = $(this).data('id');

              // Error because `this` is referencing the radio input and not the pageObject.
              this.doSomething($id); 
        }

    },

    /* just does something */
    doSomething($id) {
        return ... 
    }

}

// just calls the method on object so we can get started
pageObject.onChange();

I also want to avoid using es6's arrow-functions () => and self = this techniques if possible as recommended in YDKJS: This & Object Prototypes.

Is there a way to .bind()/.call()/.apply() the method onChange() to reference the this which references to the pageObj and also the this which references to the radio input?

Feel free to rearrange the code if necessary. Thanks, looking forward!

Update

Thanks to event.target as suggested below, here is a working code block:

const pageObject = {

    /* set listener to get id of the radio input and then do something with it*/
    onChange() {

        // `this` here refers to the pageObject
        console.log(this);

       let radioHandler = function radioHandler(event) {

              // `this` here also refers to the pageObject too since we are about to call this function via .bind()
              console.log(this);

              // use event.target here instead. sweet stuff.
              let $id = $(event.target).data('id');

              // doSomething() now works!
              this.doSomething($id); 
        }

        $('.radio input[type="radio"]').on('click', radioHandler.bind(this));

    },

    /* just does something */
    doSomething($id) {
        return ... 
    }

}

// just calls the method on object so we can get started
pageObject.onChange();

Update 2

How to access the correct this inside a callback? as suggested by @gyre in the comments below provides a great detail of how to control this but doesn't mention event.target at all. Anyway, here is MDN Docs on Event.target

Community
  • 1
  • 1
clodal
  • 1,615
  • 20
  • 33
  • 1
    Possible duplicate of [How to access the correct \`this\` inside a callback?](http://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – gyre Feb 28 '17 at 04:14

3 Answers3

3

You can use event.target or event.currentTarget to reference element event is dispatched to. javascript is also missing closing ) at .on() call.

$('.radio input[type="radio"]').on('click', function(event) {

  // `this` here refers to the radio input
  let $id = $(event.target).data('radioId');

  // Error because `this` is referencing the radio input and not the pageObject.
  this.doSomething($id); 
})
guest271314
  • 1
  • 15
  • 104
  • 177
  • thanks, that helped point me in the right direction. though the this code would still cause an error since the this is still pointing to the radio input. I've updated my question to include the working answer . – clodal Feb 28 '17 at 04:21
  • You can pass `this` to `.on()`, inside handler use `event.data` to reference `this`:`pageObject` `.on("click", {pageObject:this}, function(event) {console.log(event.data.pageObject)})`. You can post and accept your own Answer http://stackoverflow.com/help/self-answer – guest271314 Feb 28 '17 at 04:24
2

Just go old style and set it to something else.

const pageObject = {

    /* set listener to get id of the radio input and then do something with it*/
    onChange() {

        // `this` here refers to the pageObject
        console.log(this);
        const self = this;

        $('.radio input[type="radio"]').on('click', function() {

              // `this` here refers to the radio input
              console.log(this);

              let $id = $(this).data('id');

              // Error because `this` is referencing the radio input and not the pageObject.
              self.doSomething($id); 
        };

    },

    /* just does something */
    doSomething($id) {
        return ... 
    }

}

// just calls the method on object so we can get started
pageObject.onChange();

In that particular case you don't need to use this inside the event handler by the way, you can have an event parameter and use event.target, like:

const pageObject = {

    /* set listener to get id of the radio input and then do something with it*/
    onChange() {

        // `this` here refers to the pageObject
        console.log(this);

        $('.radio input[type="radio"]').on('click', (event) => {

              // `this` here refers to the radio input
              console.log(event.target);

              let $id = $(event.target).data('id');

              // Error because `this` is referencing the radio input and not the pageObject.
              this.doSomething($id); 
        };

    },

    /* just does something */
    doSomething($id) {
        return ... 
    }

}

// just calls the method on object so we can get started
pageObject.onChange();

Even further...

const pageObject = {

    /* set listener to get id of the radio input and then do something with it*/
    onChange() {

        // `this` here refers to the pageObject
        console.log(this);

        $('.radio input[type="radio"]').on('click', this.onRadioClick.bind(this)) ;

    },

    onRadioClick(event) {

            // `this` here refers to the radio input
            console.log(event.target);

            let $id = $(event.target).data('id');

            // Error because `this` is referencing the radio input and not the pageObject.
            this.doSomething($id); 
    },

    /* just does something */
    doSomething($id) {
        return ... 
    }

}

// just calls the method on object so we can get started
pageObject.onChange();
Meligy
  • 35,654
  • 11
  • 85
  • 109
  • 1
    The 'Even further...' code here is also a working answer to this question since it is a complete answer and doesn't use es6's double-arrow function as stated in the question. Marked as answer! – clodal Feb 28 '17 at 04:44
1

You'll come across this and similar issues pretty often. Depending on the need, I solve it in one of two ways: using closures or binding.

Using closures works well in cases like yours here where you're setting an event and defining the event handler in the same parent function. You can take advantage of the fact the the child functions can access parent function variables and access a masked form of this

const pageObject = {

    /* set listener to get id of the radio input and then do something with it*/
    onChange() {

        const parent = this
        // 'parent and `this` here both refer to the pageObject
        // 'parent' masks an instance of 'this' which can be accessed via closures
        console.log('parent, this: ', parent, this);

        $('.radio input[type="radio"]').on('click', function() {

            // `this` here refers to the radio input
            console.log(this);

            let $id = $(this).data('id');

            // 'parent' accesses parent 'this' thanks to closures and masking 
            parent.doSomething($id); 
        }

    },

    /* just does something */
    doSomething($id) {
        return ... 
    }

}

// just calls the method on object so we can get started
pageObject.onChange();

Another method uses bind. This is particularly useful when you want to set an eventListener in one function that calls a handler function defined elsewhere but need information from the 'this' context of the function setting the listener. It you can use it to break down your code into even small functions.

An example using your example could look something like this:

const pageObject = {

    /* set listener to get id of the radio input and then do something with it*/
    onChange() {

        // `this` refers to the pageObject
        console.log(this);

        // creates radio onClick eventListener
        // assigns handleRadio function as event handler
        // binds pageObject 'this' context for use in event handler
        $('.radio input[type="radio"]').on('click', this.handleRadio).bind(this);
    },

    // separated event handler function to show how this works in more modular code.
    handleRadio(e) {
        // use e.target to give you the context for the click event
        // use 'this' to access the pageObject 'this' context
        console.log('e.target, this: ', e.target, this);

        // still accesses pageObject 'this' due to the eventListener bind
        let $id = $(this).data('id');

        // No error
        this.doSomething($id); 
    },

/* just does something */
doSomething($id) {
    return ... 
}

}

// just calls the method on object so we can get started
pageObject.onChange();
Isaac B
  • 695
  • 1
  • 11
  • 20
  • The closure method is the same as trying to setup a lexical`this`. I'll personally stick with `.bind()` for now as cited in [YDKJS](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md#lexical-this): _"While self = this and arrow-functions both seem like good "solutions" to not wanting to use bind(..), they are essentially fleeing from this instead of understanding and embracing it."_. But again, it could also be a matter of preference and consistency, so... great answer, thanks! – clodal Feb 28 '17 at 07:59