I wrote a function which I'm allowing a callback to be passed to. I then want to execute that with both the ajax repsonse, and another parameter.
My problem is the the callback is executed, and when I step through the code, it looks like the function is called with the proper parameters, however when actually stepping in to the callback function, the first parameter takes the value of whatever I assign to the second argument, and the second argument is undefined.
Here is my function:
namespace = {
fetch : function(sr, count, callback) {
srCount = count ? count : '25'
var sub;
if (sr == 'frontpage'){
sub = '';
}else{
sub = 'foo/' + sr + '/';
};
$.ajax({
url: "http://www.example.com/"+sub+ ".json?count=" + count,
dataType: "json",
success: function(msg)
{
callback.call(msg, count)
}
})
};
Now, when I call it like this:
mynamespace.fetch($(this).attr('href'), 25, true, another namespace.createPost);
I would expect callback.call(msg, count) to resolve to callback.call(/*the ajax response*/, 25);
However, when I run it, I get msg == 25 and count == 'undefined'. I'm at a loss for why...
.call calls a function with explicit context given by the first argument, so callback.call(msg, count) calls the callback function with msg set as context (the this value inside the callback function for this call) and count as a first argument.
So you probably want callback( msg, count ) or callback.call( namespace, msg, count ); which means that this inside the callback will refer to namespace for that call.
$.ajax() function has a context parameter, which you can use for this purpose.
$.ajax({
url: "http://www.example.com/"+sub+ ".json?count=" + count,
context: "hello",
dataType: "json",
success: function(msg)
{
// here 'this' will be the 'hello' you specified for context while making the call.
callback.call(msg, count)
}
})
Related
In this tiny bit of (pure) js code, for example :
document.getElementById("example").addEventListener('click', function(event) {
event.preventDefault();
}
What exactly is this "event" parameter ? I could also call it "myGoat", right ? Where/when is it defined that this parameter refers to the actual event ?
Another jQuery example :
request = $.ajax({
url: "cre_devis.php",
type: "post",
data: someData
});
request.done(function (response, textStatus, jQueryXMLHttpRequest){
document.getElementById("serverAnswer").innerHTML = response;
});
How are response, textStatus and jQueryXMLHttpRequest defined ? I suppose it is related to the .done method ?
Those are callback functions and they receive the parameter from the code which is calling it which in this case happens on some event such as an event.
Every function in JavaScript is a Function object. You can pass it as a parameter to some other function like any other object. Example:
function bar(value, callback) {
callback(value);
}
bar('actual value', function(value) {
console.log(value);
});
You can read about callback functions at:
https://developer.mozilla.org/en-US/docs/Glossary/Callback_function
I have jQuery function:
$('#fetch').click( function() {
...
GetPage( curPage, items, DrawItemSet( items ) )
});
As you can see, I want the callback function DrawItemSet be executed from inside GetPage function. Sometimes, I need to call not DrawItemSet but some other functions.. I just want to any function I need to be called from GetPage function. It is necessary to do some afterwork after (as you may guesses) I get the page. In this example I want to display some data. In other cases I want not to display some text, but, for example, swap information got from GetPage function. In that case, my idea was to write something like:
...
GetPage( curPage, items, SwapItemSet( oldItems ) )
As fo now, everything seems to be ok.
Here's my GetPage listing:
function GetPage( pageNum, collection, callback ) {
console.log( 'Starting to get page number ' + pageNum )
$.ajax({
url: 'http://www.xxxxxxxxxxx.php?page=' + pageNum,
type: 'get',
dataType: '',
success: function(data) {
...
console.log( 'Curpage=' + curPage + ' MaxPage=' + maxPages )
if( curPage < maxPages ) {
curPage = curPage + 1
GetPage( curPage, collection, callback )
}
else {
console.log( 'passed' )
callback()
}
}
});
}
As you can see this function calls itself while there are some pages left to fetch, and if there's no page left, the callback function should be called to do some stuff. The callback example you can see below (just for testing):
function DrawItemSet() {
console.log( 'DrawItemSet called.' )
...
}
My problem is that in theory everything looks ok, and I should get in the console DrawItemSet as the last message meaning that the callback function was called after the fetching is being finished. Well.. instead of it I get the following:
DrawItemSet called.
Starting to get page number 1
Curpage=1 MaxPage=2
Starting to get page number 2
Curpage=2 MaxPage=2
passed
And this means that somehow the callback is the first function being executed.
How so?!
When you write DrawItemSet( items ) you are calling DrawItemSet immediately and then passing it's return value in as callback. That is why it is called first. You are calling it before GetPage is even called.
There are a few ways to pass it in as a callback with arguments. The most common approach is to pass in an anonymous function like so:
GetPage( curPage, items, function(){ DrawItemSet( items ) } );
In modern browsers you can also use bind:
GetPage( curPage, items, DrawItemSet.bind(null, items) );
Or you can use the jQuery version of bind called proxy:
GetPage( curPage, items, $.proxy(DrawItemSet, null, items) );
I have one JS function that makes an ajax call and runs continuously
function First(ServerId)
{
var CallServer = jQuery.ajax({
type: "POST",
url: "somefile.php",
dataType: "json",
success: function(response)
{
// do something here
First(response.ServerId);
}
}};
}
In somefile.php there's a sleep timer of 60 seconds, so ajax call returns response after 60 seconds. Every time a different server id is returned.
Now I have another function and I want to do something like this
function Second()
{
/*
wait for 5 seconds to see if function First() has returned server id
if (ServerIdIsReturned)
{
i) abort CallServer
ii) make another Ajax call (CallServer2)
iii) after CallServer2 is done, call CallServer with the returned ServerId
}
else
{
i) abort CallServer
ii) make another Ajax call (CallServer2)
iii) after CallServer2 is done, call CallServer with the ServerId as 0
}
*/
}
I am not sure if I have explained it properly, but I want to check in function Second() if function First() has returned a new server id and accordingly proceed further. I think I'd need to use setTimeout and breakup the Second() function, but not sure.
How can I accomplish this?
Just call the second function in the success block of your first function.
success: function(response) {
// do something here
First(response.ServerId);
// proceed further
Second();
}
in order to make a delayed call just use setTimeout(Second,5000);
set a global variable to false before the call and set it to true when the call has returned then you can just check that variable
Why not just use the built in timeout ability of the jQuery ajax call - set your 5 second timeout and then add an error handler - check for a timeout and call again, if appropriate.
var CallServer = jQuery.ajax({
type: "POST",
url: "somefile.php",
timeout:5000,
dataType: "json",
success: function(response)
{
// do something here
First(response.ServerId);
},
complete(jqXHR, textStatus)
{
if (textStatus === "timeout")
{
Second();
}
}
}};
I have this callback function setup:
var contextMenu = [];
var context = [ { "name": "name1", "url": "url1" }, {"name": name2", "url: "url2" } ];
for(var i=0; i < context.length; i++) {
var c = context[i];
var arr = {};
arr[c.name] = function() { callback(c.url); }
contextMenu.push( arr );
}
function callback(url) {
alert(url);
}
The problem is that the url value passed to the callback is always the last value in the context variable - in this case "url2". I am expecting to pass specific values to each "instance" of the callback, but as the callback seems to be remember the same value, the last time it was referred.
I am kind of stuck. Any help would be appreciated.
PS: I am using jQuery ContextMenu which, to my understanding, does not support sending custom data to its callback functions. It is in this context that I have this problem. Any suggestions to overcome in this environment is also helpful!
Use an additional closure.
arr[c.name] = (function(url) {
return function() { callback(url); }
})(c.url);
See Creating closures in loops: A common mistake and most other questions on this topic, and now your question is also added to this pool.
You are creating a series of closure functions inside the for loop
arr[c.name] = function() { callback(c.url); }
and they all share the same scope, and hence the same c object which will point to the last element in your array after the loop finishes.
To overcome this issue, try doing this:
arr[c.name] = function(url) {
return function() { callback(url); };
}(c.url);
Read more about closures here: http://jibbering.com/faq/notes/closures/
General solution
Callback creator helper
I created a general callback creator along the Creating closures in loops: A common mistake that Anurag pointed out in his answer.
Parameters of the callback creator
The function's first parameter is the callback.
Every other parameter will be passed to this callback as parameters.
Parameters of the passed callback
First part of the parameters come from the arguments you passed to the callback creator helper (after the first parameter as I described previously).
Second part comes from the arguments that will be directly passed to the callback by its caller.
Source code
//Creates an anonymus function that will call the first parameter of
//this callbackCreator function (the passed callback)
//whose arguments will be this callbackCreator function's remaining parameters
//followed by the arguments passed to the anonymus function
//(the returned callback).
function callbackCreator() {
var functionToCall = arguments[0];
var argumentsOfFunctionToCall = Array.prototype.slice.apply(arguments, [1]);
return function () {
var argumentsOfCallback = Array.prototype.slice.apply(arguments, [0]);
functionToCall.apply(this, argumentsOfFunctionToCall.concat(argumentsOfCallback));
}
}
Example usage
Here is a custom AJAX configuration object whose success callback uses my callback creator helper. With the response text the callback updates the first cell of a row in a DataTables table based on which row the action happened, and prints a message.
{
url: 'example.com/data/' + elementId + '/generate-id',
method: 'POST',
successHandler: callbackCreator(function (row, message, response) {//Callback parameters: Values we want to pass followed with the arguments passed through successHandler.
table.cell(row, 0).data(JSON.parse(response).text);
console.log(message);
},
$(this).parents('tr'),//Row value we want to pass for the callback.
actionName + ' was successful'//Message value we want to pass for the callback.
)
}
Or in your case:
arr[c.name] = callbackCreator(function(url) {
callback(url);
},
c.url
);
I have this ajax call to a doop.php.
function doop(){
var old = $(this).siblings('.old').html();
var new = $(this).siblings('.new').val();
$.ajax({
url: 'doop.php',
type: 'POST',
data: 'before=' + old + '&after=' + new,
success: function(resp) {
if(resp == 1) {
$(this).siblings('.old').html(new);
}
}
});
return false;
}
My problem is that the $(this).siblings('.old').html(new); line isn't doing what it's supposed to do.
thanks..
all helpful comments/answers are voted up.
Update: it appears that half of the problem was the scope (thanks for the answers that helped me clarify that), but the other half is that I'm trying to use ajax in a synchronous manner. I've created a new post
You should use the context setting as in http://api.jquery.com/jQuery.ajax/
function doop(){
var old = $(this).siblings('.old').html();
var newValue = $(this).siblings('.new').val();
$.ajax({
url: 'doop.php',
type: 'POST',
context: this,
data: 'before=' + old + '&after=' + newValue,
success: function(resp) {
if(resp == 1) {
$(this).siblings('.old').html(newValue);
}
}
});
return false;
}
"this" will be transfer to the success scope and will act as expected.
First of all new is a reserved word. You need to rename that variable.
To answer your question, Yes, you need to save this in a variable outside the success callback, and reference it inside your success handler code:
var that = this;
$.ajax({
// ...
success: function(resp) {
if(resp == 1) {
$(that).siblings('.old').html($new);
}
}
})
This is called a closure.
this is bound to the object to which the executing function was applied. That could be some AJAX response object, or the global object (window), or something else (depending on the implementation of $.ajax.
Do I need to capture $(this) into a variable before entering the $.ajax call, and then pass it as a parameter to the $.ajax call? or do I need to pass it to the anonymous success function? If that's going to solve the problem, where do I pass it to the $.ajax?
You do indeed need a way to capture the value of this before defining the success function. Creating a closure is the way to do this. You need to define a separate variable (e.g. self):
function doop() {
var old = $(this).siblings('.old').html();
var new = $(this).siblings('.new').val();
var self = this;
$.ajax({
url: 'doop.php',
type: 'POST',
data: 'before=' + old + '&after=' + new,
success: function(resp) {
if(resp == 1) {
$(self).siblings('.old').html(new);
}
}
});
return false;
}
The success function will retain the value of self when invoked, and should behave as you expected.