0

Event binding is a common pattern in JS, and here is one way to do it from a code I read: $('#toggle-all').on('change', this.toggleAll.bind(this));

In this case, there is an input with id toggle-all, which runs the this.toggleAll method when clicked.

However, even without binding, eg $('#toggle-all').on('change', this.toggleAll); the toggleAll method can still run, although it hits an error halfway through the method. But it still runs.

However, for another method: todoLi.appendChild(this.createDeleteButton());

this.createDeleteButton() does not run at all if I do not bind it. What is the difference between the two examples? I know one is passing in a (callback) function as a parameter, and the other is directly running the method. But the second example does not run at all without binding while the this.toggleAll method runs even without binding. Why is this so?

var App = {
    bindEvents: function () {
        $('#toggle-all').on('change', this.toggleAll); // without binding
    }, 

    toggleAll: function (e) {
        console.log(this); // prints jQuery object which is the input #toggle-all
        var isChecked = $(e.target).prop('checked');

        // error from this line onward, which is expected.
        this.todos.forEach(function (todo) { // "this" is the input element, hence error
           todo.completed = isChecked;
        });

       this.render();
    }
};

For the second option, here is the code:

var view = {
  displayTodos: function() {
    var todosUl = document.querySelector('ul');
    todosUl.innerHTML = '';

    todoList.todos.forEach(function(todo, position) {
      var todoLi = document.createElement('li');
      var todoTextWithCompletion = '';

      if (todo.completed === true) {
        todoTextWithCompletion = '(x) ' + todo.todoText;
      } else {
        todoTextWithCompletion = '( ) ' + todo.todoText;
      }

      todoLi.id = position;
      todoLi.textContent = todoTextWithCompletion;
      todoLi.appendChild(this.createDeleteButton());
      todosUl.appendChild(todoLi);
    }, this); // binding "this". If I remove it, the error I get in the console is: client.js:90 Uncaught TypeError: this.createDeleteButton is not a function
  },

  // creates a delete button beside every li element in the DOM.
  createDeleteButton: function() {
    console.log(this);
    var deleteButton = document.createElement('button');
    deleteButton.textContent = 'Delete';
    deleteButton.className = 'deleteButton';
    return deleteButton;
  },

Main question: Why is it that a method can be run without binding (in the first case). But in the second case, it cannot be run at all without binding? AKA Why does jQuery.on() accept the un-binded this.toggleAll, but appendChild is not able to accept the un-binded this.createDeleteButton()?

JXKENG
  • 89
  • 3
  • 10
  • Possible duplicate of [How to access the correct \`this\` inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – VLAZ May 21 '19 at 04:08
  • 1
    Basically, the problem is that the value of `this` could change depending on how it's executed. Binding a function sets the value of `this` inside it. – VLAZ May 21 '19 at 04:09
  • I think my question was more of: Why is it that a method can be run without binding (in the first case). But in the second case, it cannot be run at all without binding? – JXKENG May 21 '19 at 04:15
  • You said your first option *has an error*. That doesn't mean it "runs". And you haven't supplied any details about the second option. – VLAZ May 21 '19 at 04:17
  • Ah yes, I apologize. The first option does execute the console.log(this) line (hence the method "runs"), but it faces an error when trying to access this.todos.forEach, because "this" has not been binded. – JXKENG May 21 '19 at 04:20
  • Correct. However, we have no way of knowing why your second option doesn't work without any details about it. – VLAZ May 21 '19 at 04:23
  • Actually 'this' is a special keyword inside each function and its value only depends on how the function was called, not how/when/where it was defined. – Abhishek Upadhyay May 21 '19 at 04:23
  • Code need not be bolded. – Nishant May 21 '19 at 04:25
  • @freedomn-m I shall add the scope/context of first option. But I have little to no interest in "bind", I am curious as to why the method runs (the first few lines at least), *without* bind. – JXKENG May 21 '19 at 04:33
  • In summary: because jquery is calling `.bind` (or `.call`) for you – freedomn-m May 21 '19 at 04:39
  • @freedomn-m HAHA my guess was something along those lines too. Not very inclined to look into how the .on() method is executed. Shall just take that as an answer then. – JXKENG May 21 '19 at 04:43
  • Did you check you browser's console for error messages? These would probably be informative for you. `this` in `forEach()` callback is `globalThis` (i.e `window` in browser) except if you did pass the `thisArg` second argument, or somehow bound the callback. So when the engine encounters `this.createDeleteButton()` it's actually `window.createDeleteButton` that gets called, and I guess there is no such method available => You must have an Error message along the lines of *"Uncaught TypeError: this.createDeleteButton is not a function"*. – Kaiido May 21 '19 at 04:55
  • @Kaiido Yes, very perceptive. That is exactly what happened. I guess my overall question is: why does jQuery.on() accept the un-binded this.toggleAll, but appendChild is not able to accept the un-binded this.createDeleteButton() – JXKENG May 21 '19 at 05:01
  • But `jquery.on` has nothing to do with the problem here. You are merely doing `var mycallback = this.toggleAll; $('#toggle-all').on('change', mycallback)`. In this scope `this` is just your `App` object. nothing fancy. – Kaiido May 21 '19 at 05:03
  • Hmm... but why doesn't the unbinded this.createDeleteButton() work then? I feel like you are on the verge of an answer though. – JXKENG May 21 '19 at 05:06
  • Now with context: `this.createDeleteButton()` doesn't work because *it's inside another function*. Therefore `this` refers to the callback given to `forEach` - the `function(todo, position) { }`, and NOT to `view`. If you bind the callback, then it uses the correct `this`. This is exactly the same issue you get when you call `this.todos` where `this` doesn't have a `todos` property, only in this case it doesn't have a `createDeleteButton` property. So, again I refer you to the dupe. – VLAZ May 21 '19 at 05:18
  • Mhaaa... What do you not understand? In the second case, you are **in the forEach callback**. There `this` is `window`. In the first case, you are **in `App.bindEvents`**, `this` is `App`. – Kaiido May 21 '19 at 05:19
  • Okay, I have tried adding this line: printOne: function() { console.log("1"); console.log(this); // window object }, and subsequently: todos.forEach(this.printOne); and it works. This is for case 2. I kind of get it now. Thank you! @Kaiido – JXKENG May 21 '19 at 10:12

0 Answers0