Lucas
Lucas

Reputation: 3491

Javascript object method issue

I'm trying to create an object called List. This object has a method add which simply pushes a task object onto this tasks array. I also built a load method to load items from a url.

My issue is I can't seem to reference the add method from within the load method, I get the following error:

Uncaught TypeError: Object # has no method 'add'.

How do I reference the add method from within the load method? The code I am using is below.

function List(){
    this.tasks = new Array();
    this.add = function(taskItem){
        this.tasks.push(taskItem);
    };
    this.load = function(url){
        $.getJSON(
            url,
            function(data){
                $.each(data, function(key,val){
                    var task = new Task({
                        id:val.pkTaskId,
                        title:val.fldName,
                        status:val.fldStatus
                    });
                    this.add(task);
                });
            }
            );
    }
}

var userList = new List();
userList.load(url)

Upvotes: 0

Views: 287

Answers (4)

gilly3
gilly3

Reputation: 91667

The context for jQuery Ajax callbacks is an object that represents the options used to make the Ajax request. That is, the options object passed to the call to $.ajax(options), merged with $.ajaxSettings. You can override the context by setting the context option. This means calling $.ajax() instead of $.getJSON().

$.ajax({
    context: this,
    url: url,
    dataType: 'json',
    success: callback
});

Edit: Not sure why I was downvoted. It works the way I said. Try it here:

const ajaxOpts = {
    url: URL.createObjectURL(new Blob([null], { type: "application/json" })),
    dataType: 'json',
    success(data) {
        console.log(`this.foo(): '${this.foo()}'`);
    },
}

$.ajax(ajaxOpts);

$.ajax({
    ...ajaxOpts,
    context: {
        foo() {
            return "bar";
        },
    },
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>

So, plugging this into the code from the question should work:

function List(){
    this.tasks = new Array();
    this.add = function(taskItem){
        this.tasks.push(taskItem);
    };
    this.load = function(url){
        $.ajax({
            context: this,
            url: url,
            dataType: 'json',
            success: function(data){
                $.each(data, function(key,val){
                    var task = new Task({
                        id:val.pkTaskId,
                        title:val.fldName,
                        status:val.fldStatus
                    });
                    this.add(task);
                });
            },
        });
    }
}

var userList = new List();
userList.load(url)

Upvotes: 2

Mike Croteau
Mike Croteau

Reputation: 1132

To build off of Tomalak's answer, you could move the declaration of "self" to the main object level. This has proven to be pretty useful in the case of using this within nested object functions.

function List(){
   var self = this;
   self.tasks = new Array();
   self.add = function(taskItem){
      self.tasks.push(taskItem);
   };
   self.load = function(url){
      $.getJSON(
        url,
        function(data){
            $.each(data, function(key,val){
                var task = new Task({
                    id:val.pkTaskId,
                    title:val.fldName,
                    status:val.fldStatus
                });
                self.add(task);
            });
        });
    }
}

var userList = new List();
userList.load(url);

Upvotes: 0

Tomalak
Tomalak

Reputation: 338406

Try this:

function List(){
    this.tasks = [];  // prefer [] over new Array()
    this.add = function(taskItem){
        this.tasks.push(taskItem);
    };
    this.load = function(url){
        var self = this;
        $.getJSON(
            url,
            function (data){
                $.each(data, function(key,val){
                    var task = new Task({
                        id:val.pkTaskId,
                        title:val.fldName,
                        status:val.fldStatus
                    });
                    self.add(task);
                });
            }
        );
    }
}

The issue is that this is not what you think it is in the Ajax callback. The callback function is not called in the object's context, it is called in the global context (so this will point to the window object).

Saving an object reference (by convention called self) beforehand is necessary.


this will not always point to the object instance a function "belongs to". In fact, a function does not belong to an object in the same way it does in other languages. this maintains the context a function is called in. Any function can be called in any context:

function A() {
  this.val = "foo";
  this.say = function () { alert( "A: " + this.val ); };
}

function B() {
  this.val = "bar";
  this.say = function () { alert( "B: " + this.val ); };
}

function test() { alert( "T: " + this.val ); }

var a = new A(), b = new B();

a.say()         // alerts "A: foo"
b.say()         // alerts "B: bar"
b.say.call(a);  // alerts "B: foo";      (.call() switches the context)
test()          // alerts "T: undefined" (val does not exist in window)
test.call(b)    // alerts "T: bar"       (Ah!)

Unless you define context implicitly (b.say() implies that this will be b) or explicitly (by using call() or apply()), the context will be the global context - which in a browser is the window object. And that's exactly the case for your Ajax callback.

Upvotes: 2

Thibault J
Thibault J

Reputation: 4466

Use this syntax:

function List() {
    this.tasks = new Array();
}

List.prototype.add = function(taskItem) {
    this.tasks.push(taskItem);
}

var list = new List();
list.add(…);

Also, try to improve your accept rate, people will be more willing to help you.

Upvotes: 0

Related Questions