i have javascript code that does these things in a loop
create a div element,append it to the dom and get its reference
pass this reference to a function that makes an ajax post request
set the response of the ajax request to the innerHTML of the passed element reference
here is the code
window.onload = function () {
var categories = document.getElementById('categories').children;
for (i = 0; i < categories.length; i++) {
var link = categories[i].children[1].children[0].attributes['href'].nodeValue;
var div = document.createElement('div');
div.className = "books";
div.style.display = "none";
categories[i].appendChild(div);
getLinks(link, div);
}
}
function getLinks(url, div) {
xhr = new XMLHttpRequest();
xhr.open('POST', 'ebook_catg.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
url = encodeURIComponent(url)
var post = "url=" + url;
xhr.node=div; //in response to Marc B's suggestion
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
xhr.node.innerHTML = xhr.responseText;
xhr.node.style.display = "block";
}
}
xhr.send(post);
}
now when i check this in firebug i can see that the div element is created and appended to the categories element and its display is set to hidden. also the ajax post requests are being sent and the response is being received as expected. But the innerHTML property of div is not set and neither its display is set to block.
This means that the function getLinks loses the div reference.
when i type console.log(div) in the firefox console it says ReferenceError: div is not defined.
can somebody explain whats going on here?
in response to Franks's comment i changed readystate to readyState and i am able to attach the response of the last ajax request to the dom. so that makes it obvious that the div reference is being lost.
Thats because you are using a public (global) variable div that keeps getting overwritten.
Try this in your for loop:
for (i = 0; i < categories.length; i++) {
var link = categories[i].children[1].children[0].attributes['href'].nodeValue;
var div = document.createElement('div'); //USE var!
div.className = "books";
div.style.display = "none";
categories[i].appendChild(div);
getLinks(link, div);
}
Remember that the response handlers innards aren't "fixated" when the callback is defined, so the 'current' value of the div var doesn't get embedded into the function's definition. It'll only be resolved when the function actually executes, by which time it might have been set to some completely other div, or been reset to null as the parent function's scope has been destroyed.
You could store the div value as a data attribute on the xhr object, which you can then retrieve from within the callback:
xhr.data('thediv', div);
xhr.onreadystatechange = function () {
if (xhr.readystate == 4) {
div = xhr.data('thediv');
etc....
Ok, you've got a few globals going on that you don't want. Rule of thumb: unless you need to access a variable outside of a function, place var in front of it. Otherwise you'll have data clobbering itself all over the place:
// changed the name to `d` because div seems to already be a global var.
function getLinks(url, d) {
// make xhr a local variable so it won't get re-written.
var request = new XMLHttpRequest();
request.open('POST', 'ebook_catg.php', true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
url = encodeURIComponent(url)
var post = "url=" + url;
request.onreadystatechange = function () {
// when the request was global, this would be false until the last
// request completed
if (request.readyState == 4) {
// since d only exists as a parameter to getLinks, this should
// already be bound when the onreadystatechange is created.
d.innerHTML = request.responseText;
d.style.display = "block";
}
}
request.send(post);
}
So, why did I just do such strange, strange things? Well, it looks like div was being assigned as a global variable and while JS should always look to function parameter name for binding, we want to eliminate all possible problems. So I changed the name of that variable. Then I set xhr to reflect a local variable with the var keyword. I also changed the name to request. Once again, it shouldn't matter -- var means that the variable will be bound to that scope, but the change is harmless and since I don't know what else you have, I decided to remove ambiguities. If it does not help JS, it will at least help the reader.
NOTE:
The important part of the above answer is var in front of request.
here i am answering my question.The following code works,i mean the response from each post is appended to the corresponding div element.
var xhr=new Array();
window.onload=function() {
var categories=document.getElementById('categories').children;
for(i=0;i<categories.length;i++)
{
var link=categories[i].children[1].children[0].attributes['href'].nodeValue;
var div=document.createElement('div');
div.className="books";
div.style.display="none";
categories[i].appendChild(div);
getLinks(link,div,i);
}
}
function getLinks(url,div,i)
{
xhr[i]=new XMLHttpRequest();
xhr[i].open('POST','ebook_catg.php',true);
xhr[i].setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
url=encodeURIComponent(url)
var post="url="+url;
xhr[i].node=div;
xhr[i].onreadystatechange=function() {
if(xhr[i].readyState==4)
{
xhr[i].node.innerHTML=xhr[i].responseText;
xhr[i].node.style.display="block";
}
}
xhr[i].send(post);
}
i am not marking it as accepted because i still dont understand why i need to use an array of xhr since a local xhr object should be enough because each time the onreadystate function executes it has the reference of the xhr object. Now since javascript functions are also objects therefore every instance of onreadystate function should have its own reference of xhr object and therefore i shouldnt need to create an array of xhrs.
please correct me if i am wrong here
Related
Im making a website for this spa bussiness. It has four horizontal sections with several buttons each, every button should display its own "treatment information" in the div "#container_xhr_info".
To avoid stuffing the HTML I put all the treatment divs in their own html file in a folder, so when making click on a button the XHR request should fetch the button's file and display it inside the container.
Ive added a Click Event to the buttons with a for loop, the event fires the xhr. I also saved the file URLs in an array to put them inside the open() also with a for loop. So far the code works but it displays the 4th URL in the array on every button (the "peeling" one). I havent found a solution to this specific case.
let array_asyncs_facial_location = ["Asyncs/facial/radio.html",
'Asyncs/facial/diamante.html',
'Asyncs/facial/limpieza.html',
'Asyncs/facial/peeling.html',
'Asyncs/facial/acne.html',
'Asyncs/facial/rosacea.html',
'Asyncs/facial/pustulas.html'];
let tratamientos_facial = document.getElementsByClassName('tratamientos_facial');
for(var i = 0; i < tratamientos_facial.length; i++){
tratamientos_facial[i].addEventListener('click', ()=>{
let xhr = new XMLHttpRequest;
let url = array_asyncs_facial_location[i];
xhr.open('GET', url );
xhr.addEventListener('load', ()=>{
if (xhr.status == 200){
let plantilla = xhr.response;
let container_xhr_info = document.querySelector('#container_xhr_info');
container_xhr_info.innerHTML = plantilla;
}
})
xhr.send();
})
}
The variable i that you're relying on to get your URL isn't resolved until the click function fires, at which point it's not the same value as when you set up the listener. There's a better way. Pass through the event argument in your listener and set the url dynamically using element.setAttribute(), then get the value in element.dataset. Setting an attribute like data-url allows us to get it simply with element.dataset.url
let array_asyncs_facial_location = ["Asyncs/facial/radio.html",
'Asyncs/facial/diamante.html',
'Asyncs/facial/limpieza.html',
'Asyncs/facial/peeling.html',
'Asyncs/facial/acne.html',
'Asyncs/facial/rosacea.html',
'Asyncs/facial/pustulas.html'
];
let tratamientos_facial = document.getElementsByClassName('tratamientos_facial');
for (var i = 0; i < tratamientos_facial.length; i++) {
// set the URL as an attribute of the element itself so we can get it later
tratamientos_facial[i].setAttribute('data-url', array_asyncs_facial_location[i]);
tratamientos_facial[i].addEventListener('click', (event) => {
// make sure to pass the event argument through
let xhr = new XMLHttpRequest;
// now we just query the dataset property
let url = event.target.dataset('url');
xhr.open('GET', url);
xhr.addEventListener('load', () => {
if (xhr.status == 200) {
let plantilla = xhr.response;
let container_xhr_info = document.querySelector('#container_xhr_info');
container_xhr_info.innerHTML = plantilla;
}
})
xhr.send();
})
}
EDIT: made title more specific to address duplicate question problem.
I have an HTML page that has a button and a div. When the user clicks the button, a function runs an XMLHttpRequest to get a table from an XML file. The table should be inserted into the div.
The AJAX request returns an object Element to my variable. How can I get the content of that variable (i.e., the <table><tr><td>... etc. from the XML file) to insert into the div?
Here's the JavaScript:
function table_loadContent() {
var request;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
console.log("HTTPRequest");
} else {
request = new ActiveXObject("Microsoft.XMLHTTP");
console.log("ActiveX");
}
request.open('GET', 'myXMLFile.xml');
request.onreadystatechange = function() {
if ((request.readyState === 4) && (request.status === 200)) {
var newTable = request.responseXML.getElementsByTagName('table');
document.getElementById('myDiv').appendChild(newTable);
}
}
request.send();
}
Variations that I've tried that don't work:
document.getElementById("myDiv").innerHTML = myVar;
document.getElementById("myDiv").appendChild(myVar);
document.getElementById("myDiv").appendChild(myVar[0]);
same as above but using a second variable to hold a .toString() version
tried declaring the XML file as XML and as HTML
var newTable = request.responseXML.getElementById('myTable');
In the next code:
var newTable = request.responseXML.documentElement.getElementsByTagName('table');
The newTable variable is an HTMLCollection, it is not an element.
If you want to append just the first element in the collection you can use:
document.getElementById('myDiv').appendChild(newTable[0]);
If you want to add all the nodes inside the collection you need to iterate in the collection and add to the div each element contained in it:
var newTable = request.responseXML.documentElement.querySelectorAll('table');
var myDiv = document.getElementById('myDiv');
[].forEach.call(newTable, function (table) {
myDiv.appendChild(table)
});
NOTES:
1 - I've used querySelectorAll method because it is a not-live HTMLCollection and can be used to modify the DOM without modifying the collection.
2 - Use the documentElement property of the responseXML to ensure that you are selecting the main document node.
We discovered the solution:
By changing the lines:
var newTable = request.responseXML.getElementsByTagName('table')
document.getElementById('myDiv').appendChild(newTable)
to:
var newTable = request.responseText
document.getElementById('myDiv').innerHTML = newTable
it works as we'd like it to. (Edit frustration: I can't get it to break lines properly)
I've been looking through a lot of previously asked questions and I'm still stumped on how to get this fileReader to work. Currently the code I have is as follows:
var reader = new XMLHttpRequest() || new ActiveXObject('MSXML2.XMLHTTP');
function updateWineProfile(name){
//write bio info
var filePath = "Data/"+name+".txt";
loadFile(filePath, name);
}
function loadFile(filePath, wineName) {
reader.onreadystatechange = displayContents(wineName);
reader.onload = doneLoading(wineName);
reader.open('get', filePath, true);
reader.send();
}
function displayContents(wineName) {
if(reader.readyState === 4 && reader.status === 200) {
reader.responseText = formatText(reader.responseText, wineName);
//document.getElementById('Info').innerHTML = "";
} else{
reader.responseText = formatText(reader.responseText, wineName);
document.getElementById('Info').innerHTML = formatText("Text Loading. Please Try Again.", wineName);
}
}
function doneLoading(name) {
document.getElementById('Info').innerHTML = formatText(this.responseText, name);
}
I have a list of names, and for each name I have a .txt file with "Info". updateWineProfile is called during an onClick event handler on the list of names.
My issue is that it only works after the second click. Imagine I have a list of 'A' and 'B'. If I click 'A' for the first time it'll read my little "Text Loading" line. But once I click 'B' it populates the 'Info' with item A's bio while displaying item B's name.
It seems that this.responseText isn't updated when its passed into formatText(). I thought that adding the reader.onload would fix this, but it hasn't.
(I'm only reading local files)
You need to assign a function to onload and onreadystatechange.
You are calling displayContents(wineName) and doneLoading(wineName) immediately and then assigning their return values (undefined in both cases as they have no return statements).
I'm taking a class on Ajax and this very simple script is throwing an error I don't understand. I keep getting an Uncaught Type Error saying that the appendChild method is not a function.
The function I'm working through is meant to asynchronously load the contents of a text file into a 'p' tag when the link is clicked.
I'm somewhat new to javascript so it's probably something simple I've missed, I'd appreciate any help in understanding what I'm doing wrong.
(function(){
var link = document.getElementsByTagName('a')[0];
link.onclick = function(){
//create xhr object
var xhr = new XMLHttpRequest();
// handle the "onreadystatechange" event
/*
xhr.readysState property values
0 = Uninitialized
1 = Loading
2 = Loaded
3 = Interactive - server is sending a response
4 = Complete
*/
xhr.onreadystatechange = function() {
if ( (xhr.readyState == 4) && (xhr.status == 200 || xhr.status == 304) ) {
xhr.responseText;
var body = document.getElementsByTagName('body')[0];
var p = document.getElementsByTagName('p');
var pText = document.createTextNode(xhr.responseText);
console.log(p);
console.log(pText);
p.appendChild(pText);
body.appendChild(p);
}
};
// open the request
/* arguments:
1. type of request a (GET or POST)
2. path
3. asynchronaus request? (true || false)*/
xhr.open('GET', 'files/ajax.txt', true);
// send the request
xhr.send(null)
return false; // disable default behavior of link
};
})();
I've created a jsFiddle to show my code:
http://jsfiddle.net/scottm1164/656edjsf/
getElementsByTagName returns a NodeList. Therefore, you have to access individual items with an index like
var p = document.getElementsByTagName('p')[0];
The reason it was giving you
p.appendChild is not a function
is because appendChild is not a function of type NodeList
p is a NodeList; I think you meant document.createElement rather than document.getElementsByTagName.
var p = document.createElement('p');
p is an array of elements. You should select just one. In your fiddle, you don't even have 1.
Perplexed by this issue of my xmlhttp failing to complete. The issue only arises when I have multiple calls. Oddly enough only the last one completes. I feel as if the first one times out or something. In watching the code in the window the ready state change function fires a few times but the value is always 1 and eventually it jumps out and performs the next call. Is there a way of fixing this? MAybe adding a delay? Any advice is much apprecaited.
<script>
<!--var plant_select = document.createElement("select"); -->
var datafile = '';
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET","http://localhost:8080/res/plants.csv",true);
xmlhttp.send();
xmlhttp.onreadystatechange = function()
{
if(xmlhttp.status==200 && xmlhttp.readyState==4)
{
processCSV(xmlhttp.responseText, document.getElementById("plant_select"),"Select Plant");
}
}
</script>
</select>
</div>
<div class="menu_element" id="plantType_div">
<select class="Menu" id="plantType_select">
<script>
<!--var plant_select = document.createElement("select"); -->
var datafile = '';
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET","http://localhost:8080/res/planttypes.csv",true);
xmlhttp.send();
xmlhttp.onreadystatechange = function()
{
if(xmlhttp.status==200 && xmlhttp.readyState==4)
{
processCSV(xmlhttp.responseText, document.getElementById("plantType_select"),"Select Plant Type");
}
}
</script>
You are using the same, global variable for each request: var xmlhttp. Each subsequent instance then tries to operate on the same variable. So you will only get the last one because that was the last value of the variable to be written before any of the responses got back.
Wrap each instance in a function so you are dealing with locally scoped variables instead of globals.
You are using the same object every time:
var xmlhttp = new XMLHttpRequest();
Try to give a different name for each request.