0

I have a class which dynamically creates a button that calls an inputted function when it's clicked:

var TestObj = {
    name: "foobar",
    submit: function() {
        alert("my name is: " + this.name);
    },
    init: function() {
        $('<button>').click(function() { this.submit() }).text("hello").appendTo("#entryFormBox");
    }
};

TestObj.init();

(#entryFormBox is just the ID of the container it goes into.)

The problem is, I get the error:

Uncaught TypeError: this.submit is not a function

I can guess why this doesn't work (when the function is given to the button, "this" doesn't mean "TestObj" anymore, right?). But I'm still lost as to how to get this functionality the way I'm trying to get it to work. I want the submit function to call correctly when the button is pressed.

user3760657
  • 397
  • 4
  • 16

3 Answers3

1

You can use Function.prototype.bind() or $.proxy() to set this of a function call, as this within a jQuery event handler is set to the DOM element by default

var TestObj = {
  name: "foobar",
  submit: function() {
    alert("my name is: " + this.name);
  },
  clickHandler: function() {
    this.submit()
  },
  init: function() {
    $('<button>').click(this.clickHandler.bind(this))
    .text("hello")
    .appendTo("#entryFormBox");
  }
};

TestObj.init();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="entryFormBox"></div>

var TestObj = {
  name: "foobar",
  submit: function() {
    alert("my name is: " + this.name);
  },
  init: function() {
    $('<button>').click(
      $.proxy(function() {
        this.submit()
      }, this)
    )
    .text("hello")      
    .appendTo("#entryFormBox");
  }
};

TestObj.init();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="entryFormBox"></div>
guest271314
  • 1
  • 15
  • 104
  • 177
-1

Problem is that this is refering to click callback function. Try this:

var TestObj = {
    name: "foobar",
    submit: function() {
        alert("my name is: " + this.name);
    },
    init: function() {
         var self=this;
        $('<button>').click(function() { self.submit() }).text("hello").appendTo("#entryFormBox");
    }
};

TestObj.init();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<dv id="entryFormBox"></div>

About this keyword:

A function's this keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode. In most cases, the value of this is determined by how a function is called. It can't be set by assignment during execution, and it may be different each time the function is called. ES5 introduced the bind method to set the value of a function's this regardless of how it's called, and ES2015 introduced arrow functions whose this is lexically scoped (it is set to the this value of the enclosing execution context).

1. When used in global context

When you use this in global context, it is bound to global object (window in browser)

document.write(this);  //[object Window]

When you use this inside a function defined in the global context, this is still bound to global object since the function is actually made a method of global context.

function f1()
{
   return this;
}
document.write(f1());  //[object Window]

Above f1 is made a method of global object. Thus we can also call it on window object as follows:

function f()
{
    return this;
}

document.write(window.f()); //[object Window]

2. When used inside object method

When you use this keyword inside an object method, this is bound to the "immediate" enclosing object.

var obj = {
    name: "obj",
    f: function () {
        return this + ":" + this.name;
    }
};
document.write(obj.f());  //[object Object]:obj

Above I have put the word immediate in double quotes. It is to make the point that if you nest the object inside another object, then this is bound to the immediate parent.

var obj = {
    name: "obj1",
    nestedobj: {
        name:"nestedobj",
        f: function () {
            return this + ":" + this.name;
        }
    }            
}

document.write(obj.nestedobj.f()); //[object Object]:nestedobj

Even if you add function explicitly to the object as a method, it still follows above rules, that is this still points to the immediate parent object.

var obj1 = {
    name: "obj1",
}

function returnName() {
    return this + ":" + this.name;
}

obj1.f = returnName; //add method to object
document.write(obj1.f()); //[object Object]:obj1

3. When invoking context-less function

When you use this inside function that is invoked without any context (i.e. not on any object), it is bound to the global object (window in browser)(even if the function is defined inside the object) .

var context = "global";

var obj = {  
    context: "object",
    method: function () {                  
        function f() {
            var context = "function";
            return this + ":" +this.context; 
        };
        return f(); //invoked without context
    }
};

document.write(obj.method()); //[object Window]:global 

Trying it all with functions

We can try above points with functions too. However there are some differences.

  • Above we added members to objects using object literal notation. We can add members to functions by using this. to specify them.
  • Object literal notation creates an instance of object which we can use immediately. With function we may need to first create its instance using new operator.
  • Also in an object literal approach, we can explicitly add members to already defined object using dot operator. This gets added to the specific instance only. However I have added variable to the function prototype so that it gets reflected in all instances of the function.

