Async xhr and callback - javascript

I have a problem with waiting for DOM elems to exist.
First of all, I make an XHR to my backend and get some info from there:
$(document).ready(function() {
var searchParam, searchStr;
// some values to vars
loadTags(15,highlightAndSearchTags(searchParam,searchStr));
});
The functions are here:
function highlightAndSearchTags(searchParam, searchStr) {
if (searchParam == 'tags') {
var selectedTags = searchStr.split(',');
console.log($("#my_favorite_latin_words").children().length); // sometimes returns 0, sometimes returns number of <span> in the div (see loadTags())
for (var i = 0; i < selectedTags.length; i++) {
$("#" + selectedTags[i]).toggleClass("tag-selected");
}
}
}
function loadTags(showedTagsLength, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', apiUrl + "tags/", true);
xhr.withCredentials = true;
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status != 200) {
console.log(xhr.responseText);
}
else {
tagList = JSON.parse(xhr.responseText);
tagList = tagList.results;
for (var i = 0; i < showedTagsLength; i++) {
$("#my_favorite_latin_words").append("<span id=\'" + tagList[i].tag_pk + "\'>" + tagList[i].name + "</span>");
}
}
setTimeout(callback, 1); //found this trick somewhere on stackoverflow
}
};
xhr.send();
}
As you can see there is a callback which is executed after 1ms timeout (I found this trick somewhere on stack a while ago), but then another function does not see the appended elements from time to time.
I have also tried
callback.call()
with no luck so far.
Can anybody advise how to wait for the elements correctly in this case?

loadTags(15,function(searchParam,searchStr){highlightAndSearchTags(searchParam,searchStr)});
As multiple comments already mentioned, you have to wrap it into a function so that it isnt called when you call the loadTags function

You are not passing any callback function. You are immediately invoking the function and passing the returned value of highlightAndSearchTags function which is undefined.
An anonymous function can be created and passed as
loadTags(15,function(){
highlightAndSearchTags(searchParam,searchStr)
});

loadTags(15,highlightAndSearchTags(searchParam,searchStr));
This code will execute your function highlightAndSearchTags immediately and the result value will be sent instead of your callback, if you want to use it as a callback, you need to only pass the function name like:
loadTags(15, highlightAndSearchTags);
If you need to pass your searchParam and searchStr parameters, add them as parameters:
loadTags(15, highlightAndSearchTags, searchParam, searchStr);
When your tags are loaded, you can directly call your callback with the searchParam and searchStr parameters you added to your loadTags function:
function loadTags(showedTagsLength, callback, searchParam, searchStr) {
var xhr = new XMLHttpRequest();
xhr.open('GET', apiUrl + "tags/", true);
xhr.withCredentials = true;
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status != 200) {
console.log(xhr.responseText);
}
else {
tagList = JSON.parse(xhr.responseText);
tagList = tagList.results;
for (var i = 0; i < showedTagsLength; i++) {
$("#my_favorite_latin_words").append("<span id=\'" + tagList[i].tag_pk + "\'>" + tagList[i].name + "</span>");
}
}
callback(searchParam,searchStr);
}
};
xhr.send();
}
Another approach could also be to wrap your callback in an self-executing anonymous function. This will prevent the highlightAndSearchTags to be executed immediately so you can call it later when your tags are loaded:
loadTags(15, function() { highlightAndSearchTags(searchParam, searchStr); });

Related

An AJAX call with JavaScript

