jquery this pointer scope when call a function in ajax - javascript

I want to call a external function from $.ajax when getting through a xml file, but I am confused by the scope of this pointer in this case.
so in my ajax function
function getCustomerInfo (customerID) {
$.ajax ({
type: "GET",
url: "./content/customer_list.xml",
dataType:"xml",
success: function (xml) {
$(xml).find("customer[value=" + customerID + "]").each(function(){
//I want to create a function and call here to achieve the following commented code
//the commented code works fine. I just want to change it to a function because
//otherwise I have to hard code many similar lines...
// so here is the current function I call:
addCustomerDetails("timeAdded", "time_added");
// the following code works fine:
// var timeAdded = $(this).children('time_added').text();
// var lastUpdate = $(this).children('last_update').text();
// $("#time_added").html("<p>" + timeAdded + "</p>");
// $("#last_update").html("<p>" + lastUpdate + "</p>");
});
}
});
}
So the current addCustomerDetails function:
function addCustomerDetails (varName, tagName) {
window[varName] = $(this).children("time_added");
$("#"+tagName).html("<p>"+window[varName]+"</p>");
}
So I need a variable name as the argument, so I used window[varName]. Maybe this is also a problem, but I think the $(this) in addCustomerDetails() also doesn't seem work.
I hope I have explained it clearly. Please post any questions if this isn't clear enough and realllly appreciate your help!!

function addCustomerDetails (tagName) {
var tag = $(this).children(tagName).text();
$("#" + tagName).html("<p>" + tag + "</p>");
}
and call it like this:
addCustomerDetails.call(this, "time_added");
addCustomerDetails.call(this, "last_update");
Or following this path you can invent something even more convenient to use:
$(xml).find("customer[value=" + customerID + "]").each(appendTag('time_added', 'last_update'));
where appendTag will look like:
function appendTag() {
var tags = [].slice.call(arguments);
return function() {
for (var i = 0; i < tags.length; i++) {
var tag = $(this).children(tags[i]).text();
$("#" + tags[i]).html("<p>" + tag + "</p>");
}
};
}

When you call getCustomerInfo, assuming the caller has access to addCustomerDetails, you can save a reference to it before you make your ajax call and then use that reference to call addCustomerDetails or pass it as the caller if assigned 'self' doesn't have access, like:
var self = this;
$.ajax(
....
self.addCustomerDetails(...);
);
If addCustomerDetails isn't in the context of self, the other option is:
addCustomerDetails.apply(self, ...)
For differences between call and apply, you can check this SO thread:
What is the difference between call and apply?

Related

call a javascript function from within another function

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'

jQuery.find() doesn't work inside html variable

I have a very simple problem and keep finding answers to similar questions with more complexity. I am trying to replace image links in loaded html and decided that the best is to read the html into a string variable loadedHTML using .get(), like this:
$.get(loadURL, function(loadedHTML) {
myFunction(loadedHTML);
}, 'html');
In myFunction, I want to make some changes to the loaded html and eventually return it. I can't get .find() to work. Here is what that code looks like:
function myFunction( html ) {
var $html = $("<div>" + html + "</div>");
console.log( "$html.html() = " + $html.html()); // works!
$html.find("img", function() {
console.log("found an image"); // doesn't work :(
});
}
I am killing myself with something that is probably really simply. Let me know how I am dumb please...
I'm almost sure that you cannot use find in the way that you have.
Try something like:
var $foundImages = $html.find("img");
console.log($foundImages.length);
Which would, in theory, output the number of images that were found.
The find method doesn't have a second parameter:
http://api.jquery.com/find/
You should try this:
function myFunction( html ) {
var $html = $("<div>" + html + "</div>");
console.log( "$html.html() = " + $html.html()); // works!
console.log($html.find("img"));
}
Simply assign id to your div tag .
like below,
var $html = $("<div id='placeholder'>" + html + "</div>");
and find img with it like below,
$("#placeholder").find("img", function() {
console.log("found an image"); // doesn't work :(
});
your resultant code,
function myFunction( html ) {
var $html = $("<div id='placeholder'>" + html + "</div>");
console.log( "$html.html() = " + $html.html()); // works!
$("#placeholder").find("img", function() {
console.log("found an image"); // doesn't work :(
});
}
.find() didn't have callback function in jquery. it have parameter for selectors,elements,jqueryObject only.you have to check with length or condition like this
if($html.find("img").length > 0){
// do stuff here
}
or
if($html.has("img")){
// do stuff here
}
You can use this .filter():
var found = $html.find("img").filter(function() {
return this;
});
console.log(found);
or make an array out of it with .map():
var found = $html.find("img").map(function() {
return this;
}).get(); // use get() method here to get the values in array
console.log(found.length); // would give you the length of array created.
jQuery.find() doesn't have a callback but you can extend jQuery to do what you want:
jQuery.fn.extend({
findEach: function (selector, callback) {
var found = this.find(selector);
if (typeof callback == 'function' && found.length > 0) {
found.each(callback);
}
return found;
}
});
Then use like you expect:
$html.findEach("img", function(key, value) {//will run for each image
console.log(key);
console.log(value);
console.log(this);
});

javascript api simplified

