How to query select html made with javascript, and make more html?

How to query select html made with javascript, and make more html?
0

#1

I made a string of html with javascript and I am adding it to the dom. I want to select an element from that html string with a query selector so I can make another string of html in another if statement. When I try to do this the console says the selected element is null. Is it possible to select a javascript created html element? Here is the function with the code:

 function streams(){
    if(this.readyState === XMLHttpRequest.DONE){
      if(this.status === 200){
        let data = JSON.parse(this.response);
        console.log(data.stream);
const main = document.querySelector('.center-box');
 const desc = document.querySelector('.gamers');
if(data.logo){
          main.innerHTML += ` <div class ='gamers'>
    <a href='${data.url}' target='blank'><img class='logo' src='${data.logo}'></a>
    <h4 class='streamer'>${data.display_name}</h4>
  </div>`;
  
}
        if(data.stream){
  desc.innerHTML += `<p class='desc'></p>
    <h6 class='online'>online</h6>`
} else {
  desc.innerHTML += `
<h6 class='offline'>offline</h6>
`
}

      } 
    }
  }

I am trying to make the const desc select the class .gamers which is made by javascript under an if statement.


#2

Can you provide a link to your entire project?

The line above would select a single element with class=“gamers”. However, it appears you do not create a div with class=‘gamers’ until after you have tried to select it with the querySelector.


#3


I didn’t realize where I placed the selector mattered. I put the selector under my if statement, but my console keeps return 'reference error disc is not defined. And now I’m getting a bug, even though data.stream is false, it is returning true with the html, and the html for that is only showing up on the first div, not the other 3.


#4

Because you put the const desc = document.querySelector(’.gamers’); above the code that would create any divs with class=“gamers”, desc will be null and down lower you try to reference desc.innerHTML, which will not work, because desc is null. Once you have created some divs with class=“gamers”, then querySelector could access them. Keep in mind that querySelector only selects the first one it finds. querySelectorAll would select all of the divs with class=“gamers”.


#5

Thanks, I’m not getting a refence error anymore for desc, I moved it after the if statement where I create the .gamers class.
I am still having trouble using the desc selector in my if statement though with if(data.stream). I tried debugging with console.log using true or false, and I am getting both logged in the console, but my html isn’t being made even though the reference error for disc is gone. Do you know why this is happening? I’ve tried moving my selector for const inside both of my if statements, but it only doesn’t show an error when it is just in the scope of the streams function, outside of both if statements that I made.


#6

I can’t figure it out so I’m just going to remake my code. I’m using .createElement now to make the html for my json objects and so far its working out nicely.


#7

alright when I use .createElement it works, it looks messy because I’ve been ignoring the css, but it works so far. I still have no idea why the previous method I tried didn’t work, with all my html in a string and I would appreciate if anyone could explain why it wasn’t.

EDIT: nevermind still not working, but a step forward since the true statement isn’t completely broken.


#8

You know you must eventually make two different types of API requests. For each username in your url array, you will need to request the channel information. For each username is your url array, you will also need to get the stream status so you can see if it is null or not to display some extra information. The main problem right now is you are calling the streams function on both of those calls. The streams function has the following if statement which ONLY makes sense if you requesting the streams end point and not the channels, because there is no such property called stream in the channels json response, so it will error out.