I'm trying to learn how to make an AJAX call using vanilla JavaScript in an effort to move away from JQuery for a little project that I'm working on but don't seem to be getting past xmlhttp.onreadystatechange. Can anyone point to what I'm doing wrong (the function getDVDsAndBluRays() is getting invoked on DOMContentLoaded)? Thanks!
function getDVDsAndBluRays() {
console.log("Getting logged");
var xmlhttp = new XMLHttpRequest();
var url = 'http://www.omdbapi.com/?t=metropolis&y=&plot=short&r=json';
xmlhttp.onreadystatechange = function() {
console.log("Not getting logged");
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
console.log('responseText:' + xmlhttp.responseText);
var myMovies = JSON.parse(xmlhttp.responseText);
myFunction(myMovies);
}
xmlhttp.open('GET', url, true);
xmlhttp.send();
};
}
function myFunction(myMovies) {
for (var i = 0; i < myMovies.length; i++) {
var title = myMovies[i].Title.toLowerCase().split(' ').join('+');
var year = myMovies[i].Year;
console.log(title + ", " + "year");
}
}
It should be like that, notice the location of open and send functions:
function getDVDsAndBluRays() {
console.log("Getting logged");
var xmlhttp = new XMLHttpRequest();
var url = 'http://www.omdbapi.com/?t=metropolis&y=&plot=short&r=json';
xmlhttp.open('GET', url, true);
xmlhttp.send();
xmlhttp.onreadystatechange = function() {
console.log("Not getting logged");
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
console.log('responseText:' + xmlhttp.responseText);
var myMovies = JSON.parse(xmlhttp.responseText);
myFunction(myMovies);
}
};
}
function myFunction(myMovies) {
for (var i = 0; i < myMovies.length; i++) {
var title = myMovies[i].Title.toLowerCase().split(' ').join('+');
var year = myMovies[i].Year;
console.log(title + ", " + "year");
}
}
onreadystatechange is executed after the call, you were actually "calling the service when it replies"
You have your .open() and .send() inside your onreadystatechange() handler. Put those outside of the onreadystatechange function and you should be good to go.
Onreadystatechange() is the event handler for when there is a change in state in the xmlhttp request, and will not get called until you open the request and send it.
Hope this helped!
You have put the calls to open and send inside the onreadystatechange event handler so they will never be called.
Move them outside it.

XMLHttpRequest in for loop

I am trying to make several server requests inside a for loop. I found this question and implemented the suggested solution. However it doesn't seem to work.
for (var i = 1; i <= 10; i++)
{
(function(i) {
if(<some conditions>)
{
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp[i]=new XMLHttpRequest();
} else { // code for IE6, IE5
xmlhttp[i]=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp[i].onreadystatechange=function() {
if (xmlhttp[i].readyState==4 && xmlhttp[i].status==200) {
document.getElementById("preselection").innerHTML=xmlhttp[i].responseText;
}
}
xmlhttp[i].open("GET","getBuoys.php?q="+i,true);
xmlhttp[i].send();
}
})(i);
}
If I remove the for loop and change all xmlhttp[i] to xmlhttp, everything works just fine for one element, but I can't make several requests. Thanks in advance for any suggestions.
Try the snippet below
// JavaScript
window.onload = function(){
var f = (function(){
var xhr = [], i;
for(i = 0; i < 3; i++){ //for loop
(function(i){
xhr[i] = new XMLHttpRequest();
url = "closure.php?data=" + i;
xhr[i].open("GET", url, true);
xhr[i].onreadystatechange = function(){
if (xhr[i].readyState === 4 && xhr[i].status === 200){
console.log('Response from request ' + i + ' [ ' + xhr[i].responseText + ']');
}
};
xhr[i].send();
})(i);
}
})();
};
// PHP [closure.php]
echo "Hello Kitty -> " . $_GET["data"];
Response
Response from request 0 [ Hello Kitty -> 0]
Response from request 1 [ Hello Kitty -> 1]
Response from request 2 [ Hello Kitty -> 2]
First thing first, that's awful formatting. A small request to keep it a bit more parseable in future please.
We can clean this up though.
var XMLHttpRequest
= XMLHttpRequest || require('xmlhttprequest').XMLHttpRequest;
// Makes a request for 4 buoy page responses.
requestAllBuoys(4, function(requests) {
console.log('Got results!');
// Take out the responses, they are collected in the order they were
// requested.
responses = requests.map(function(request) {
return request.responseText;
});
// Left to you to implement- I don't know what you're going to do with
// your page!
updateDom(responses);
});
// Makes request to all buoy url's, calling the given callback once
// all have completed with an array of xmlRequests.
function requestAllBuoys (n, cb) {
var latch = makeLatch(n, cb);
makeBuoyURLTo(n).map(function (url, i) {
startXMLRequest('GET', url, latch.bind(undefined, i));
});
}
// Generates a latch function, that will execute the given callback
// only once the function it returns has been called n times.
function makeLatch (n, cb) {
var remaining = n,
results = [],
countDown;
countDown = function (i, result) {
results[i] = result;
if (--remaining == 0 && typeof cb == 'function') {
cb(results);
}
}
return countDown;
}
// Generates an array of buoy URL's from 1 to n.
function makeBuoyURLTo (n) {
var i, buoyUrls = [];
for (i = 1; i <= n; i++) {
buoyUrls.push('getBuoys.php?q=' + i);
}
return buoyUrls;
}
// Create and initiate an XMLRequest, with the given method to the given url.
// The optional callback will be called on successful completion.
function startXMLRequest (method, url, cb) {
var xmlRequest = createXMLRequest();
xmlRequest.onreadystatechange = function () {
if (isXMLFinished(xmlRequest)) {
if (cb && typeof cb == 'function') {
cb(xmlRequest, method, url);
}
}
}
xmlRequest.open(method, url, true);
xmlRequest.send();
return xmlRequest;
}
// Initiates an XMLRequest from either HTML5 native, or MS ActiveX depending
// on what is available.
function createXMLRequest () {
var xmlRequest;
if (XMLHttpRequest) {
xmlRequest = new XMLHttpRequest();
} else {
xmlRequest = new ActiveXObject('Microsoft.XMLHTTP');
}
return xmlRequest;
}
// Verifies that XMLRequest has finished, with a status 200 (OK).
function isXMLFinished (xmlRequest) {
return (xmlRequest.readyState == 4) && (xmlRequest.status == 200);
}
This may seem longer, but it makes things infinitely clearer, and the time you spent making it so is time you don't spend debugging.
It also allows you to access the final result together, in the order that they came as a standard array. This is the main added bulk.
I would say you have a good idea of what you're actually doing here, as to me the only thing about your code that wouldn't work is the updating of the dom (surely you'll just be assigning them rapidly all into the same element? replacing each other each time...).
Have a look at this answer about handling async callbacks if you're still struggling. But please, for your own sake, keep your code cleaner.

Can I use a function as parameter?

I have an AJAX function inside a javascript file and I want it to be abstract and not to use any global variables.
The AJAX inserts an event into a database:
function insertCalendarEvents(calendar_group, event_name, event_datestart, event_datestop, event_timestart, event_timestop, event_info, onFinish) {
var request;
if(window.XMLHttpRequest)
request = new XMLHttpRequest();
else
request = new ActiveXObject("Microsoft.XMLHTTP");
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
if(request.responseText.substr(0, 6) == "error ")
alert(errorName[request.responseText.substr(6)]);
else {
var event_id = request.responseText;
//call onFinish function with parameter event_id which database assigned it
}
}
}
request.open("GET", "php/calendar.php?action=insertCalendarEvents&calendar_group=" + calendar_group + "&event_name=" + event_name + "&event_datestart=" + event_datestart + "&event_datestop=" + event_datestop + "&event_timestart=" + event_timestart + "&event_timestop=" + event_timestop + "&event_info=" + event_info, true);
request.send();
}
The function is asynchronous so what I want to do is, at the moment it finished inserting the data into the database, I want it to execute a function which I will give it as a parameter.
If there is a better idea on how to accomplish this, I would be really happy to hear it :)
Thank you in advance, Daniel!
Functions are just objects you can pass. The only extra thing is that you can call them using () (additionally with arguments, of course).
You can pass them around just fine; all information such as scoping will be preserved. So, this should work:
onFinish(event_id);
Basically, a more trivial example would be this:
function func() {
alert("func is being run");
}
func(); // works
function callFunction(f) {
f();
}
callFunction(func); // pass func; works
callFunction(function() { // pass a function on the fly; works
alert("function passed directly"); // (nothing special in fact)
});

