I am doing a project where I do not use jQuery.
I am trying to load posts from new pages with Ajax, but I don't know how I should extract a specific HTML element or content inside a specific HTML element from the returned string, properly.
Here is how the HTML (for the part I am trying to load new contents into) looks like;
<div id="posts">
<div class="post"> 1 </div>
<div class="post"> 2 </div>
<div class="post"> 3 </div>
<div class="post"> 4 </div>
<div class="post"> 5 </div>
<!--posts-->
</div>
<div id="loadmore">Load More</div>
Here is a sample of how the script I tried looks like (it works);
function load(path) {
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == 200) {
var resp = req.responseText;
var cont = resp.slice(resp.indexOf('id="posts">') + 11, resp.indexOf("<!--posts-->"));
document.getElementById("posts").innerHTML = document.getElementById("posts").innerHTML + cont;
}
}
req.open("GET", path, true);
req.send();
}
var page = 1;
document.getElementById("loadmore").onclick = function () {
page++;
var pathToLoad = "/page/" + page;
load(pathToLoad);
}
I feel like I am doing it the worst way, and my question is how do I load contents that is inside "#posts" from the new page in a better way?
Thank you!
If i understood what you're looking for, you could create a new element and insert the responseText into that element.
function load(path) {
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == 200) {
var resp = req.responseText,
d = document.createElement('div');
d.innerHTML = resp; // This way you have a dom node you can easily manipulate or iterate through
for(var i = 0; i < d.getElementsByClassName("post").length; i++){
var el = d.getElementsByClassName("post")[i];
console.log(el.innerHTML);
//Do stuff with the element you need to append.
}
}
}
req.open("GET", path, true);
req.send();
}
Using createElement function you have a Element object, that you can easily use and manipulate for your purposes. Here is the reference for the element object.
https://developer.mozilla.org/en-US/docs/Web/API/Element
It's been almost a month, I was busy with other works, that is why I am late. This project is my favorite though. The big part of the project is also focused on animations, working on that (in JavaScript).
I have learned a lot about "practical JavaScript" in the past month (I was lazy with jQuery before), I made a small library to avoid a lot of repetitions, mostly I will still use native JavaScript 'cause it's cool. My library is a compliment to the JavaScript for my project, not being lazy again - Oh no.
Also never going back to jQuery again or use anything I do not understand hundred percent. Sticking to my rules.
This is what I made after the help from #gmast. It works, and I am satisfied now.
function load(path) {
var rUrl = path.substring(0, path.indexOf(" ")),
rSelector = path.substring(path.indexOf(" ") + 1),
req = new XMLHttpRequest();
req.open("GET", rUrl, true);
req.onreadystatechange = function () {
if (req.readyState == 4 && req.status == 200) {
var nDoc = document.createElement('div'), nElem, length, i;
nDoc.innerHTML = req.responseText;
nElem = nDoc.querySelectorAll(rSelector);
length = nDoc.length;
for (i = 0; i < length; i += 1) {
document.getElementById("posts").appendChild(nElem[i]);
}
}
};
req.send();
}
Then I call it like;
var pageNumber = 1;
document.getElementById("loadmore").addEventListener("click", function () {
pageNumber++;
var pathToLoad = "/page/" + page + " #posts > .post";
load(pathToLoad);
});
Explanation:
The reason I changed getElementsByClassName to querySelectorAll is because I am going to use it for some more things, and the actual code I have to use in the project is more complex with callbacks and a lot of different objects. I would prefer getElementsByClassName as suggested by #gmast, if I did not have to use this function for multiple different things.
Getting the length of the elements out side of the for loop is important, it creates bugs otherwise, because here I am removing one element with every loop. Optionally do not use increment (that is do not use i++, i += 1 etc) to solve the problem.
If you see my comments under #gmast's answer you will see I was talking about getting "[object HTMLDivElement]", that is because innerHTML accepts HTML not objects, and also my point was about the numbers.
I was noob about most part of JavaScript that is connected with the DOM. Now I am better.
Related
My code works in Chrome and Safari, but it hangs in FF.
I removed the parts of the code that aren't necessary.
I used console commands to show how far the first loop gets, and it will do the second log fine right before the xhr open and send commands.
If the open/send commands are present the loop only happens once, if I remove the open/send commands the loop completes successfully.
Currently using FF 62nightly, but this issue has plagued me since Quantum has come out and I'm now trying to figure out why it doesn't work right.
for (i = 0; i < length; i++) {
(function(i) {
// new XMLHttpRequest
xhr[i] = new XMLHttpRequest();
// gets machine url from href tag
url = rows[i].getElementsByTagName("td")[0].getElementsByTagName('a')[0].getAttribute('href');
// Insert the desired values at the end of each row;
// will try to make this customizable later as well
insertVNC[i] = rows[i].insertCell(-1);
insertSerial[i] = rows[i].insertCell(-1);
insertVersion[i] = rows[i].insertCell(-1);
insertFreeDiskSpace[i] = rows[i].insertCell(-1);
// the fun part: this function takes each url, loads it in the background,
// retrieves the values needed, and then discards the page once the function is complete;
// In theory you could add whatever you want without taking significantly longer
// as long as it's on this page
console.log(i);
xhr[i].onreadystatechange = function() {
if (xhr[i].readyState == 4 && xhr[i].status == 200) {
}
};
//"Get" the "Url"... true means asyncrhonous
console.log(url);
xhr[i].open("GET", url, true);
xhr[i].send(null);
})(i); //end for loop
}
I cannot tell you why it gives issues in Firefox. I would not trust sending off arbitrarily many requests from any browser
I would personally try this instead since it will not fire off the next one until one is finished
const urls = [...document.querySelectorAll("tr>td:nth-child(0) a")].map(x => x.href);
let cnt=0;
function getUrl() {
console.log(urls[cnt]);
xhr[i].open("GET", urls[cnt], true);
xhr[i].send(null);
}
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr[i].readyState == 4 && xhr[i].status == 200) {
if (cnt>urls.length) getUrl();
cnt++;
}
}
getUrl();
Am having the data as
var response = '[{"id":1,"name":"Web Demo"},{"id":2,"name":"Audio Countdown"},{"id":3,"name":"The Tab Key"},{"id":4,"name":"Music Sleep Timer"}]';
var obj = JSON.parse(response);
So how I can add the list items dynamically so in future if the list items in the backend increases they should be added directly and how can link dynamic and static elements.
Thanks
Use js appendChild to append each item in your html like this...
var response = [{"id":1,"name":"Web Demo"},{"id":2,"name":"Audio Countdown"},{"id":3,"name":"The Tab Key"},{"id":4,"name":"Music Sleep Timer"}] ;
for (i = 0; i < response.length; i++)
{
var node = document.createElement("LI"); // Create a <li> node
var textnode = document.createTextNode(response[i].name); // Create a text node
node.appendChild(textnode);
document.getElementById("items").appendChild(node); ///append Item
}
<div id="items"></div>
If you want to change your lists according the the changes in the backend, you should periodically test for that via ajax. You can use setInterval and
XMLHttpRequest for these purposes.
When the new data arrives, you should erase your existing list and add new elements to represent the arrived data dynamically.
setInterval(function(){
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
populateList(xhttp.responseText); //This is your function to create the list
}
};
xhttp.open("GET", "http://www.mybackend.com/list", true);
xhttp.send();
})
And you can use document.createElement function to create a series of elements to represent your data.
function populateList(data) {
var obj = var obj = JSON.parse(response);
document.getElementById('myUl').remove(); //Delete the existing list
var myUi = document.createElement('ul'); //Add new list
obj.forEach(item) {
var listItem = document.createElement('li');
myUi.appendChild(li);
//Then add li details here
}
}
This is just rough code. Your can youse Jquery to get this done very shorter than this. You can even use a JS framework like Angular to automate most of these tasks.
If you are storing dynamic data as like static data, use concat
response.concat(dynamicdata);
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.
I'm very beginner so I'm sorry if something doesn't make sense :)
I'm trying to search an XML DOM by ClassName, using a custom function which only seems to work when I'm searching the document the script is in.
This is the function:
Object.prototype.getElementsByClassName = function(getElementsByClass) {
function getElementsByClass(searchClass,node,tag) {
var classElements = new Array();
if ( node == null )
node = document;
if ( tag == null )
tag = '*';
var els = node.getElementsByTagName(tag);
var elsLen = els.length;
var pattern = new RegExp('(^|\\\\s)'+searchClass+'(\\\\s|$)');
for (i = 0, j = 0; i < elsLen; i++) {
if ( pattern.test(els[i].className) ) {
classElements[j] = els[i];
j++;
}
}
return classElements;
}
xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET","somexml.xml",false);
xmlhttp.send();
xmlDoc=xmlhttp.responseXML;
var foo = xmlDoc.getElementsByClassName("gridCellAlt");
(I then output foo somewhere down later - the output all works fine.)
Now, everything works if I use xmlDoc.getElementsByTagName and use a tag name - however, the page I am going to be accessing makes use of class names, so I want to use xmlDoc.getElementsByClassName. The above script works completely fine if I use var foo = document.getElementsByClassName("gridCellAlt"); and include the information I am accessing in the page running the script, but when I try to use it on the xmlDoc DOM it doesn't work. To sum it up: the only time the script doesn't work is when I'm using the xmlDoc (instead of document), and the getElementsbyClassName in conjunction. If I use document and getElementsByClassName its fine. If I use xmlDoc and get elementsByTagName its fine.
I'm thinking the getElementsByClassName isn't searching within the DOM nodes correctly, but even if I put class="gridCellAlt" somewhere in the root node of a test DOM it still doesn't find it. I've tried adding more information behind foo = xmlDoc.getElementsByClassName("gridCellAlt", (info here)) but that doesn't seem to address the issue either.
xmlhttp.onreadystatechange = function(){
if(xmlhttp.readyState == 4){
xmlDoc=xmlhttp.responseXML;
var foo = xmlDoc.getElementsByClassName("gridCellAlt");
}
}
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