So, I created an object which makes an AJAX call to populate its properties during the initialization phase. However, I am running into a very weird behaviour: I can print and see the property values fine within the $.ajax() scope, but any public method that returns the value of properties have a return value of "undefined".
Here's what the JS code looks like:
function MyFunction() {
this.myProperty;
this.init();
}
Myfunction.prototype.getPropertyValue = function () {
alert(this.myProperty); // returns 'undefined'
}
Myfunction.prototype.init = function () {
$.ajax({
type: 'get',
url: "getProperty.php",
dataType: "json",
success: function(response) {
this.myProperty = response[0].Property;
alert(this.myProperty) // returns 'Property Name'
}
});
}
My thinking is that within the $.ajax() scope, 'this' is actually referring to something else. So, my question is how do I make sure that 'this.myProperty' is set and doesn't lose its value once we get outside of the AJAX scope?
Any help is much appreciated.
Part of the reason why you're getting "undefined" because of the way you establish the value:
var MyFunction = function () {
this.myProperty;
alert(this.myProperty); // undefined!
this.init();
};
When you declare properties (or variables) without specifying a value, they default to "undefined". Instead:
var MyFunction = function () {
this.myProperty = false;
alert(this.myProperty); // false
this.init();
};
On to the ajax call. You are right that the scope of the callback is not the same as the object. this, in the ajax success function, refers to the jQuery-wrapped XHR object. When you call this.myProperty = response[0].Property, you are actually creating a new property on the ajax object and setting its value. To correct this, you can either use the context option of the jQuery ajax object, OR bind the callback function using the javascript bind method:
success: function(response) {
this.myProperty = response[0].Property;
}.bind(this)
... or:
$.ajax({
type: 'get',
url: "getProperty.php",
dataType: "json",
context: this,
success: function(response) {
this.myProperty = response[0].Property;
}
});
Try it here: http://jsfiddle.net/SnLmu/
Documentation
bind() on MDN - https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
jQuery.ajax() - http://api.jquery.com/jQuery.ajax/
Functions and Function Scope on MDN - https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope
Part of the problem is that the ajax is asynchronous so the properties may not be set when you try to access them (race condition). The other is the value of this inside of the ajax call is not Myfunction. You can fix by:
Myfunction.prototype.init = function () {
var that = this;
$.ajax({
type: 'get',
url: "getProperty.php",
dataType: "json",
success: function(response) {
that.myProperty = response[0].Property;
alert(that.myProperty) // returns 'Property Name'
}
});
}
or you can use the context setting in the ajax call. Per the site:
This object will be made the context of all Ajax-related callbacks. By
default, the context is an object that represents the ajax settings
used in the call ($.ajaxSettings merged with the settings passed to
$.ajax). For example, specifying a DOM element as the context will
make that the context for the complete callback of a request, like so:
Myfunction.prototype.init = function () {
var that = this;
$.ajax({
type: 'get',
url: "getProperty.php",
dataType: "json",
context: Myfunction,
success: function(response) {
this.myProperty = response[0].Property;
alert(this.myProperty) // returns 'Property Name'
}
});
}
var MyFunction = {
myProperty: null,
init: function() {
var self = this;
self.ajax(function(response) {
self.myProperty = response;
self.secondfunction(self.myProperty); //call next step only when ajax is complete
});
},
ajax: function(callback) {
$.ajax({
type: 'get',
url: "getProperty.php",
dataType: "json"
}).done(function(response) {
callback(response[0].Property);
});
},
secondfunction: function(prop) {
alert(prop);
}
}
$(function() {
MyFunction.init();
});
Related
I'm trying to pass an object around in JS (with some jQuery thrown in). :)
I want to call the FlappyBarHelper.getUserPropertyCount() method once the promise.success function has run. I've tried passing this.FlappyBarHelper to :
return $.ajax({
type: "GET",
url: 'get-the-score',
flappy: this.FlappyBarHelper,
});
But that still makes flappy undefined in promise.success
My full code is:
function Rating(FlappyBarHelper) {
this.FlappyBarHelper = FlappyBarHelper;
}
Rating.prototype.attachRaty = function(property_id)
{
var promise = this.getPropertyScoreAjax(property_id);
promise.success(function (data) {
$('#'+property_id).raty({
click: function (score, evt) {
$.ajax({
type: "GET",
url: '/set-the-score',
})
.done(function (msg) {
$('#extruderTop').openMbExtruder(true);
//**** FlappyBarHelper is undefined at this point ****///
FlappyBarHelper.getUserPropertyCount('.flap');
});
}
});
});
};
Rating.prototype.getPropertyScoreAjax = function(property_id)
{
return $.ajax({
type: "GET",
url: 'get-the-score',
});
}
Read from the documentation of ($.ajax](https://api.jquery.com/jQuery.ajax/)
The this reference within all callbacks is the object in the context option passed to $.ajax in the settings; if context is not specified, this is a reference to the Ajax settings themselves.
Therefore you should pass your variable along the multiple call you are doing:
Rating.prototype.attachRaty = function(property_id){
var promise = this.getPropertyScoreAjax(property_id);
// it's best to use done
promise.done(function (data) {
$('#'+property_id).raty({
// use proxy to keep context when the click will be received
click: $.proxy(function(score, evt) {
$.ajax({
type: "GET",
url: '/set-the-score',
// propagate your variable
FlappyBarHelper: this.FlappyBarHelper
}).done(function (msg) {
$('#extruderTop').openMbExtruder(true);
// here it should be defined
this.FlappyBarHelper.getUserPropertyCount('.flap');
});
}, this);
});
});
};
Rating.prototype.getPropertyScoreAjax = function(property_id) {
return $.ajax({
type: "GET",
url: 'get-the-score',
// propagate your variable
FlappyBarHelper: this.FlappyBarHelper
});
}
You can also consider making a closure variable:
Rating.prototype.attachRaty = function(property_id){
// here is the closure variable
var helper = this.FlappyBarHelper;
var promise = this.getPropertyScoreAjax(property_id);
// it's best to use done
promise.done(function (data) {
$('#'+property_id).raty({
click: function(score, evt) {
$.ajax({
type: "GET",
url: '/set-the-score'
}).done(function (msg) {
$('#extruderTop').openMbExtruder(true);
// you can still use the defined variable: power (and danger) of closures
helper.getUserPropertyCount('.flap');
});
}, this);
});
});
};
Rating.prototype.getPropertyScoreAjax = function(property_id) {
return $.ajax({
type: "GET",
url: 'get-the-score'
});
}
I have a JavaScript bootstrapper module that I'm trying to keep very clean and simnple: Currently I have code like:
function bootStrapper() {
xmlRepository.getAll().done(function (result) {
autoPolicyForm.show();
});
}
In xmlRepository I am using a deferred object within getAll();
function getAll () {
var d = $.Deferred();
$.ajax({
type: "GET",
url: "http://localhost/Autopolicy/dataSource.xml",
dataType: "xml",
success: function (xml) {
d.resolve(xml);
}
});
return d.promise();
}
This code is working well but I would really like to simplify the bootstrapper code further to something like:
function bootStrapper() {
var result = xmlRepository.getAll();
autoPolicyForm.show();
});
}
Everything I try results in 'result' being undefined due to the async nature of call.
Does anyone know how to modify the code to move the complexity to the xmlRepository so that the above can be achieved?
Thanks
In modern jQuery the ajax function returns a promise so you can simplify getAll to :
function getAll () {
return $.ajax({
type: "GET",
url: "http://localhost/Autopolicy/dataSource.xml",
dataType: "xml"
});
}
In other words it is now possible to do
$.ajax(url).done(function(xml){...});
You could also change getAll to
function fetchAll (callback) {
$.ajax({
type: "GET",
url: "http://localhost/Autopolicy/dataSource.xml",
dataType: "xml"
}).done(callback);
}
So you'll do
xmlRepository.fetchAll(function (result) {
autoPolicyForm.show();
});
But apart setting async to false, you can't avoid passing a function as the execution is asynchronous. And you should consider that a javascript application is naturally event based so it's fine for the users of an API to use and pass anonymous functions (callbacks).
I've got computed function which should basically listen to any changes in observable array (Items). The problem is that Knockout calls this function on initialisation. I know it does it to figure out its dependencies but in my case it's a problem as it makes ajax request. Is it any solution beside introducing some counter variable to skip calling ajax on the first call?
Thanks.
function MyViewModel(data) {
var self = this;
self.nameToAdd = ko.observable("");
self.Items = ko.observableArray(data.items || []);
self.add = function () {
self.Items.push({ Name: self.nameToAdd()});
self.nameToAdd("");
};
self.remove = function (item) {
self.Items.remove(item);
};
ko.computed(function () {
$.ajax({
url: "myUrl",
type: 'POST',
data: ko.toJSON(self.Items),
contentType: 'application/json'
});
}, self);
}
I think what you really want is to manually subscribe to your Items array so when it is updated you call the ajax function.
self.Items.subscribe(function(){
$.ajax({
url: "myUrl",
type: 'POST',
data: ko.toJSON(self.Items),
contentType: 'application/json'
});
});
The function passed will only be called when a change has occured to the array itself, thus, not being called on initialization. See fiddle for example.
I would to use a callback function in the same 'class' when the ajax request is successful. Here's the code
$.ajax({
type: 'get',
url: ajax_url,
success: this.callback
})
this.callback = function() {
// ERROR! this doesn't point to the right context!
this.run_some_process();
}
Are there any built-in JavaScript constructs that can allow me to get or save the correct context, without having to resort to a custom delegate function?
If I understand your question correctly.
var that = this;
$.ajax({
type: 'get',
url: ajax_url,
success: that.callback
})
that.callback = function() {
// ERROR! this doesn't point to the right context!
that.run_some_process();
}
Have a look at $.ajax's 'context' option.
Why cant I access the render function when ajax returns successfully? maybe im going crazy but i've done this before.
Its telling me that this.render is not a function?
DataItem.prototype = {
display: function () {
$('body').append(this.name + ": " + this.getData(this.rootData, this.subData) + "<br />");
},
getData: function (rootData, subData) {
$.ajax({
type: "GET",
url: "json/data.js",
data: "",
dataType: "json",
success: function (json){
this.render(json);
}
});
},
render: function (json) {
var res = [];
for(var i=0, t; t=json.log.entries[i]; i++) {
var p = t.request.url;
if (p!=undefined) res.push(p);
}
return res.length;
}
};
The scope has changed when you try to call this.render(). I believe this contains the ajax request object instead of the DataItem object.
A simple solution is doing like this:
getData: function (rootData, subData) {
var self = this;
$.ajax({
type: "GET",
url: "json/data.js",
data: "",
dataType: "json",
success: function (json){
self.render(json);
}
});
},
Edit: I was wrong, inside the success function the this variable contains the options for the ajax request, however my solution is still correct. See more in the jQuery documentation (http://docs.jquery.com/Ajax/jQuery.ajax#options)
Just to add to #adamse answer. If you want to externalize your success function instead of using an anonymous function you could use the following to pass additional parameters:
function handleSuccess(json) {
this.self.render(json);
}
$(function() {
$.ajax({
type: "GET",
url: "json/data.js",
data: "",
dataType: "json",
// pass an additional parameter to the success callback
self: this,
success: handleSuccess
});
});
since the following code works (i.e "abcd" is printed), I am not sure what is the problem you are facing unless you would share more code.
<script>
DataItem = function(){};
DataItem.prototype = {
display: function () {
return 'a';
},
getData: function () {
document.write(this.render());
return this;
},
render: function () { return "abcd"; }
};
di = new DataItem();
di.getData();
</script>