why i can't parse xml in javascript?

hello i have problem to parse xml..
i have xml like this :
<tejemahan>
<kategori> komputer </kategori>
<hasil> aplikasi komputer </hasil>
</terjemahan>
Edited:
xml above I get in that way :
var url="http://localhost:8080/inlinetrans/api/translate/"+userSelection+"/"+hasilStemSel+"/"+hasilStem;
var client = new XMLHttpRequest();
client.open("GET", url, false);
client.setRequestHeader("Content-Type", "text/plain");
client.send(null);
if(client.status == 200)
alert("the request success"+client.responseText);
else
alert("the request isn't success"+client.status+""+client.statusText)
}
and this is my code to parse an xml file above :
this.loadXML = function (){
var url = http://localhost:8080/coba/api/artikan/"+sel+"/"+hasilStemSel+"/"+hasilStem
xmlDoc=document.implementation.createDocument("","",null);
xmlDoc.load("url");
xmlDoc.onload= this.readXML;
}
this.readXML = function() {
alert(xmlDoc.documentElement.tagName);
alert(xmlDoc.documentElement.childNodes[0].tagName);
alert(xmlDoc.documentElement.childNodes[1].tagName);
alert(xmlDoc.documentElement.childNodes[0].textContent);
alert(xmlDoc.documentElement.childNodes[1].textContent);
}
i can execute this code
xmlDoc=document.implementation.createDocument("","",null);
xmlDoc.load("url");
but why i can't execute this code
xmlDoc.load = this.readXML ???
Try putting the onload handler assignment before the load() call. If you call load() first, the onload event will happen before you have assigned a handler to handle it. Like this:
xmlDoc=document.implementation.createDocument("","",null);
xmlDoc.onload= this.readXML;
xmlDoc.load("url");
Firstly, I second David Dorward's suggestion: use XMLHttpRequest instead, which will work in all major browsers. Code is below.
Secondly, your readXML function is flawed, since most browsers will include whitespace text nodes within the childNodes collection, so xmlDoc.documentElement.childNodes[0] will actually be a text node and have no tagName property. I would suggest using getElementsByTagName() or checking the nodeType property of each node as you iterate over childNodes.
Thirdly, your XML is not valid: the <tejemahan> and </terjemahan> do not match, although this may be a typo in your question.
var url = "http://localhost:8080/coba/api/artikan/"+sel+"/"+hasilStemSel+"/"+hasilStem;
var readXML = function(xmlDoc) {
alert(xmlDoc.documentElement.tagName);
var kategori = xmlDoc.getElementsByTagName("kategori")[0];
alert(kategori.tagName);
};
var createXmlHttpRequest = (function() {
var factories = [
function() { return new XMLHttpRequest(); },
function() { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); },
function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); },
function() { return new ActiveXObject("Microsoft.XMLHTTP"); }
];
for (var i = 0, len = factories.length; i < len; ++i) {
try {
if ( factories[i]() ) {
return factories[i];
}
}
catch (e) {}
}
})();
var xmlHttp = createXmlHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
readXML(xmlHttp.responseXML);
}
};
xmlHttp.open("GET", url, true);
xmlHttp.send(null);