Below I tried out all the things that we did with Object and this above, but by first creating function instead of directly writing an object.

/********************************************************************* 
  1. When you add variable to the function using this keyword, it 
     gets added to the function prototype, thus allowing all function 
     instances to have their own copy of the variables added.
*********************************************************************/
function functionDef()
{
    this.name = "ObjDefinition";
    this.getName = function(){                
        return this+":"+this.name;
    }
}        

obj1 = new functionDef();
document.write(obj1.getName() + "<br />"); //[object Object]:ObjDefinition   

/********************************************************************* 
   2. Members explicitly added to the function protorype also behave 
      as above: all function instances have their own copy of the 
      variable added.
*********************************************************************/
functionDef.prototype.version = 1;
functionDef.prototype.getVersion = function(){
    return "v"+this.version; //see how this.version refers to the
                             //version variable added through 
                             //prototype
}
document.write(obj1.getVersion() + "<br />"); //v1

/********************************************************************* 
   3. Illustrating that the function variables added by both above 
      ways have their own copies across function instances
*********************************************************************/
functionDef.prototype.incrementVersion = function(){
    this.version = this.version + 1;
}
var obj2 = new functionDef();
document.write(obj2.getVersion() + "<br />"); //v1

obj2.incrementVersion();      //incrementing version in obj2
                              //does not affect obj1 version

document.write(obj2.getVersion() + "<br />"); //v2
document.write(obj1.getVersion() + "<br />"); //v1

/********************************************************************* 
   4. `this` keyword refers to the immediate parent object. If you 
       nest the object through function prototype, then `this` inside 
       object refers to the nested object not the function instance
*********************************************************************/
functionDef.prototype.nestedObj = { name: 'nestedObj', 
                                    getName1 : function(){
                                        return this+":"+this.name;
                                    }                            
                                  };

document.write(obj2.nestedObj.getName1() + "<br />"); //[object Object]:nestedObj

/********************************************************************* 
   5. If the method is on an object's prototype chain, `this` refers 
      to the object the method was called on, as if the method was on 
      the object.
*********************************************************************/
var ProtoObj = { fun: function () { return this.a } };
var obj3 = Object.create(ProtoObj); //creating an object setting ProtoObj
                                    //as its prototype
obj3.a = 999;                       //adding instance member to obj3
document.write(obj3.fun()+"<br />");//999
                                    //calling obj3.fun() makes 
                                    //ProtoObj.fun() to access obj3.a as 
                                    //if fun() is defined on obj3

4. When used inside constructor function.

When the function is used as a constructor (that is when it is called with new keyword), this inside function body points to the new object being constructed.

var myname = "global context";
function SimpleFun()
{
    this.myname = "simple function";
}

var obj1 = new SimpleFun(); //adds myname to obj1
//1. `new` causes `this` inside the SimpleFun() to point to the
//   object being constructed thus adding any member
//   created inside SimipleFun() using this.membername to the
//   object being constructed
//2. And by default `new` makes function to return newly 
//   constructed object if no explicit return value is specified

document.write(obj1.myname); //simple function

5. When used inside function defined on prototype chain

If the method is on an object's prototype chain, this inside such method refers to the object the method was called on, as if the method is defined on the object.

var ProtoObj = {
    fun: function () {
        return this.a;
    }
};
//Object.create() creates object with ProtoObj as its
//prototype and assigns it to obj3, thus making fun() 
//to be the method on its prototype chain

var obj3 = Object.create(ProtoObj);
obj3.a = 999;
document.write(obj3.fun()); //999

//Notice that fun() is defined on obj3's prototype but 
//`this.a` inside fun() retrieves obj3.a   

6. Inside call(), apply() and bind() functions

  • All these methods are defined on Function.prototype.
  • These methods allows to write a function once and invoke it in different context. In other words, they allows to specify the value of this which will be used while the function is being executed. They also take any parameters to be passed to the original function when it is invoked.
Vlado Pandžić
  • 4,879
  • 8
  • 44
  • 75
-1

Try this:

var TestObj = {
    name: "foobar",
    submit: function() {
        alert("my name is: " + this.name);
    },
    init: function() {
        $('<button>').click(function() { TestObj.submit() }).text("hello").appendTo("#entryFormBox");
    }
};

TestObj.init();
sidewinder
  • 682
  • 4
  • 9