if(data.stream && data.logo != null){

My suggestion is to first get the channels information displaying first and then figure out where and how you should make the 2nd request to the streams end point.

Also, I am not sure why you are calling streams(); in the very last line of your getStreams function, because it should only be called on the httpRequest.onreadystatechange. Definitely get rid of that last line in the function.

Below, I refactored your original code slightly by commenting out some items and in general, making the code more readable with proper indentation. This code will actually display the channel information for each user (using your named IIFE called makeRequest). I also gave you a bonus in the streams function. You should notice that I added an id attribute which happens to be the username. This will come in handy when you start making requests to the streams channel, as you will be able to easily add additional elements to the applicable div with id=“username”.

function getStreams() {
  let url = ["elajjaz", "eleaguetv", "destiny", "blusewilly_retry"];

  (function makeRequest() {
    url.forEach(function(e) {
      let httpRequest = new XMLHttpRequest();
      if (!httpRequest) {
        alert("can not create http instance!");
        return false;
      }
      httpRequest.onreadystatechange = streams;
      httpRequest.open("GET", "https://wind-bow.glitch.me/twitch-api/channels/" + e, true);
      httpRequest.send();
    });
  })();

  //    (function makeRequest2(){
  //     url.forEach(function(e){
  //     let httpRequest = new XMLHttpRequest();
  //     if(!httpRequest){
  //       alert('can not create http instance!')
  //       return false;
  //     }
  //     httpRequest.onreadystatechange = streams;
  //     httpRequest.open('GET', 'https://wind-bow.glitch.me/twitch-api/streams/' + e, true);
  //     httpRequest.send();
  //       });
  // return true;
  //   })();

  function streams() {
    if (this.readyState === XMLHttpRequest.DONE) {
      if (this.status === 200) {
        let data = JSON.parse(this.response);
        main.innerHTML += `
          <div id='${data.name}' class ='gamers'>
            <a href='${data.url}' target='blank'><img class='logo' src='${data.logo}'></a>
            <h4 class='streamer'>${data.display_name}</h4>
          </div>
        `;
        // <p class='offline'>online</p>
        // <p class='game'>${data.stream.game}</p>
      }
    }
  }
  // streams();
}

const main = document.querySelector(".center-box");
document.addEventListener("DOMContentLoaded", function(event) {
  getStreams();
});

#9

I couldn’t find out how to make it work in the streams function, so now I have two seperate functions. The streamers function now only takes the response data from makeRequest, and now the function streams takes the response data from my ajax request with the data.streams object. in streams function now with my if statement I am correctly getting who is online and offline now, but I have an issue with how it is positioned in my html which I’m not sure how to fix. I want to put the online or offline html strings from my streams function into the div with the class .gamers, made from my other streamers function with a different ajax call. I can’t use a querySelector to get .gamers because it shows a reference error. append child to center-box doesn’t work, so now I’m stuck with the result of the if statement being randomly placed across the center-box each time it is reloaded. I tried passing .gamers as a parameter to the streamers function, streamers(gamers), but this showed an httprequest object strangely, and I don’t really know how to solve it.


#10

Both the makeRequest and the makeRequest2 are running asynchronously, so it is quite possible that a response from the streams endpoint for a particular username could come back before than a response from the channels end point. You really should not be making a call to the streams end point for a specific username, until you have gotten a response back from the channels endpoint for that same username. So,inside your streamers function, you need to call a stand-alone version of makeRequest2 (currently it is a IIFE) and pass the current username (data.name) so you can build the correct url for the API endpoint call. This call to makeRequest2 should be after the line which starts with main.innerHTML += …

You will have to make a small tweak to makeRequest2, so that you can pass the username to the streams function. See below for the refactored makeRequest2:

  function makeRequest2(username) {
    let httpRequest = new XMLHttpRequest();
    if (!httpRequest) {
      alert("can not create http instance!");
      return false;
    }
    httpRequest.onreadystatechange = function() {
      streams(this, username); // this is httpRequest
    }
    httpRequest.open("GET", "https://wind-bow.glitch.me/twitch-api/streams/" + username, true);
    httpRequest.send();
  }

Then your streams function would look something like below. Notice that I commented out your main.innerHTML += part. This was what was putting the information “randomly” on your page. You do not want to add this html to main. Instead, you want to target the channel div you created for the particular username. Remember, when I told you in a previous thread having an id attribute for each div of class=“gamer” would be useful… This is why. You will see that I have put one other comment below each offline and online code section. You just need to replace ??? with something else.

  function streams(httpReq, username){
    if(httpReq.readyState === XMLHttpRequest.DONE){
      if(httpReq.status === 200){
        let data = JSON.parse(httpReq.response);
        if(data.stream === null){
          let offline = ` <p class='offline'> offline </p>`;
          // main.innerHTML += offline;
          // need to concatenate offline to the div with id equal to the username passed into this function
          ???.innerHTML += offline;
        }
        else {  
          let online =` <p class='online'>${data.stream.game} <span>online</span> </p>`; 
          // main.innerHTML += online;
          // need to concatenate online to the div with id equal to the username passed into this function 
          ???.innerHTML += online;
        }     
      }   
    }    
  }

I gave you most of this in the previous reply. I am hoping the extra help I have given you here can help you complete this challenge and understand how this works and why your previous attempt was not. If you have any questions about why I did anything described/written here, please do not hesitate to ask.


#12

alright I made a variable called div to query select the id of the streamers, and I passed it into the makeRequest2 function and the streams function, and I made username = data.name and also passed it into those functions, but both of those are returning as undefined when I try to use them in the streams function, I don’t know why.

const div = main.querySelector(#${data.name});
let username = data.name;
makeRequest2(username, div)

streams(httpRequest, username, div)
and username and div return undefined in streams.


#13

In your current Codpen version, your streamers function contains a function called makeRequest2 with two parameters username and div. However, later in the streamers function you make the following call:

makeRequest2();

Since you did not pass values into the function, username and div will be undefined. You should make the call like:

makeRequest2(username, div);

#14

I’m having trouble with my div in streams function. I am trying to set div’s inner html equal to the online and offline variables, but the console keeps showing that div is undefined. I am console.logging div, and in the console it shows the html div tag with the correct id’s, so I don’t know why it keeps showing undefined. The offline and online true or false statements are working correctly. And even though the console is showing that the divs are undefined, only one of my users divs is updated to offline. Why is this happening?

EDIT: only the last user id html that is returned from the ajax request is updated with either online or offline, while the rest of the users html are blank and the console shows div is undefined.


#15

I saw that I was calling makeRequest2 2 times in my streamers functoin, fixed that and now I’m not getting an error that div is undefined, but I still have the problem that only one streamers html is updated in my list. I’m not sure how to approach this to fix it, tried a forEach loop and a for loop and that did nothing. I think next time I’ll do this using promises, this is getting really too messy to read and work with, and another guy helped me solve this whole problem already using async functions, but I just wanted to finish it using ajax requests, even though its messy.


#16

After reviewing the code again for the streams function, you really do not need to pass in div, because to manipulate the div, you need to replace the following code:

        if(data.stream === null){
          let offline = ` <p class='offline'> offline </p>`; 
         div.innerHTML += offline;
        }else {       
          let online =` <p class='online'>${data.stream.game} <span>online</span> </p>`; 
          div.innerHTML += online; 
        }     

with the following:

        if(data.stream === null){      
          let offline = ` <p class='offline'> offline </p>`; 
          main.querySelector(`#${username}`).innerHTML += offline;
        }else {       
          let online =` <p class='online'>${data.stream.game} <span>online</span> </p>`; 
          main.querySelector(`#${username}`).innerHTML += online;           
        }   

#17

Been looking at this for 3 hours trying to think of a solution and it was this simple… I was thinking of just getting rid of the username argument because I wasn’t using it actually, but I didn’t expect it to work as a solution. Thanks again randelldawson. After all of this mess I’m done with using ajax, its too messy, time to learn promises.


#18

You are still using AJAX with async functions and promises. You were simply using another vanilla JavaScript way to implement ajax using the XMLHttpRequest method. XMLHttpRequest is a much older method which has been around for a very long time. Your overall use of XMLHttpRequest was made it seem like more of a mess, because you had a lot of duplicate code. I refactored your original code from 3 or 4 days ago and solved the challenge with substantially less code, but still using your general logic. The asynchronous nature of AJAX is the part you are still struggling with. Just keep practicing. Even though you may end up using async functions and promises, you still need to be able to understand legacy code which may still use XMLHttpRequest. I would also suggest starting this project from scratch, but still try to use XMLHttpRequest and make the solution DRY. You will cement your understanding of AJAX and know several ways to accomplishing the same thing.