For a project of mine I need to do multiple calls to a (remote) API using JSONP for processing the API response. All calls use the same callback function. All the calls are generated dynamically on the client's side using JavaScript.
The problem is as follows: How do I pass additional parameters to that callback function in order to tell the function about the request parameters I used. So, e.g., in the following example, I need the myCallback function to know about id=123.
<script src="http://remote.host.com/api?id=123&jsonp=myCallback"></script>
Is there any way to achieve this without having to create a separate callback function for each of my calls? A vanilla JavaScript solution is preferred.
EDIT:
After the first comments and answers the following points came up:
I do not have any control over the remote server. So adding the parameter to the response is not an option.
I fire up multiple request concurrently, so any variable to store my parameters does not solve the problem.
I know, that I can create multiple callbacks on the fly and assign them. But the question is, whether I can avoid this somehow. This would be my fallback plan, if no other solutions pop up.
Your options are as follows:
Have the server put the ID into the response. This is the cleanest, but often you cannot change the server code.
If you can guarantee that there is never more than one JSONP call involving the ID inflight at once, then you can just stuff the ID value into a global variable and when the callback is called, fetch the id value from the global variable. This is simple, but brittle because if there are every more than one JSONP call involving the ID in process at the same time, they will step on each other and something will not work.
Generate a unique function name for each JSONP call and use a function closure associated with that function name to connect the id to the callback.
Here's an example of the third option.
You can use a closure to keep track of the variable for you, but since you can have multiple JSON calls in flight at the same time, you have to use a dynamically generated globally accessible function name that is unique for each successive JSONP call. It can work like this:
Suppose your function that generate the tag for the JSONP is something like this (you substitute whatever you're using now):
function doJSONP(url, callbackFuncName) {
var fullURL = url + "&" + callbackFuncName;
// generate the script tag here
}
Then, you could have another function outside of it that does this:
// global var
var jsonpCallbacks = {cntr: 0};
function getDataForId(url, id, fn) {
// create a globally unique function name
var name = "fn" + jsonpCallbacks.cntr++;
// put that function in a globally accessible place for JSONP to call
jsonpCallbacks[name] = function() {
// upon success, remove the name
delete jsonpCallbacks[name];
// now call the desired callback internally and pass it the id
var args = Array.prototype.slice.call(arguments);
args.unshift(id);
fn.apply(this, args);
}
doJSONP(url, "jsonpCallbacks." + name);
}
Your main code would call getDataForId() and the callback passed to it would be passed the id value like this followed by whatever other arguments the JSONP had on the function:
getDataForId(123, "http://remote.host.com/api?id=123", function(id, /* other args here*/) {
// you can process the returned data here with id available as the argument
});
There's a easier way.
Append the parameter to your url after '?'. And access it in the callback function as follows.
var url = "yourURL";
url += "?"+"yourparameter";
$.jsonp({
url: url,
cache: true,
callbackParameter: "callback",
callback: "cb",
success: onreceive,
error: function () {
console.log("data error");
}
});
And the call back function as follows
function onreceive(response,temp,k){
var data = k.url.split("?");
alert(data[1]); //gives out your parameter
}
Note: You can append the parameter in a better way in the URL if you already have other parameters in the URL. I have shown a quick dirty solution here.
Since it seems I can't comment, I have to write an answer. I've followed the instructions by jfriend00 for my case but did not receive the actual response from the server in my callback. What I ended up doing was this:
var callbacks = {};
function doJsonCallWithExtraParams(url, id, renderCallBack) {
var safeId = id.replace(/[\.\-]/g, "_");
url = url + "?callback=callbacks." + safeId;
var s = document.createElement("script");
s.setAttribute("type", "text/javascript");
s.setAttribute("src", url);
callbacks[safeId] = function() {
delete callbacks[safeId];
var data = arguments[0];
var node = document.getElementById(id);
if (data && data.status == "200" && data.value) {
renderCallBack(data, node);
}
else {
data.value = "(error)";
renderCallBack(data, node);
}
document.body.removeChild(s);
};
document.body.appendChild(s);
}
Essentially, I compacted goJSONP and getDataForUrl into 1 function which writes the script tag (and removes it later) as well as not use the "unshift" function since that seemed to remove the server's response from the args array. So I just extract the data and call my callback with the arguments available. Another difference here is, I re-use the callback names, I might change that to completely unique names with a counter.
What's missing as of now is timeout handling. I'll probably start a timer and check for existence of the callback function. If it exists it hasn't removed itself so it's a timeout and I can act accordingly.
This is a year old now, but I think jfriend00 was on the right track, although it's simpler than all that - use a closure still, just, when specifying the callback add the param:
http://url.to.some.service?callback=myFunc('optA')
http://url.to.some.service?callback=myFunc('optB')
Then use a closure to pass it through:
function myFunc (opt) {
var myOpt = opt; // will be optA or optB
return function (data) {
if (opt == 'optA') {
// do something with the data
}
else if (opt == 'optB') {
// do something else with the data
}
}
}
Related
I am making a ajax call as follows:
util.AjaxCall(url,successCallbackFunction,errorCallbackFunction);
function successCallbackFunction(result){
//Ajax returns result
}
Everything is working fine, except I have to pass one of my own parameter to be available on 'successCallbackFunction'.
Something like
function successCallbackFunction(result, passedParameter){
}
How can I do that ?
Note: I don't have access to util.AjaxCall source code ( I am not authorized to change that code).
You can use bind:
util.AjaxCall(url,
successCallbackFunction.bind(this, passedParameter),
errorCallbackFunction);
The first parameter is the functions scope and in your case propably negligible.
Which will then trigger your successCallbackFunction with an additional (prepended) parameter:
function successCallbackFunction(passedParameter, result){}
Normally when you are sending/receiving Ajax request you exchange data with JSON format.
so that you can receive your data as JSON Object.
For example if the line below is the return result via Ajax call
{"firstname":"john","lastname":"doe"}
then you can retreive your data by
function successCallbackFunction(result){
var obj = jQuery.parseJSON(result); // if you are using Jquery if not use eval
// var obj = eval(result); // this is not recommeneded for security issue.
var firstname = obj.firstname;
var lastname = obj.lastname;
}
I seem to be having some issues with making HEAD requests, and preserving the integrity of data in an array.
Given this snippet:
var imageTemp = Array();
$('*')
.each(function(index){
if($(this).css('background-image') != 'none'){
imageTemp.push($(this).css('background-image').slice(5, -2));
}
});
I capture the URLs of all background-images on a given page. Now, trying to grab the size of each image via HEAD requests for Content-Length, I use this snippet:
var imageData = Array();
for(var i = 0; i < imageTemp.length; i++){
ajaxSizeRequest = $.ajax({
type: "HEAD",
async: true,
url: imageTemp[i],
success: function(message){
imageData.push([imageTemp[i], ajaxSizeRequest.getResponseHeader('Content-Length')]);
}
});
}
However, when I dump imageData via console.log, I each element (which should be an array containing the URL and the content-length) ends up as [undefined, XXXX] where XXXX is always the size of the last requested Content-Length
I'm stumped, though it appears to be a timing/scoping issue. Do I have a sort of race-condition occuring here?
The problem is that the single variables i and ajaxSizeRequest being captured by the callback function are the same variables for all instances of the callback function. I think if you call a function and pass the index variable to it and, at the same time, scope the request variable locally to the function itself use the response parameter of the done handler, you should end up with independent variables captured by the callback. It should then reference each array element and each response variable correctly.
var imageData = Array();
for(var i = 0; i < imageTemp.length; i++){
updateImageData( i );
}
function updateImageData( i )
$.ajax({
type: "HEAD",
async: true,
url: imageTemp[i],
}).done(function(message,text,jqXHR){
imageData.push([imageTemp[i], jqXHR.getResponseHeader('Content-Length')]);
});
}
looks like your i isnt properly closed-in
in addition, you can't use ajaxSizeRequest because it too is pointing to just one request (probably the last, because the loop will execute very fast)
just wrap your success callback function as follows, changing the reference to ajaxSizeRequest:
success: (function(i){
return function(data,status,xhr){
imageData.push([imageTemp[i], xhr.getResponseHeader('Content-Length')]);
};
})(i)
You can scope I like so:
success: function(i){
return function(message){
imageData.push([imageTemp[i], ajaxSizeRequest.getResponseHeader('Content-Length')]);
}
}(i)
You have a single i variable which is shared by all of the callbacks.
Since AJAX is asynchronous, all of the callbacks run after your loop is finished, and they all get the same i.
To fix this, you need to move the AJAX call into a separate function that takes i as a parameter.
Thus, each callback will get a separate i parameter.
If anyone still having trouble with this, and since this post is, like, 5 years-old already, here's a more 'modern' version of the answer: just use let instead of var in the original post's for loop.
Info: Is there any reason to use the “var” keyword in ES6?
and: MDN - Let syntax
I have a small jQuery script that gets information by looking at an ID.
What is the best way to prevent that the same data are requested more than once (e.g. what's the best practices for caching in jQuery)?
I have tried to use $.post and $.ajax with option "cache" set to true, but the request is being sent more than once.
Is it better to save collected data and use sets to see whether you'll have to request it or not?
Any ideas and suggestions are welcome!
If it matters, I use ASP.Net MVC on the server-side.
The cache option you saw on the documentation, refers to the browser's cache.
You can implement a pattern of self-memorizing functions in many ways, the goal is that the function result for determined argument (id in your case) is only computed once.
Since you are using an Ajax request, I would suggest you to use a callback argument also, e.g.:
var getInfo = (function () {
var cache = {}; // results will be cached in this object
return function (id, callback) {
if (cache[id] != null) { // if exist on cache
callback(cache[id]);
return;
}
// doesn't exists on cache, make Ajax request and cache it
$.post("info.url", { "id": id }, function (data) {
cache[id] = data; // store the returned data
callback(data);
});
};
})();
Example usage:
getInfo(5, function (data) {
alert(data);
});
Here is what I got so far. Please read the comment in the code. It contains my questions.
var customer; //global variable
function getCustomerOption(ddId){
$.getJSON("http://localhost:8080/WebApps/DDListJASON?dd="+ddId, function(opts) {
$('>option', dd).remove(); // Remove all the previous option of the drop down
if(opts){
customer = jQuery.parseJSON(opts); //Attempt to parse the JSON Object.
}
});
}
function getFacilityOption(){
//How do I display the value of "customer" here. If I use alert(customer), I got null
}
Here is what my json object should look like: {"3":"Stanley Furniture","2":"Shaw","1":"First Quality"}. What I ultimately want is that, if I pass in key 3, I want to get Stanley Furniture back, and if I pass in Stanley Furniture, I got a 3 back. Since 3 is the customerId and Stanley Furniture is customerName in my database.
If the servlet already returns JSON (as the URL seem to suggest), you don't need to parse it in jQuery's $.getJSON() function, but just handle it as JSON. Get rid of that jQuery.parseJSON(). It would make things potentially more worse. The getFacilityOption() function should be used as callback function of $.getJSON() or you need to write its logic in the function(opts) (which is actually the current callback function).
A JSON string of
{"3":"Stanley Furniture","2":"Shaw","1":"First Quality"}
...would return "Stanley Furniture" when accessed as follows
var json = {"3":"Stanley Furniture","2":"Shaw","1":"First Quality"};
alert(json['3']);
// or
var key = '3';
alert(json[key]);
To learn more about JSON, I strongly recommend to go through this article. To learn more about $.getJSON, check its documentation.
getJSON will fire an asynchronous XHR request. Since it's asynchronous there is no telling when it will complete, and that's why you pass a callback to getJSON -- so that jQuery can let you know when it's done. So, the variable customer is only assigned once the request has completed, and not a moment before.
parseJSON returns a JavaScript object:
var parsed = jQuery.parseJSON('{"foo":"bar"}');
alert(parsed.foo); // => alerts "bar"
.. but, as BalusC has said, you don't need to parse anything since jQuery does that for you and then passes the resulting JS object to your callback function.
var customer; //global variable
function getCustomerOption(ddId){
$.getJSON("http://localhost:8080/WebApps/DDListJASON?dd="+ddId, function(opts) {
$('>option', dd).remove(); // Remove all the previous option of the drop down
if(opts){
customer = opts; //Attempt to parse the JSON Object.
}
});
}
function getFacilityOption(){
for(key in costumer)
{
alert(key + ':' + costumer[key]);
}
}
We're using Prototype for all of our Ajax request handling and to keep things simple we simple render HTML content which is then assigned to the appropriate div using the following function:
function ajaxModify(controller, parameters, div_id)
{
var div = $(div_id);
var request = new Ajax.Request
(
controller,
{
method: "post",
parameters: parameters,
onSuccess: function(data) {
div.innerHTML = data.responseText;
},
onFailure: function() {
div.innerHTML = "Information Temporarily Unavailable";
}
}
);
}
However, I occasionally need to execute Javascript within the HTML response and this method appears incapable of doing that.
I'm trying to keep the list of functions for Ajax calls to a minimum for a number of reasons so if there is a way to modify the existing function without breaking everywhere that it is currently being used or a way to modify the HTML response that will cause any embedded javascript to execute that would great.
By way of note, I've already tried adding "evalJS : 'force'" to the function to see what it would do and it didn't help things any.
The parameter is:
evalScripts:true
Note that you should be using Ajax.Updater, not Ajax.Request
See: http://www.prototypejs.org/api/ajax/updater
Ajax.Request will only process JavaScript if the response headers are:
application/ecmascript,
application/javascript,
application/x-ecmascript,
application/x-javascript,
text/ecmascript, text/javascript,
text/x-ecmascript, or
text/x-javascript
Whereas Ajax.Updater will process JS is evalScripts:true is set. Ajax.Request is geared toward data transport, such as getting a JSON response.
Since you are updating HTML you should be using Ajax.Updater anyways.
Does setting evalScripts: true as an option help?
You should be able to do something like this:
div.innerHTML = "<div onclick='someOtherFunctionTocall();'>";
If you need to execute something at the same time as injecting the HTML, can you modify the signature of ajaxModify() by passing another parameter, which will be the javascript function you're going to execute (if it's not null - which let's you keep it optional, as you surely won't want to execute something on EVERY AJAX response).
Just execute a custom my_function() after the ajax response
div.innerHTML=...ajax response text...
my_function()
then execute any function inside the custom my_function()
function my_function() {
function_1()
...
}
Note that my_function() should be somewhere outside the div.innerHTML.
you need to use eval() function to run the javascript in Ajax repose
this can be use full to separate the script and run it
function PaseAjaxResponse(somemixedcode)
{
var source = somemixedcode;
var scripts = new Array();
while(source.indexOf("<script") > -1 || source.indexOf("</script") > -1) {
var s = source.indexOf("<script");
var s_e = source.indexOf(">", s);
var e = source.indexOf("</script", s);
var e_e = source.indexOf(">", e);
scripts.push(source.substring(s_e+1, e));
source = source.substring(0, s) + source.substring(e_e+1);
}
for(var x=0; x<scripts.length; x++) {
try {
eval(scripts[x]);
}
catch(ex) {
}
}
return source;
}
alliteratively for more information see this
http://www.yasha.co/Ajax/execute-javascript-on-Ajax-return/article-2.html