0

I've read a couple of other threads on this but can't quite seem to grasp it. I don't really know a lot about Javascript and have basically guessed my way through so far. I have one function run an AJAX request to get some rows from a database. Then for each row, I need to run a nested AJAX request and return it's values to the first function. Both AJAX requests work independently however I don't know how to nest them correctly. Here is what I have:

function updateSummaryVariablesInput(typeId) {
    if (typeId=='') {
        document.getElementById('summaryVariables').innerHTML='';
        return;
    }
    if (window.XMLHttpRequest) {
        // code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp2=new XMLHttpRequest();
    } else {
        // code for IE6, IE5
        xmlhttp2=new ActiveXObject('Microsoft.XMLHTTP');
    }
    xmlhttp2.onreadystatechange=function() {
        if (xmlhttp2.readyState==4 && xmlhttp2.status==200) {
            xmlDoc=xmlhttp2.responseXML;
            txt='<table>';
            x=xmlDoc.getElementsByTagName('row');
            for (i=0;i<x.length;i++) {
                if (x[i].getElementsByTagName('common')[0].childNodes[0].nodeValue < 1) {
                    txt=txt + '<tr><th style=\"width: 150px;\">' + x[i].getElementsByTagName('label')[0].childNodes[0].nodeValue +  '</th><td>';
                    // Select
                    if (x[i].getElementsByTagName('input')[0].childNodes[0].nodeValue == 'select') {
                        txt=txt + '<select name=\"' + x[i].getElementsByTagName('title')[0].childNodes[0].nodeValue + '\"><option></option>';
                        myoptions = getSummaryVariableOptions(x[i].getElementsByTagName('id')[0].childNodes[0].nodeValue);
                        //alert(myoptions.length);
                        for (j=0;j<myoptions.length;j++) {
                            txt=txt + '<option value=\"' + myoptions[j] + '\">' + myoptions[j] + '</option>';
                        }
                        txt=txt + '</select>';
                    }
                    // Text
                    if (x[i].getElementsByTagName('input')[0].childNodes[0].nodeValue == 'text') {
                        txt=txt + '<input type=\"text\" name=\"' + x[i].getElementsByTagName('title')[0].childNodes[0].nodeValue + '\" />';
                    }
                    txt=txt + '</td></tr>';
                }
            }
            txt=txt + '</table>';
            document.getElementById('summaryVariables').innerHTML=txt;
        }
    }
    xmlhttp2.open('GET','/cgi/new/Ajax/getOutageVariablesByTypeId.php?typeId='+typeId,true);
    xmlhttp2.send();
}

function getSummaryVariableOptions(variableId) {
    var options = new Array();
    if (window.XMLHttpRequest) {
        // code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp3=new XMLHttpRequest();
    } else {
        // code for IE6, IE5
        xmlhttp3=new ActiveXObject('Microsoft.XMLHTTP');
    }
    xmlhttp3.onreadystatechange=function() {
        if (xmlhttp3.readyState==4 && xmlhttp3.status==200) {
            xmlDoc=xmlhttp3.responseXML;
            x=xmlDoc.getElementsByTagName('row');
            for (i=0;i<x.length;i++) {
                options[i] = x[i].getElementsByTagName('description')[0].childNodes[0].nodeValue;
            }
            alert(options.length);
        }
    }
    xmlhttp3.open('GET','/cgi/new/Ajax/getOutageVariableOptionsByVariableId.php?variableId='+variableId,true);
    xmlhttp3.send();
    //alert(options.length);
    return options;
}

Run as is, I will get an alert with a valid number, e.g. 5. If I uncomment any of the other alert functions they will only output 0.