Was using fourquare api to get venue, had previously had a clickable list written out from api but cut it down to just one venue name written to screen. Then decided it'd be best to just send it over to php. So when I did what I thought was logical cutting of the code, it stopped working completely.
My program has this, working:
$(document).ready(function doEverything(element) {
$.getJSON("https://api.foursquare.com/v2/venues/search?ll=" + lat + "," + lng + "&client_id=L2VWBKPOW45D5X3FJ3P4MJB5TGVJ4ST2J005RIVAFIWG44ND%20&client_secret=ZKDAOLHASCA31VUOGMBTAS3RFYUOMXL4IFFYPRURIDQA3QMA%20&v=20111107", function(data) {
one = data.response.venues[0].name;
var list = [];
list[0] = [one];
function Make() {
for (var i = 0; i < 1; i++) {
var div = document.createElement("div");
div.style.margin = "-435px 100px 0px 110px";
div.innerHTML = list[i];
!
function() {
var index = 0;
div.onclick = function() {
doSomething(this);
};
}();
document.body.appendChild(div);
}
}
function doSomething(element) {
var value = element.innerHTML;
switch (value) {
case one:
break;
}
}
Make();
});
});
Then I decided I wanted to pass a variable over to php using this:
theVar = 10; //just to make things simple.
urlString = "cookiestesttwo.php?var=" +theVar;
window.location = urlString;
So I tried to simplify my api code to this, and it stopped working:
$(document).ready() {
$.getJSON("https://api.foursquare.com/v2/venues/search?ll=" + lat + "," + lng + "&client_id=L2VWBKPOW45D5X3FJ3P4MJB5TGVJ4ST2J005RIVAFIWG44ND%20&client_secret=ZKDAOLHASCA31VUOGMBTAS3RFYUOMXL4IFFYPRURIDQA3QMA%20&v=20111107", function(data) {
one = data.response.venues[0].name;
document.write(one)
theVar = one
urlString = "cookiestesttwo.php?var=" + theVar;
window.location = urlString;)
};
};​
$(document).ready() { is not proper syntax and does throw errors.
Furthermore there was another syntax error at the end of the function. you reversed } and )
$(document).ready(function() {
$.getJSON("https://api.foursquare.com/v2/venues/search?ll=" + lat + "," + lng + "&client_id=L2VWBKPOW45D5X3FJ3P4MJB5TGVJ4ST2J005RIVAFIWG44ND%20&client_secret=ZKDAOLHASCA31VUOGMBTAS3RFYUOMXL4IFFYPRURIDQA3QMA%20&v=20111107", function(data) {
one = data.response.venues[0].name; // if one is local to this function then use VAR otherwise you'll leak it globally.
document.write(one);
theVar = one; // same here for the VAR keyword.
urlString = "cookiestesttwo.php?var=" + theVar; // and here as well. NO GLOBAL LEAKS!
window.location = urlString;
});
});
I threw a few more hints in the comments.
Your problem might be that you use document.write() when the DOM is already complete. You are not supposed to do that. Create an element document.createElement( "div" ) and set the innerText() and then append it to the dom. Much like you did before the refactor.
EDIT
I understand that it wasn't the document.write() but just do clarify what I was talking about I wrote a little refactor. I also threw out the theVar = one since that is redundant. Also make sure to declare your variables in the right scope. Therefore I added a var in front of the one.
$(document).ready(function() {
$.getJSON("https://api.foursquare.com/v2/venues/search?ll="+lat+","+lng+"&client_id=L2VWBKPOW45D5X3FJ3P4MJB5TGVJ4ST2J005RIVAFIWG44ND%20&client_secret=ZKDAOLHASCA31VUOGMBTAS3RFYUOMXL4IFFYPRURIDQA3QMA%20&v=20111107",
function(data){
var one = data.response.venues[0].name;
var div = document.createElement( "div" );
div.innerText( one );
document.appendChild( div );
window.location = "cookiestesttwo.php?var=" + one;
});
});
But if you change the location of the window. There is no point to document.write() or appending a new div since you leave the site anyways.

how to access object's member through a method which filled by another method?

I know the title is a little bit confusion, here is the details:
Say I have a custom object defined in javascript, and there is a public member defined in it:
function Test()
{
this.testArray = [];
}
And I have two methods for this object, one is read out some xml file and filled into the array:
Test.prototype.readXML = function()
{
var self = this;
$.get('assest/xml/mydata.xml', function(d){
$(d).find("item").each(function(){
var item = new Item;
item.ID = ($(this).attr("ID"));
item.body = ($(this).find("body").text());
});
self.testArray.push(item);
});
}
And another function, which will display the content into the HTML page.
Test.prototype.appendInfo = function()
{
var i;
for (i=0; i<testArray.length;i++)
{
$('#testdisplay').append(testArray[i].ID +"<br />");
$('#testdisplay').append(testArray[i].body = "<br /");
}
}
However, the display function continue gives me error that the testArray is not defined. I'm not sure where is the problem, since I put the display function behind the reading function. I expect that the data will be stored in the array and could be accessed anytime I need them.
Hope some one will kindly help me about this! Thank you!
}
}
So I notice two problems with your code.
First when you do your ajax call you need to pass a deferred back to the user. Since ajax calls are async it may not finish right away.
So your readXML function should do this. It should return the jquery get.
Test.prototype.readXML = function() {
var self = this;
return $.get('assest/xml/mydata.xml', function(d){
$(d).find("item").each(function(){
var item = new Item;
item.ID = ($(this).attr("ID"));
item.body = ($(this).find("body").text());
});
self.testArray.push(item);
});
}
Next you your second function append was just missing some context.
Test.prototype.appendInfo = function() {
var i;
for (i=0; i<this.testArray.length;i++) {
$('#testdisplay').append(this.testArray[i].ID +"<br />");
$('#testdisplay').append(this.testArray[i].body = "<br /");
}
}
So your code should look like this.
var mytest = new Test();
mytest.readXML().done(function(){
mytest.appendInfo();
}).fail(function(){
// put some fallback code here
});
Updated:
Added additional this's.
There is no testArray in your appendInfo() function, that's why it says it's not defined. You should use this.testArray instead.
Every time you want to use a variable declared inside your scope, but outside the function you are using, you must use this.yourVariable

Why has the author of this code used the .call method? Surely he could have just accessed the prototype?

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.

Categories