Object's function getting called while sending data via AJAX - javascript

Sending a plain javascript object with function via AJAX request results in invoking this function.
Why is that happening?
The problem does not occur when sending JSON data.
What is the best way to convert that object to JSON?
I enclose sample code that presents this behaviour.
var A = (function () {
function A(value) {
this.value = value;
}
A.prototype.getValue = function () {
console.log('getValue');
return this.value;
};
return A;
})();
$(document).ready(function () {
var a = new A(10);
var xhr = $.ajax("http://example.com/", {
data: a,
type: 'post',
error: function (data) {
console.log('error');
},
success: function (data) {
console.log('success');
},
crossDomain: true
});
}
Output of the code is:
getValue
success

This is because jQuery.param (used internally by jQuery.ajax) uses the return value of a function when serializing the request parameters. From the docs:
As of jQuery 1.3, the return value of a function is used instead of the function as a String.
Thus, when jQuery.param encounters your a object and sees the getValue property being a function, it calls the function and uses the (serialized) the return value.
Either way, why would you want to pass such a complex object as request data? If you don't want to pass that function along with the request data, you're better off creating a custom serialization function for your A class, such as:
A.prototype.serialize = function() {
return { value: this.value };
}
and use that instead:
var xhr = $.ajax("http://example.com/", {
data: a.serialize(),
// ...
});

Related

Set Private Variable for Reuse In Encapsulated JavaScript function

I have a JavaScript function that is reading some data from a text file containing JSON. Ideally, I wanted to pass all the JSON data to a private variable within my encapulated function, so that if I was required to read the data again the variable could be used.
I don't want to be reading the text file everytime I'm need to make certain checks. To me, this doesn't sound good for performance.
Here's my code:
function someObj() {
var siteDataSet = null;
this.Init = function () {
populateData();
}
this.GetPageData = function () {
var props = new Array();
props[0] = siteDataSet.pagetitle;
props[1] = siteDataSet.week_no;
props[2] = siteDataSet.intro_text;
return props;
}
function populateData() {
$.ajax({
type: "GET",
url: "data.txt",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: "",
success: function (result) {
siteDataSet = result;
},
error: function () { },
complete: function () { }
});
}
}
$(document).ready(function () {
o_obj = new someObj();
o_obj.Init();
var t = o_obj.GetPageData();
});
As you can see, I'm calling Init() to use jQuery Ajax to pass all JSON to a variable called siteDataSet. When I call GetPageData(), the siteDataSet variable is null when this should not be the case.
Am I approaching what I am trying to achieve in a completely absurd way?
It's probably because your ajax call happens asynchronously and has not yet completed when you attempt to access the variable. Typically, in a situation like that, your best bet is to use a promise which will tell your code to expect a result, since it has not yet been set.
There are several libraries that provide an implementation, such as: https://github.com/promises-aplus/promises-spec
As an alternative, could create a method you wish to be called on completion that will have all of the code you wish to execute and then pass it in. Something like:
function afterRetrieved(o_obj) {
var t = o_obj.GetPageData();
...
}
o_obj.Init(afterRetrieved);
That passes the function into your Init method to be executed after the data is retrieved. It would require a few changes to your implementation as below:
this.Init = function (fn) {
populateData(fn);
}
function populateData(fn) {
....
success: function (result) {
siteDataSet = result;
fn(this)
},
...
}
I'd imagine that either of those should resolve your issue.

JavaScript / jQuery scope

Hi I'm extending an existing plugin to use static JSON rather than load it from the server. This is a trimmed down version of the extension:
(function ($) {
$.fn.MyExtension = function (options) {
return this.each(function () {
if (opts.load_Json) {
$.get("", function (result) {
fromJson(opts.load_Json)
});
}
var fromJson = function (json) {
// json stuff..
}
});
});
If I remove the $.Get and call fromJson directly without the call back I get an error saying that fromJson is not defined. This must be some form of scope issue but I cant work it out?
This isn't scope. This is timing.
A function isn't assigned to fromJson until the end of the anonymous function you pass to each.
If you call it from your get callback, then that assignment will happen before the HTTP response comes back and the function fires.
If you call it directly, then it just doesn't exist yet.
Either reorder:
return this.each(function () {
var fromJson = function (json) {
// json stuff..
}
if (opts.load_Json) {
$.get("", function (result) {
fromJson(opts.load_Json)
});
}
});
Or use a function declaration (which will be subject to hoisting):
return this.each(function () {
if (opts.load_Json) {
$.get("", function (result) {
fromJson(opts.load_Json)
});
}
function fromJson (json) {
// json stuff..
}
});

Pass or return JSON Object from jQuery GET

I have a function that makes a GET request for a nested JSON object. The object is returned successfully but you can't access the other objects within the returned object.
the object looks like this :
{
"student": {
"hobbies": ["reading", "dancing", "music"],
"subjects": ["english", "maths", "science"]
}
}
and this is the function :
var superObject = {
getData: function(obj) {
$.get(obj.target, function(callbackObject) {
// It works fine if i log callbackObject
// console.log(callbackObject);
return callbackObject;
}
},
useData: function() {
var data = superObject.getData({'target': 'file.json'});
var hobbies = data.student.hobbies;
console.log(hobbies); // This fails and returns nothing.
}
}
Due to asynchronous Ajax behaviour, you need to pass a callback function to execute once the data retrieved via Ajax is available; something like:
getData: function(obj, callback) {
$.get(obj.target, function(callbackObject) {
callback.call(null, callbackObject);
}
}
useData: function() {
superObject.getData({'target': 'file.json'}, function(data) {
var hobbies = data.student.hobbies;
});
}
The problem is that you're returning callbackObject from your $.get callback, and not from your getData function. $.get is asynchronous, so its callback will not fire until long after getData() has finished. That's why you're seeing undefined.
What about something like:
var superObject = {
getReuslts: {},
getData: function(obj) {
$.get(obj.target, function(callbackObject) {
getReuslts = callbackObject;
this.useData();
}
},
useData: function() {
var hobbies = getReuslts.student.hobbies;
console.log(hobbies);
}
}
Of course this would create a temporal dependency between useData and getData. Why not create this object in a function so you can add some encapsulation?
funcition getSuperObject = {
var result = {};
var getReuslts = {};
function useData() {
var hobbies = getReuslts.student.hobbies;
console.log(hobbies);
}
result.getData = function(obj) {
$.get(obj.target, function(callbackObject) {
getReuslts = callbackObject;
useData();
});
};
return result;
}
Or supply your own callback:
var superObject = {
getData: function(obj, callback) {
$.get(obj.target, function(callbackObject) {
if (callback)
callback(calbackObject);
});
}
}
And then
superObject.getData({'target': 'file.json'}, function(result) {
var hobbies = result.student.hobbies;
console.log(hobbies); // This fails and returns nothing.
});
$.get works asynchronously: you call it, then the browser goes off to make the request. Meanwhile, your code continues running. When the browser gets the response from the server, it invokes the callback function you provided passing it the results.
This means that when getData runs, it will return "almost immediately" and the request started by $.get will still be in progress in the background. So, getData cannot return anything meaningful to its caller because it can only schedule a requestl; it cannot know what the result will end up being.
So it follows that you cannot call getData like this:
var data = superObject.getData({'target': 'file.json'});
What you need to do instead is put the code that depends on the response inside the callback:
$.get(obj.target, function(data) {
var hobbies = data.student.hobbies;
console.log(hobbies); // This will now work
}
The scope of your variables is only in your function
You can try to set the variable hobbies out of your structure, and set it's value inside of your function
var hobbies; //global scope
var superObject = {
getData: function(obj) {
$.get(obj.target, function(callbackObject) {
// It works fine if i log callbackObject
// console.log(callbackObject);
return callbackObject;
}},
useData: function() {
var data = superObject.getData({'target': 'file.json'});
hobbies= data.student.hobbies;
//set it's value
}
}
You are returning in the callback handler. So your result is sent to the callback dispatcher which drops your result. What you need to do is attach your processing code to the get callback:
getData: function(obj) { return $.getJSON(obj.target); },
useData: function() {
getData({ target : 'file.json'}).then(function(results) {
var hobbies = results.student.hobbies;
console.log(hobbies);
}
}
You could turn your asynchronous AJAX call to be synchronous. See http://api.jquery.com/jQuery.ajax/, mainly the "async" option.
See this discussion:
Ajax jquery synchronous callback success

javascript prototype access this

I have a javascript 'class' which contains a wrapper method to call jquery .ajax(). I want to pass in the onSuccess and onError function handlers, but am not sure how. I can do this with plain old global functions, but I'm trying to improve my javascript (from Java background). Any pointers would be appreciated.
In the _makeAjaxCall() method below, how do I reference the onSuccessHandler
function testApp() {
new X();
}
function X() {
// Init X by making Ajax call, passing the func to be called on ajax return
this._makeAjaxCall(initUrl, this.onSuccessInit, this.onError);
// Make another ajax call to init another component
this._makeAjaxCall(initUrl, this.onSuccessSomeOtherAjaxCall, this.onError);
}
X.prototype.onSuccessInit = function(){
this.doStuff(...);
}
X.prototype.onSuccessSomeOtherAjaxCall = function(){
this.doOtherStuff(...);
}
/**
* make an ajax call, and call the provided success/error handler
*/
X.prototype._makeAjaxCall = function(url, onSuccessHandler, onError){
$.ajax({
url : url,
success : function (jsonData, textStatus, XMLHttpRequest) {
// If I don't user 'this', the func called but I've lost my reference
// to my instance of X
onSuccessHandler();
// If I use 'this', it points to the ajax call object, not to my X object.
this.onSuccessHandler();
}
});
}
The problem is that when the success callback is called by the $.ajax function, the default context is used window. You need to tell JQuery that you want a different context, so you can do one of 3 things:
Add a context attribute to the hash that is sent to $.ajax, so I your case you can do:
$.ajax({
url: url,
context: this, // this will tell JQuery to use the right context
success: this.onSuccessHandler
});
Use JQuery's $.proxy function, like:
$.ajax({
url: url,
success: $.proxy(this.onSuccessHandler, this) // this will bind the correct context to the callback function
});
Cache the variable this, like #mVChr suggested, although I would encourage you to use self as it has become somewhat of a javascript idiom
var self = this;
$.ajax({
url: url,
success: function(data) {
self.onSuccessHandler(data);
}
});
Edit:
If you need a more in depth explanation of context and scope in javascript checkout this article: http://www.digital-web.com/articles/scope_in_javascript/
Cache this within the local scope of _makeAjaxCall before conducting the ajax call:
X.prototype._makeAjaxCall = function(url, onSuccessHandler, onError){
var _X = this; // cache this
$.ajax({
url : url,
success : function (jsonData, textStatus, XMLHttpRequest) {
// use cached this
_X.onSuccessHandler();
}
});
}
Thanks to input from CarlosZ & mVChr, I've figured out the solution, http://jsfiddle.net/bX35E/3/
$(document).ready(function testApp() {
new X();
});
function X() {
console.dir(this);
var initUrl = "/echo/json/";
this._instanceVariable = "I AM defined!";
// Init X by making Ajax call, passing the func to be called on ajax return
this._makeAjaxCall(initUrl, this.onSuccessInit(), this.onError);
// Make another ajax call to init another component
this._makeAjaxCall(initUrl, this.onSuccessSomeOtherAjaxCall(), this.onError);
}
X.prototype.onSuccessInit = function(){
//this.doStuff(...);
var self = this;
return function() {
alert("onSuccessInit, _instanceVariable="+self._instanceVariable);
}
}
X.prototype.onSuccessSomeOtherAjaxCall = function(){
var self = this;
return function() {
alert("onSuccessSomeOtherAjaxCall, _instanceVariable="+self._instanceVariable);
}
}

round tripping callbacks

I have an Ajax call with a callback. (I am using YUI for this). The basic call looks like this:
function something() {
var callback1_success = function (o) {…
};
var callback1_failure = function (o) {…
};
var callback1 = {
success: callback1_success,
failure: callback1_failure
};
var callback2_success = function (o) {…
};
var callback2_failure = function (o) {…
};
var callback2 = {
success: callback2_success,
failure: callback2_failure
};
var ajax_params = buildAjaxParams(….);
Y_GET(ajax_params, callback1);
var ajax_params = buildAjaxParams(….); // different stuff
Y_GET(ajax_params, callback2);
} // something
function Y_GET(p_parms, p_callback) {
request = YAHOO.util.Connect.asyncRequest('GET', p_parms, p_callback);
return (request);
} // Y_GET
This all works fine.What I want to do is send the callback to the server, the server will not process the callback parameters and will send them back in the result set.
So, Y_GET will become: Function Y_GET(p_parms, p_callback) {
var parms_to_send = p_parms + “ & passthrough = ” + p_callback;
request = YAHOO.util.Connect.asyncRequest('GET', parms_to_send, local_callback);
return (request);
} // Y_GET
var local_callback = {
success: function (o) {
o.responseText.passthrough.success
},
failure: function (o) {
o.responseText.passthrough.failure
}
}; /* paste in your code and press Beautify button */
if ('this_is' == /an_example/) {
do_something();
} else {
var a = b ? (c % d) : e[f];
}
So, how do I pass a callback function to the server; which returns the name, and call it. If there is another approach, I am open to that, of passing a callback function and acting upon it in the response set.
Thank you.
Off the top of my head, there are a couple of approaches I can think of. eval() is a possibility, but is generally considered something to avoid given the risk of running arbitrary JS code (ultimately this depends on who is providing the string which is being evaled).
I would recommend the following approach:
Create you functions as declarations on a basic JS object.
var Callbacks = {
callback1: function(){ },
callback2: function(){ }
};
Then, use the string returned from your AJAX call as a property indexer into your Callbacks object. I'm not familiar with YUI AJAX requests, but hopefully you get the idea:
var p_callback = function(){
var local_callback = // parse response, get the callback method you want by name/string
Callbacks[local_callback](); // providing arguments as needed, of course
};
YAHOO.util.Connect.asyncRequest('GET', p_parms, p_callback);
By using property accessors on your object you are assured that you are executing only your own callback code, instead of arbitrary JavaScript that may have been included in the response.

Categories