How to access method variables from within an anonymous function in JavaScript?

I'm writing a small ajax class for personal use. In the class, I have a "post" method for sending post requests. The post method has a callback parameter. In the onreadystatechange propperty, I need to call the callback method.
Something like this:
this.requestObject.onreadystatechange = function() {
callback(this.responseText);
}
However, I can't access the callback variable from within the anonomous function. How can I bring the callback variable into the scope of the onreadystatechange anonomous function?
edit:
Here's the full code so far:
function request()
{
this.initialize = function(errorHandeler)
{
try {
try {
this.requestObject = new XDomainRequest();
} catch(e) {
try {
this.requestObject = new XMLHttpRequest();
} catch (e) {
try {
this.requestObject = new ActiveXObject("Msxml2.XMLHTTP"); //newer versions of IE5+
} catch (e) {
this.requestObject = new ActiveXObject("Microsoft.XMLHTTP"); //older versions of IE5+
}
}
}
} catch(e) {
errorHandeler();
}
}
this.post = function(url,data,callback)
{
var response;var escapedData = "";
if (typeof data == 'object') {
for (i in data) {
escapedData += escape(i)+'='+escape(data[i])+'&';
}
escapedData = escapedData.substr(0,escapedData.length-1);
} else {
escapedData = escape(data);
}
this.requestObject.open('post',url,true);
this.requestObject.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
this.requestObject.setRequestHeader("Content-length", data.length);
this.requestObject.setRequestHeader("Connection", "close");
this.requestObject.onreadystatechange = function()
{
if (this.readyState == 4) {
// call callback function
}
}
this.requestObject.send(data);
}
}
Just pass the callback function together with the rest of the arguments
this.post = function(url, data, callback) {
...
this.requestObject.onreadystatechange = function() {
if (this.readyState == 4) {
callback(this.responseText);
}
};
...
}
And then
foo.post("foo.html", {foo:"bar"}, function(result){
alert(result);
});
By the way, this is a better way to convert the data into a proper string
var q = [];
for (var key in data) {
if (data.hasOwnProperty(key)) {
q.push(key + "=" + encodeURIComponent(data[key]));
}
}
data = q.join("&"); //data can now be passed to .send()
encodeURIComponent is the proper function to use here as encode will not escape data properly
If you want to get a ready made function for all of this you can take a look here http://github.com/oyvindkinsey/easyXDM/blob/master/src/easyXDM.js#L358
var that = this;
Then use that instead of this inside the anonymous function.
If callback is a variable in the containing function, it should be in scope. If it is not a variable, but is in scope in in the containing function, you may have to do something like
var cb = callback;
var xhrRequest = this;
then
cb(xhrRequest.responseText);

Categories