I'm trying to generate an array of callback functions for use in a jQuery UI dialog
Given the following code:
for(var x in methods)
{
buttons[x] = function() {
var method = methods[x];
var data = $('#dialog_'+model+' form').serialize();
data += '&form='+model;
$.post(
$('#dialog_'+model+' form').attr('action')+'method/'+method+'/',
data,
function(r) {
handleFormReturn(r);
},
'json'
);
};
}
When called, the function will obviously use the last known value of the variable x and not the one that I need. How can I avoid this problem without having to resort to using eval() ?
Maybe I'm going about this all wrong but as far as I know it's not possible to pass a parameter to the callback.
You need to create a new variable scope during each pass in the for loop. This can only be done by invoking a function.
function createButton(x) {
buttons[x] = function () {
var method = methods[x];
var data = $('#dialog_' + model + ' form').serialize();
data += '&form=' + model;
$.post(
$('#dialog_' + model + ' form').attr('action') + 'method/' + method + '/', data, function (r) {
handleFormReturn(r);
}, 'json');
};
}
for (var x in methods) {
createButton(x);
}
Now the value of x that the buttons[x] function refers to will be the one that was passed to createButton.
An immediate function version of patrick dw's solution:
for (var x in methods) {
buttons[x] = (function (x) {
return function () {
/* ... x is local for this function ... */
};
})(x);
}
You need to create a closure for each element in methods array:
for(var x in methods) {
buttons[x] = (function(x) {
var method = methods[x];
return function () {
var data = $('#dialog_'+model+' form').serialize();
data += '&form='+model;
$.post(
$('#dialog_'+model+' form').attr('action')+'method/'+method+'/',
data,
function(r) {
handleFormReturn(r);
},
'json'
);
};
})(x);
}
Related
I am coding a chat program but i am stuck in this part.
var Controller=function conversation() {
this.createMessageNode=function(msg,sender,time,mid){
var newMessage;
if(sender==sessionStorage.getItem('userid')){
newMessage="<div class='message-sent' id='"+mid+"'>"+msg+"<span class='time'>"+time+"</span></div>";
}else{
newMessage="<div class='message-recv' id='"+mid+"'>"+msg+"<span class='time'>"+time+"</span></div>";
}
sessionStorage.setItem('lastMessage',mid);
$('.chat-messages').append(newMessage);
}
this.getMessages=function(){
if(sessionStorage.getItem('lastMessage')==null){
sessionStorage.setItem('lastMessage',0);
}
$.ajax({url:"getmessages.php",type:"POST",data:{last:sessionStorage.getItem('lastMessage'),cid:sessionStorage.getItem('conversationid')},success:function(result) {
var messages=JSON.parse(result);
for (var i = 0; i < messages.length; i++) {
createMessageNode(messages[i].message,messages[i].sender,messages[i].time,messages[i].mid);
var cont=document.getElementById('chat-messages');
cont.scrollTop=cont.scrollHeight;
};
}});
}
}
now when i do this it shows an error
Uncaught ReferenceError: createMessageNode is not defined
now in the for loop "this" variable is referring to the ajax object. how can i call the createMessageNode function?
Your functions are bound to the this object. If it is a global object (top most parent scope) then you can reference the functions within this by this.yourfunction
You must study SCOPE properly to understand
http://www.w3schools.com/js/js_scope.asp
The issue is createMessageNode() is a method of the Controller object instance, so you need to refer to the instance when calling it. Without refering to the instance, the JavaScript engine is looking for the function in the current scope, then each higher scope all the way up to the global scope.
Typically you would use the this keyword to reference the instance, but in your case, the jQuery ajax call has changed the this context, so you can't directly use this.
A possible solution is, before the ajax call, store the this context:
var that = this;
Now, in the ajax success function:
that.createMessageNode(messages[i].message,messages[i].sender,messages[i].time,messages[i].mid);
^^ refer to the instance
It'd probably be better to write your code following better prototypical inheritance models, like so:
function Controller() {
this.chatMessages = $('.chat-messages');
}
Controller.prototype.createMessageNode = function (msg, sender, time, mid) {
var newMessage;
if (sender == sessionStorage.getItem('userid')) {
newMessage = "<div class='message-sent' id='" + mid + "'>" + msg + "<span class='time'>" + time + "</span></div>";
} else {
newMessage = "<div class='message-recv' id='" + mid + "'>" + msg + "<span class='time'>" + time + "</span></div>";
}
sessionStorage.setItem('lastMessage', mid);
this.chatMessages.append(newMessage);
};
Controller.prototype.getMessages = function () {
var _this = this;
if (sessionStorage.getItem('lastMessage') === null) {
sessionStorage.setItem('lastMessage', 0);
}
$.ajax({
url: "getmessages.php",
type: "POST",
data: {
last: sessionStorage.getItem('lastMessage'),
cid: sessionStorage.getItem('conversationid')
},
success: function (result) {
var messages = JSON.parse(result);
for (var i = 0; i < messages.length; i++) {
_this.createMessageNode(messages[i].message, messages[i].sender, messages[i].time, messages[i].mid);
}
var cont = $('#chat-messages');
cont.scrollTop(cont.scrollHeight);
}
});
};
This solves the issue of the context by creating a true class, for example:
var conversation = new Controller();
conversation.getMessages();
conversation.createMessageNode('Hello, World!', 'JimmyBoh', new Date(), 8268124);
Also, whenever you have nested functions, it can help to store the desired context in a local variable, such as _this or that, etc. Here is a more simple example:
function outer() {
// Temporarily store your desired context.
var _this = this;
// Make any call that executes with a different context.
$.ajax({
url: "getmessages.php",
type: "GET",
success: function inner(result) {
_this.doSomething(result);
}
});
};
Lastly, there might be a time when you want to execute a method in a different context than the current. .call() and .apply() can be used to run a method with a specified context and arguments. For example:
function printThis() {
console.log(this.toString());
console.dir(arguments);
}
printThis.call('Hello, World!');
printThis.call('Call array:', [2, 4, 6, 8], 10); // Keeps arguments as-is.
printThis.apply('Apply array:', [2, 4, 6, 8], 10); // Accepts an array of the arguments.
function Sum(startingValue) {
this.value = startingValue || 0;
}
Sum.prototype.add = function (number) {
this.value += number;
}
var realSum = new Sum(2);
var fakeSum = {
value: 3
};
realSum.add(1);
Sum.prototype.add.call(fakeSum, 2);
console.log(fakeSum.value); // Prints '5'
console.log(realSum.value); // Prints '3'
I am calling local (class) function via this pointer, but get an error 'Uncaught TypeError: undefined is not a function'. Probem occur on line
this.createtimetable(); at loadtimetable function.
My JS (relevant) is :
this.createtimetable = function () {
this.inside_timetable = [];
for (var d = new Date(in_week_start); d <= new Date(in_week_end); d.setDate(d.getDate() + 1)) {
console.log(new Date(d));
daysOfYear.push(new Date(d));
}
}
this.loadtimetable = function (in_guide_id, in_week_start, in_week_end) {
this.guide_id = in_guide_id;
this.week_start = in_week_start;
this.week_end = in_week_end;
$.post("./j.php", {
guide_id : in_guide_id,
week_start : in_week_start,
week_end : in_week_end
})
.done(function (data) {
var res_arr = jQuery.parseJSON(data);
if (res_arr.code == 0) {
this.excursions_base = res_arr.answer;
alertify.success("Data extracted");
this.createtimetable();
} else {
alertify.error("Some problem occured." + data);
}
}).fail(function () {
alertify.alert("Error. Please, refresh page, or try later. We are sorry. Write or call us with your question!");
});
}
Calling by name (i.e. createtimetable() ) also fail. Thank you for ideas!
Your code is executed in a callback, and this no longer points to your object. You should either use a closure, aliasing this to something like self, or explicitly bind this
this.createtimetable = function () {
this.inside_timetable = [];
for (var d = new Date(in_week_start); d <= new Date(in_week_end); d.setDate(d.getDate() + 1)) {
console.log(new Date(d));
daysOfYear.push(new Date(d));
}
}
this.loadtimetable = function (in_guide_id, in_week_start, in_week_end) {
this.guide_id = in_guide_id;
this.week_start = in_week_start;
this.week_end = in_week_end;
$.post("./j.php", {
guide_id: in_guide_id,
week_start: in_week_start,
week_end: in_week_end
})
.done(function (data) {
var res_arr = jQuery.parseJSON(data);
if (res_arr.code == 0) {
this.excursions_base = res_arr.answer;
alertify.success("Data extracted");
this.createtimetable();
} else {
alertify.error("Some problem occured." + data);
}
}.bind(this)).fail(function () {
alertify.alert("Error. Please, refresh page, or try later. We are sorry. Write or call us with your question!");
}.bind(this));
}
Store reference of $(this)outside of post function call ans use it in done callback function, here this doesn't refers to your object.
this.loadtimetable = function(in_guide_id, in_week_start, in_week_end)
{
var self = this; //store reference of this
$.post( "./j.php", {})
.done(function( data ) {
self.createtimetable(); //Here instead of this use your variable
});
}
EDIT
If you are open to use $.ajax() instead of $.post(). You can use the context option.
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). (...)
$.ajax({
context: this
});
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
This is a simplified code that runs on Node.js crawler and it gets all data.
But how do I insert inside the "callback": function value of var "i" from cycle for(var i=0... When I'm adding name: datas[i].name it returns an error:
TypeError: Cannot read property 'undefined' of undefined
var Crawler = require("crawler").Crawler;
var crawler = new Crawler;
var datas = [
{name: 'John', url: 'john025'},
{name: 'Jim', url: 'jim04567'}
];
function crauler(done) {
for (var i = 0; i < datas.length; i++) {
var link = 'http://somesite.com/' + datas[i].url;
crawler.queue([{
"uri": link,
// inside this func
"callback": function (error, result, $, datas, i) {
var arr = $('.blogpost').map(function (index) {
var str = $(this).attr('href');
var object = {
numb: str,
name: datas[i].name
};
return obj;
}).get().join(',');
done(arr);
} }]) }; };
crauler (function (arr) {
console.log(arr);
});
You can't pass datas and i into callback functions like this. What arguments that the callback functions will be called with are up to the caller, you don't have the control of it.
You're seeing "TypeError: Cannot read property 'undefined' of undefined" because you want your callback function to have datas and i as parameters; but the caller will call the callback with the first 3 arguments only [crawler callback reference], so the datas and i are undefined.
Therefore, you should remove the datas and i from in line:
"callback": function (error, result, $, datas, i) {
Because datas is defined in the outer scope of the callback function, the callback can access datas without any special treatment. For the variable i, it's a little bit tricky as mentioned in other answers, so you need to create a closure for it.
So, your callback function definition should be something looks like the following:
"callback": (function(i) { // create closure for i
return function (error, result, $) { // no more datas and i here
var arr = $('.blogpost').map(function (index) {
var str = $(this).attr('href');
var object = {
numb: str,
name: datas[i].name // access datas as it
};
return obj;
}).get().join(',');
done(arr);
}
})(i)
You're trying to create a closure around i inside of a loop which is causing you problems. This answer should help you:
JavaScript closure inside loops – simple practical example
You need a closure to capture the values, this is one way to solve the problem. Read up on closures.
Javascript
var Crawler = require("crawler").Crawler;
var crawler = new Crawler;
var datas = [{
name: 'John',
url: 'john025'
}, {
name: 'Jim',
url: 'jim04567'
}];
function queue(link, i) {
crawler.queue([{
"uri": link,
// inside this func
"callback": function (error, result, $, datas, i) {
var arr = $('.blogpost').map(function (index) {
var str = $(this).attr('href');
var object = {
numb: str,
name: datas[i].name
};
return obj;
}).get().join(',');
done(arr);
}
}]);
}
function crauler(done) {
for (var i = 0; i < datas.length; i++) {
var link = 'http://somesite.com/' + datas[i].url;
queue(link, i);
};
crauler(function (arr) {
console.log(arr);
});
Im looking through some code (unfortunatly the author isnt around anymore) and im wondering why he has used the .call method.
hmlPlaylist.prototype.loadVideos = function () {
var scope = this;
this.config.scriptUrl = '_HMLPlaylistAjax.aspx?' + Math.random();
jQuery.ajax({
type: 'GET',
url: this.config.scriptUrl,
success: function (d, t, x) {
scope.loadVideos_callback.call(scope, d);
},
error: function () {
}
});
};
hmlPlaylist.prototype.loadVideos_callback = function (data) {
var jsonData = '';
var jsonError = false;
try {
jsonData = eval("(" + data + ")");
} catch (jError) {
jsonError = true;
}
if (!jsonError) {
if (jsonData.playlists.length > 0) {
this.buildPlaylistList(jsonData.playlists);
}
if (jsonData.videos.length > 0) {
this.buildVideoList(jsonData.videos);
this.bindVideoNavs();
}
}
else {
// no json returned, don't do anything
}
};
Obviously he seems to have used it to pass a 'this' reference to the loadVideos_callback method but why? The 'loadVideos_callback' method is attached to the prototype of 'hmlplaylist' which is the 'class'. So if you access this inside the 'loadVideos_callback' method you get to the same thing dont you?
yes, I think you are right (I can't see the code in action). You still need the closure around scope, but in this case the use of call is not necessary.
To pull some of the comments into this answer, this is always the context on which the method was invoked. So if a new instance of htmlPlayList was created, and the method invoked on that instance, this would be a reference to that instance.
I have a json object retrieved from server in my $(document).ready(...); that has an string that I would like to resolve to a function also defined within $(document).ready(...); so, for example:
$(document).ready(function{
$.getJSON(/*blah*/,function(data){/*more blah*/});
function doAdd(left,right) {
return left+right;
}
function doSub(left,right) {
return left-right;
}
});
with json string:
{"doAdd":{"left":10,"right":20}}
One way I thought about was creating an associative array of the function before loading the json:
var assocArray=...;
assocArray['doAdd'] = doAdd;
assocArray['doSub'] = doSub;
Using eval or window[](); are no good as the function may not be called for some time, basically I want to link/resolve but not execute yet.
Change your JSON to
{method: "doAdd", parameters : {"left":10,"right":20}}
Then do
var method = eval(json.method);
// This doesn't call it. Just gets the pointer
Or (haven't tried this)
var method = this[json.method]
How about something like this?
$(function(){
// Function to be called at later date
var ressolvedFunc = null;
// Ajax call
$.getJSON(/*blah*/,function(data){
// Generate one function from another
ressolvedFunc = (function(data) {
var innerFunc;
var left = data.left;
var right = data.right;
// Detect action
for (action in data) {
if (action == "doAdd")
innerFunc = function() {
return left + right;
};
else
innerFunc = function() {
return left - right;
};
}
return innerFunc;
})(data);
});
});
The anonymous function returns fresh function, with the new values stored within the enclosure. This should allow you to call the function at later date with the data previously retrieved from the GET request.
Rich
try this:
var doX = (function() {
var
data = [],
getDo = function(action) {
for(var d in data) {
if (data[d][action]) {
return data[d];
}
}
return null;
};
return {
set: function(sdata) {
data.push(sdata);
},
doAdd: function() {
var add = getDo("doAdd");
if (!add)
return 0;
return add.doAdd.left + add.doAdd.right;
},
doSub: function() {
var sub = getDo("doSub");
if (!sub)
return 0;
return sub.doAdd.left + sub.doAdd.right;
}
};
})();
$(document).ready(function{
$.getJSON(/*blah*/,function(data){ doX.set(data); });
});