Matt
  • 251
  • 4
  • 13
  • native JavaScript code for handling AJAX makes my eyes bleed :( – Alnitak Nov 08 '13 at 01:09
  • Agreed! Is there some other way I could be doing this? It's a php generated html form, and I need certain parts of the form to change depending on other options selected in the form. – Matt Nov 08 '13 at 01:14
  • Most people commonly handle ajax using a framework, most popularly jQuery. See http://api.jquery.com/jQuery.ajax/ – Jamie Wong Nov 08 '13 at 01:19
  • 2
    As an added note, your code is leaking variables to the global namespace. Whenever you declare a variable without a `var`, it gets attached to the window. So you want `var txt = ...`, `var x = ...`, `var xmlDoc = ...`, etc. – Jamie Wong Nov 08 '13 at 01:20
  • Head over to the [JavaScript chatroom](http://chat.stackoverflow.com/rooms/17/javascript) if you need further assistance, where we can clarify what you mean by _nesting_. – Shea Nov 08 '13 at 02:04

3 Answers3

1

The issue is that you are trying to access the return value from the second ajax call as if it were synchronous and it isn't.

So when you call getSummaryVariableOptions inside the first ajax call, you'll need to do something like:

getSummaryVariableOptions(...., function (myoptions) {
  alert(myoptions.length); // now you have what you need here
  ..... /// put the reset of the code that depende on myoptions here
});

then add a parameter to your getSummaryVariableOptions function that receives a function that is to be called when the data arrives -- the callback.

function getSummaryVariableOptions(variableId, callback) {
    ... inside the readystate === 4 ...
    callback(options);
}

When you call getSummaryVariableOptions it starts the ajax call and returns immediately, so you need to wait until the call completes and then get called back with the data from the call. You can't do var myoptions = getSummaryVariableOptions(..)

EDIT:

A jquery based approach would look like:

$.getJSON(url1, function (res1) {
  $.getJSON(url2, function (res2) {
     // now you have both res 1 and res 2 to process here...
  });
});
Jaime
  • 6,736
  • 1
  • 26
  • 42
  • So define getSummaryVariableOptions within updateSummaryVariablesInput? As you can probably tell I don't really get this whole 'synchronous' business :p Just saw your edit, I'll try that and see how I go, thanks. – Matt Nov 08 '13 at 01:20
  • Sorry I'm still not really sure what to do. Can you dumb it down a bit more? Thanks! – Matt Nov 08 '13 at 01:29
  • Are you trying to learn how to use the XHR object or just make some ajax calls? If so, you may be better off using something like jQuery to hide some of the complexity. – Jaime Nov 08 '13 at 01:37
  • I saw Jamie Wong's comment above and will be looking into jQuery after this. For now though this is almost working. I did what you said and the alert(myoptions.length) now returns 5! For some reason nothing is being appended to txt though. I'll keep trying. – Matt Nov 08 '13 at 01:42
  • I have this and I get an alert for each option. But txt is not being updated. Is there a scope issue or something? for (j=0;j' + myoptions[j] + ''; } – Matt Nov 08 '13 at 01:48
1

Almost got there following answer from Jaime. Still had an issue where getSummaryVariableOptions was actually an asynchronous ajax call meaning updateSummaryVariablesInput finished long before the results of getSummaryVariableOptions are pulled in. This caused the tags to be appended to txt AFTER the closing tag. Shea made edits to store option tags in an array and then insert them into the later using innerHtml. Thank you Shea!

var getQueue = [];

function updateSummaryVariablesInput(typeId) {
    if (typeId=='') {
        document.getElementById('summaryVariables').innerHTML='';
        return;
    }
    if (window.XMLHttpRequest) {
        // code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp2=new XMLHttpRequest();
    } else {
        // code for IE6, IE5
        xmlhttp2=new ActiveXObject('Microsoft.XMLHTTP');
    }
    xmlhttp2.onreadystatechange=function() {
        if (xmlhttp2.readyState==4 && xmlhttp2.status==200) {
            xmlDoc=xmlhttp2.responseXML;
            txt='<table>';
            x=xmlDoc.getElementsByTagName('row');
            for (i=0;i<x.length;i++) {
                if (x[i].getElementsByTagName('common')[0].childNodes[0].nodeValue < 1) {
                    txt=txt + '<tr><th style=\"width: 150px;\">' + x[i].getElementsByTagName('label')[0].childNodes[0].nodeValue +  '</th><td>';
                    // Select
                    if (x[i].getElementsByTagName('input')[0].childNodes[0].nodeValue == 'select') {
                        txt=txt + '<select id=\"' + x[i].getElementsByTagName('id')[0].childNodes[0].nodeValue + '\" name=\"' + x[i].getElementsByTagName('title')[0].childNodes[0].nodeValue + '\"><option></option></select>';
                        // Add it to a queue, to do it later
                        getQueue.push(x[i].getElementsByTagName('id')[0].childNodes[0].nodeValue);
                    }
                    // Text
                    if (x[i].getElementsByTagName('input')[0].childNodes[0].nodeValue == 'text') {
                        txt=txt + '<input type=\"text\" name=\"' + x[i].getElementsByTagName('title')[0].childNodes[0].nodeValue + '\" />';
                    }
                    txt=txt + '</td></tr>';
                }
            }
            txt=txt + '</table>';
            document.getElementById('summaryVariables').innerHTML=txt;
            for (var gsi = 0; gsi < getQueue.length; ++gsi) {
                getSummaryVariableOptions(getQueue[gsi], function (myoptions, parentId) {
                    var parentSelect = document.getElementById(parentId),
                        optionsTxt = '';
                    for (j=0;j<myoptions.length;j++) {
                        optionsTxt=optionsTxt + '<option value=\"' + myoptions[j] + '\">' + myoptions[j] + '</option>';
                    }
                    parentSelect.innerHTML = optionsTxt;
                });
            }
        }
    }
    xmlhttp2.open('GET','/cgi/new/Ajax/getOutageVariablesByTypeId.php?typeId='+typeId,true);
    xmlhttp2.send();
}

function getSummaryVariableOptions(variableId, callback) {
    var options = new Array();
    if (window.XMLHttpRequest) {
        // code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp3=new XMLHttpRequest();
    } else {
        // code for IE6, IE5
        xmlhttp3=new ActiveXObject('Microsoft.XMLHTTP');
    }
    xmlhttp3.onreadystatechange=function() {
        if (xmlhttp3.readyState==4 && xmlhttp3.status==200) {
            xmlDoc=xmlhttp3.responseXML;
            x=xmlDoc.getElementsByTagName('row');
            for (i=0;i<x.length;i++) {
                options[i] = x[i].getElementsByTagName('description')[0].childNodes[0].nodeValue;
            }
            // Revised to send variableId to callback
            callback(options, variableId);
            //alert(options.length);
        }
    }
    xmlhttp3.open('GET','/cgi/new/Ajax/getOutageVariableOptionsByVariableId.php?variableId='+variableId,true);
    xmlhttp3.send();
    //alert(options.length);
    //return options;
}
Matt
  • 251
  • 4
  • 13
0

Nested ajax requests need an array to store each request and log errors oncomplete..

https://stackoverflow.com/a/18728553/2450730

a simple solution is doing sequential ajax script .. (this is using xhr2 & JSON) needs also somemore error checks.. but it gives you the idea how to acomplish that.

var mainListArray,
Length,
Current;

function ajax(a,b,c){c=new XMLHttpRequest;c.open('GET',a);c.onload=b;c.send()};

function loadmain(){
 ajax('mainList.php',seq)
}

function seq(){
 mainListArray=JSON.parse(this.response);
 Length=mainListArray.length;
 Current=0;
 next();
}

function next(){
 ajax('Detail.php?id='+mainListArray[Current].id,add);
 Current++;
}

function add(){
 // do something with detailed data
 if(Current<Length){
  next();
 }else{
  //everything loaded.
 }
}

window.onload=loadmain;

in this case you avoid some ajax errors as each request is executed after the previous one is finished.

ajax function explained

https://stackoverflow.com/a/18309057/2450730

and here is a graphical example how sequential functions work.

http://jsfiddle.net/4eujG/

Community
  • 1
  • 1
cocco
  • 16,442
  • 7
  • 62
  • 77