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)
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.
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
});
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.
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);
Related
I have following code.
var handleJson = function(){
var jsonData ='xx';
var eventObject,eventObjectx=[];
that = this;
var convertJsonToObject = function(datax){
//debugger;
try {
that.printList(datax.data);
} catch(e) {
console.log(e);
}
//debugger;
}
return{
getDatafromserver: function(url){
$.ajax({
crossOrigin: true,
url: url,
success:convertJsonToObject
//console.log(jsonData);
});
},
printList:function(eventObject){
$.each(eventObject,function(index,val){
$('#eventlist').append('<li>'+val.event+'</li>');
})
}
}
}
var jsonHandler = new handleJson();
jsonHandler.getDatafromserver(url);
//jsonHandler.printList('eventlist');
});
Although the function printList exist its returning error
TypeError: that.printList is not a function {stack: (...), message:
"that.printList is not a function"}
Can any one help me out?
The value of this as of your that = this line is not in any way related to the object that you return at the end of your handleJson function.
Since you're using new with handleJson, you want to add to the object that this refers to, rather than returning a new, unrelated object:
var handleJson = function() {
var jsonData = 'xx';
var eventObject, eventObjectx = [];
var that = this;
var convertJsonToObject = function(datax) {
//debugger;
try {
that.printList(datax.data);
} catch (e) {
console.log(e);
}
//debugger;
}
this.getDatafromserver = function(url) {
$.ajax({
crossOrigin: true,
url: url,
success: convertJsonToObject
//console.log(jsonData);
});
};
this.printList = function(eventObject) {
$.each(eventObject, function(index, val) {
$('#eventlist').append('<li>' + val.event + '</li>');
})
};
};
var jsonHandler = new handleJson();
jsonHandler.getDatafromserver(url);
//jsonHandler.printList('eventlist');
Here's how new works:
It creates a new object backed by the object that the target function's prototype property refers to; so in your case, a new object backed by handleJson.prototype.
It calls the target function (handleJson) with this referring to that new object.
When the target function returns, if it doesn't return anything, or it returns a primitive value, or it returns null, new takes the object that it created as its result; but if the target function returns a non-null object reference, that overrides that default and new takes that object reference as its result.
In your original code, that last point was coming into play.
Side note: Your code was falling prey to The Horror of Implicit Globals becuse you weren't declaring that. I've added var on it above.
Side note: The overwhelming convention in JavaScript is that a constructor function (a function you're calling via new) starts with a capital letter. They're also usually nouns rather than verbs (other kinds of functions are usually verbs, but not constructor functions, which are named after the thing they construct). So JsonHandler rather than handleJson.
I have stored the names of the methods in a list.
var list = ['fn1', 'fn2', 'fn3', 'fn4'];
I select the method using some criteria dynamically. The methods are part of a larger class that are attached using 'prototype
MyObj.prototype.selectfn = function(criteria) {
var fn = list[sel];
this[fn].call(this, arg1);
}
MyObj.prototype.fn1 = function(args) { // do something }
MyObj.prototype.fn2 = function(args) { // do something}
And so on. The problem is inside the selected "fn" function, the this variable appears as a global object even though I used call() I read the mozilla docs, but I'm not able to understand why this is so, can someone help out please?
It it helps, my environment is node.js 0.10.12.
Edit : It's a little hard to provide the correct sample code because my code involves callbacks in a lot of places, but I'll try to elucidate.
Assume two files User.js and Helper.js.
User.js
var m, h;
var Helper = require('./Helper');
function init() {
// to simplify, assume the `this` here refers to `User`
h = new Helper(this);
}
function doSomething() {
// pass some criteria string
h.selectfn(criteria);
}
Helper.js
var Helper = module.exports = function(user) {
this.user = user;
}
Helper.prototype.selectfn = function(criteria) {
// based on some criteria string, choose the function name from "list" array
// here, the string "sel" holds the selected function name
var fn = list[sel];
this[fn].call(this.user, arg1);
// if I print to console, `this.user` is correct over here, but inside the function it shows as undefined
}
Helper.prototype.fn1 = function(args) {
// Here, I talk to databases, so I have callbacks. Say, on a callback, the user property is to be updated. This is why I want to use `call()` so that the `this` refers to `User` and can be updated.
// For example, if we want to update the "last-seen" date.
this.lastseen = new Date();
}
Hope the little example made it clearer.
the first parameter of call() is your function context "this"
as example:
var someObject = {
withAFunction: function(text) { alert('hello ' + text); }
};
var testFunction = function(text) {
this.withAFunction(text);
};
testFunction.call(someObject, 'world');
I tried to review your code:
var list = ['fn1', 'fn2', 'fn3', 'fn4'];
//MyObj is copy of Object?
var MyObj = Object;
//or a preudoclass:
//var MyObj = function(){};
//inside this function you use sel and arg1 that seems undefined and use like arguments criteria that seems useless
MyObj.prototype.selectfn = function(sel,arg1) {
var fn = list[sel];
this[fn].call(this, arg1); // this is MyObj istance.
MyObj.prototype.fn1 = function(args) { console.log("fn1", args);/* do something */ }
MyObj.prototype.fn2 = function(args) { console.log("fn2",args); /* do something */ }
xxx = new MyObj();
xxx.selectfn(1,"ciao");
//call xxx.fn1("ciao");
see the console for response.
Let's say I have the following code:
var Klass = function(){
var self = this;
this.value = 123;
this.update = function(){
$.ajax({
url: '/whatever',
async: false,
success: function(data){
$.extend(self, data);
}
});
}
}
Lets assume, '/whatever' returns this json object:
{value: 234}
And when I do this:
var obj = new Klass();
obj = ko.mapping.fromJS(obj);
console.log(obj);
We all know obj is now an knockoutjs observable.
And I run this:
obj.update();
console.log(obj);
What I have discovered is, value of obj doesn't get overridden as a simple value 234, but stayed as an observable property.
My questions are:
1) why is this?
2) How do I make the update work as I wanted.
UPDATE: ajax call is not asynchronous.
First issue is that you are extending self, which is a scoped variable and only exists inside the Klass function, not it's instances you create by calling it.
You'll need to call $.extend(this, data); if you need to overwrite value when calling update.
Although I do understand why you are using self there.
But the observable functionality added by calling ko.mapping.fromJS is then lost. value is no longer a function (ko observable) but a scalar value (234). You have to call obj = ko.mapping.fromJS(obj); again to wrap value as observable.
Second issue is that $.get is asynchronous so calling console.log(obj) right after calling obj.update will log the value before the GET response comes. You need to wait for it to execute (use a callback).
Here's a working fiddle.
var Klass = function(){
this.value = 123;
this.update = function(callback){
var self = this;
$.get('/', function(data) {
$.extend(self, {value: 234});
callback.call(undefined);
});
}
}
var obj = new Klass();
obj = ko.mapping.fromJS(obj);
console.log(obj.value());
obj.update(function() {
obj = ko.mapping.fromJS(obj);
console.log(obj.value());
});
I have written some object oriented Javascript like this:
function MyClass(){
this.SomeFunc(arg1){
result = <some processing on arg1>;
return result;
};
this.SomeOtherFunc(){
return $.ajax({
<some restful call>
}).done(function(){
var localvar = this.SomeFunc(<value obtained by restful call>);
<some operations with localvar>;
});
};
};
var myObj = new MyClass();
myObj.SomeOtherFunc();
And I get an error in the web console: "this.SomeFunc is not a function". If I call it within a function directly, there is no problem. The call fails only inside Ajax. What would be the proper way of making this function call?
this in your callback function is different from the this referring to SomeFunc, try doing:
this.SomeOtherFunc(){
var thatFunc = this; //get hold of this
return $.ajax({
<some restful call>
}).done(function(){
var localvar = thatFunc.SomeFunc(<value obtained by restful call>);
<some operations with localvar>;
});
};
Since you're using jQuery, you can also make sure of the $.proxy(http://api.jquery.com/jQuery.proxy/) method which allows you to pass in the context. For example, you could do
this.SomeOtherFunc(){
return $.ajax({
<some restful call>
}).done($.proxy(function(){
var localvar = thatFunc.SomeFunc(<value obtained by restful call>);
<some operations with localvar>;
}, this)); // Pass in what 'this' should be in method
};
Here, the callback function will execute with this referencing the object passed in as the second parameter.
$.proxy(function(){
// do stuff here
}, this);
Think of the primary function MyClass is your constructor.
This means you have to define the SomeFunc in there, but you are calling it.
That's the problem shown in you console.
You can fix it my defining the function there, instead of calling it:
function MyClass(){
// ----------vvvvvvvvvvv was missing
this.SomeFunc = function(arg1) {
result = <some processing on arg1>;
return result;
};
// ---------------vvvvvvvvvvv same here
this.SomeOtherFunc = function() {
var _this = this
return $.ajax({
<some restful call>
}).done(function(){
// ------------v use _this instead of _this
var localvar = _this.SomeFunc(<value obtained by restful call>);
<some operations with localvar>;
});
};
};
var myObj = new MyClass();
myObj.SomeOtherFunc();
Another way of defining the functions is via the prototype:
MyClass = function() { ... }
MyClass.prototype.SomeFunc = function(arg1) {
return <some processing on arg1>
}
MyClass.prototype.SomeOtherFunc = function() {
var _this = this
return $.ajax({ ... }).done(function(data) {
_this.SomeFunc(data)
})
}
The main difference is, that creating functions in the constructor will create a new function for each call of new MyClass.
Hope that helps.
So today I was coding an AJAX object.
I created a constructor, ajaxObj:
function ajaxObj( arg1, arg2, wrapper ) {
this.form = arg1;
this.btn = arg2;
this.msg = "Update successful";
this.url = "process.php";
this.wrap = wrapper; // this is the div the callback uses
this.serial = null; // serialized form for POSTing
this.callback = function () {
var div = document.createElement("div");
div.innerHTML = this.msg;
div.setAttribute( "id", "desiredID" );
this.wrap.appendChild(div);
}
this.btn.onclick = initXHR;
}
There were to be several objects of type ajaxObj instantiated on the given page. I wanted to include the functions that would not change and should be shared on the prototype:
ajaxObj.prototype = {
constructor: ajaxObj,
makeXHR: function () {
// cross browser XHR code returns XHR obj
}
makeSer: function () {
this.serial = $(this.form).serialize();
}
initXHR: function () {
// this is where shit gets weird!
this.makeSer(); // makeSer function doesnt exit
var http = this.makeXHR(); // makeXHR doesn't exist
http.onreadystatechange = function () {
/* this function checked for
http.status / http.readyState
and attempted to call this.callback()
which of course didn't exist.
I also tried to change instance
properties here which did not work */
}
http.open( "POST", this.url, true ); /* this.url did not work
and had to be replaced
with "process.php" */
http.setRequestHeaders("Content-Type","application/x..."); // TL;DT
http.send( this.serial ) // <--- this works just fine???
}
I've looked at many of the similar questions and given great time and consideration to this over the past week. I have my code working now, by taking callback out of the constructor as well as by taking makeXHR() and makeSer() off of the prototype and placing them all in global scope.
Despite the fact that I got my code working, to my chagrin, I still don't understand why this.url didn't work inside xhr.open() while this.serial works inside of xhr.send()
Bascally, why does 'this' work in some places of the prototype (such as replacing
ajaxObj.prototype = {
.............
initXHR: function () {
makeSer();
....
http.open( "POST", this.url, true );
....
http.send( this.serial );
....
}
with
ajaxObj.prototype = {
.............
initXHR: function () {
this.serial = $(this.form).serialize();
....
http.open( "POST", "process.php", true );
....
http.send(this.serial);
}
.... Please, un-bewilder me. I was under the impression that I had it figured out. Setting that=this inside the constructor seems to not work when I use the var keyword, and obviously(things are not always so obvious with javascript) removing the var keyword sets that equal to values were instantiated with the most recent object instance.
Just glancing over your code I can see that your initXHR function is only called when onclick of your target button fires. If you inspect this within initXHR I'm sure you'll find it is your button.
I think that based on your current class design you will need to use the old
var self = this;
in your constructor, and make your initXHR a privileged function (within your constructor) in order to access it. I've commented the code below to show you what I've added to your ctor.
function ajaxObj( arg1, arg2, wrapper ) {
// Assign this -> self at the start of the func
var self = this;
this.form = arg1;
this.btn = arg2;
this.msg = "Update successful";
this.url = "process.php";
this.wrap = wrapper;
this.serial = null;
this.callback = function () {
var div = document.createElement("div");
div.innerHTML = this.msg;
div.setAttribute( "id", "desiredID" );
this.wrap.appendChild(div);
}
// Make initXHR a privileged function, ie. one that has
// access to private variables in ctor
this.initXHR = function () {
self.url // should be accessible here
}
this.btn.onclick = this.initXHR; // Assign it.
}
Have a look at MDN's introduction to the this keyword. Probably you called the function "wrong", i.e. not on the ajaxObj instance. Show us that invocation.
The reason why this.serial works and this.url does not is that you just explicitly assigned to this.serial right before - though it likely was not a property on the object you expected.