Javascript problem regarding sending the correct href into the DOM

I am building a site which looks something like this:

I want it to behave in a way that everytime I clicked the thumbnails of those individual playlist, the upper main video changes to the newly clicked video.

Here is the javascript code I have.

// constants
const key = 'myAPI key here';
const playlistId = 'UUuSrv3qgQA7SSi6R9bWag5A';
var URL = 'https://www.googleapis.com/youtube/v3/playlistItems';

//youtube API sees all these info, so that it knows what kind of information you want to retrieve
const options = {
  playlistId: playlistId,
  maxResults: 20,
  key: key,
  part: 'snippet'
};

let loadMainVideo = function () {

  //put in options into the URL so that youtube API will work
  URL += '?' + Object.keys(options).map((k) => k + '=' + encodeURIComponent(options[k])).join('&');
  //use fetch to get HTTP request
  fetch(URL)
    .then(res => res.json())
    .then(function (data) {
      // console.log(data);
      loadPlayList(data);
      var first_video_id = data.items[0].snippet.resourceId.videoId;
      //put the 1st video in the playlist into the dom
      document.getElementById('youtube_feed').innerHTML = `
      <iframe width="1280" height="720" src="https://www.youtube.com/embed/${first_video_id}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
      `
    })
    .catch(err => console.log(err))
}

loadMainVideo();


/*  
Load Playlist
*/

function loadPlayList(data) {
  console.log(data.items.length); //this gives you 20 which is correct

  //let's try loop through the array using for loop
  for (let i = 1; i < data.items.length; i++) {
    var thumbnail = data.items[i].snippet.thumbnails.medium.url;
    var title = data.items[i].snippet.title.substring(0, 50);
    var videoID = data.items[i].snippet.resourceId.videoId;


    document.getElementById('youtube_playlist').innerHTML += `
      <div class="individual_list_item" data-key = "${videoID}">
      <img src="${thumbnail}" alt="video_thumbnail_placeholder" class="thumbnails">
        <p class="playlist_titles">${title}</p>
    </div>
      `;
  }
}


document.getElementById('youtube_playlist').addEventListener('click', function () {
  //1. click thumbnail, log video ID of the thumbnail video.
  //2. click thumbnail , insert video ID of the thumbnail video into first_video_id in fetch function.
})

In the snippet above I have already successfully inserted all the video snippet into the DOM using .innerHTML += ,

How do I get the clicked video’s ID then?

should I try forEach() as well as append child method when adding in HTML element?

When you say id you mean the value of the data attribute data-key, right?

At least for the first part.
You need to check the event.target and get the dataset.key

Quick pen just to show what i mean.

1 Like

thanks for the reply!
could you explain your code a little bit?
especially this part:

  let key;
  if (event.target.dataset.key) {
    key = event.target.dataset.key;
  } else {
    console.log(event.target.parentElement.dataset.key);
    key = event.target.parentElement.dataset.key;
  }

The event.target is the element that was clicked (fired the click event).

If an element has a data attribute on it, it will have a dataset property.

event.target.dataset.key

You can think of is as, dataset.key === data-key

// Var for storing the id/key
let key;
// If the element that was clicked has a dataset.key
if (event.target.dataset.key) {
  // Store the key
  key = event.target.dataset.key;
  // If it does not have a dataset.key, it must be one of the childern, either the img, or p element
} else {
  // Go up to the parrent element and get the id/key
  key = event.target.parentElement.dataset.key;
}
1 Like

okay… right now I have been able to log out individual video’s video_id whenever I click on the thumbnail.

let loadMainVideo = function () {

  //put in options into the URL so that youtube API will work
  URL += '?' + Object.keys(options).map((k) => k + '=' + encodeURIComponent(options[k])).join('&');
  //use fetch to get HTTP request
  fetch(URL)
    .then(res => res.json())
    .then(function (data) {
      loadPlayList(data);
      var first_video_id = data.items[0].snippet.resourceId.videoId;
      //put the 1st video in the playlist into the dom
      document.getElementById('youtube_feed').innerHTML = `
      <iframe width="1280" height="720" src="https://www.youtube.com/embed/${first_video_id}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
      `


      document
        .getElementById('youtube_playlist')
        .addEventListener('click', function (event) {
          // const target = event.target;
          // console.log({ target });

          if (event.target.dataset.key) {
            console.log(event.target.dataset.key);
            first_video_id = event.target.dataset.key;
          } else {
            console.log(event.target.parentElement.dataset.key);
            first_video_id = event.target.parentElement.dataset.key;
          }

          // console.log("The key is", target);
        });
    })
    .catch(err => console.log(err))
}

loadMainVideo();

but how do I pass it into this part of the function?

      var first_video_id = data.items[0].snippet.resourceId.videoId;
      //put the 1st video in the playlist into the dom
      document.getElementById('youtube_feed').innerHTML = `
      <iframe width="1280" height="720" src="https://www.youtube.com/embed/${first_video_id}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
      `

my aim is that every time I click on the thumbnail the main video feed switch to the co-responding video.

sorry if it seems that I need you to handhold me through step by step.
I’m fairly new to javascript…

var first_video_id … should be a global variable, not defined inside the ‘fetch’ function?

then you can set it to a new value,
and then call…
loadMainVideo();

I would suggest you separate the logic out into functions and maybe an object to store data.

As far as I can tell you would need a fetch function, 2 render functions and a callback handler function (make it a named function and pass it to the click event handler). The callback can set the id in an object or you can call the main video render function from within it and pass it the id.

function fetchPlaylist()
function renderPlaylist(data)
function renderMainVideo(id)
function handleGetPlaylistVideo()

Do you have your code anywhere we can see it in a more complete state.

Here is the git repo.

@lasjorg

I am re-writing my app.js file according to your suggestion.
I will tag you if I got into any issues.
Let me work on this on my own 1st. Thanks!

Here is my progress so far, I am at the last step where I need to created the call back function whenever user click on those playlist items. Logic wise I am stuck.

To put it in pseudocode I think it is something like this:

whenever the user click the specific video, 1st get the videoID of that video, 2nd, pass in that videoID into mainVideoID.

But I couldn’t convert this into javascript. Here is my code.

/* 
TODO list

1. need a fetch function to handle HTTP request
2. renderMainVideo() 
3. renderPlaylist()
4. call back function for the click event
*/


/*
Global variables and constants
*/


const key = 'my api key';
const playlistId = 'UUuSrv3qgQA7SSi6R9bWag5A';
var URL = 'https://www.googleapis.com/youtube/v3/playlistItems';

const options = {
  playlistId: playlistId,
  maxResults: 20,
  key: key,
  part: 'snippet'
};

var mainVideoID;
var new_data;

/* 
HTTP request
*/
URL += '?' + Object.keys(options).map((k) => k + '=' + encodeURIComponent(options[k])).join('&');

fetch(URL)
  .then(res => res.json())
  .then(function (data) {
    new_data = data;
    mainVideoID = data.items[0].snippet.resourceId.videoId;
    console.log(data);
    renderPlaylist(data);
  })


/*
Render main video
 */
setTimeout(function renderMainVideo() {
  document.getElementById('youtube_feed').innerHTML = `
      <iframe width="1280" height="720" src="https://www.youtube.com/embed/${mainVideoID}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`
}, 1000);

/*
Render Playlist
*/
function renderPlaylist(data) {
  console.log(data.items.length); //this gives you 20 which is correct

  //let's try loop through the array using for loop
  for (let i = 1; i < data.items.length; i++) {
    var thumbnail = data.items[i].snippet.thumbnails.medium.url;
    var title = data.items[i].snippet.title.substring(0, 50);
    var videoID = data.items[i].snippet.resourceId.videoId;


    document.getElementById('youtube_playlist').innerHTML += `
      <div class="individual_list_item" data-key = "${videoID}">
      <img src="${thumbnail}" alt="video_thumbnail_placeholder" class="thumbnails">
        <p class="playlist_titles">${title}</p>
    </div>
      `;
  }
}

/*
Call back function for the click event
*/

/* every time you click the video, you want to get the videoID of that specific video */

// setTimeout(function () {
//   console.log("main video ID is:" + mainVideoID);
// }, 1000);

function setID() {
  mainVideoID = 
}

There are probably more elegant ways to do this but I have to use setTimeout for now.
I think it is okay. I just want to get this thing working 1st.

First unwrap the renderMainVideo definition from the setTimeout. If you want to call renderMain in a setTimeout just pass it in as a callback. Now use the code i posted before and instead of just saving the id, call renderMainVideo and pass in the id.

Here is a pen where i use the data-key as the size for the placeholder image, i think it should give you an idea. I set a default parameter value of 250 on the first run of renderMainVideo (when setTimeout calls it on page load), then when you click the thumbnail or title it sets the key to the data-key value (450) and calls renderMainVideo passing in the key as an argument.

1 Like

If I take renderMainVideo out of the setTimeout function, the main video will not be rendered.
Click handler works just fine.

var mainVideoID;
var new_data;

/* 
HTTP request
*/
URL += '?' + Object.keys(options).map((k) => k + '=' + encodeURIComponent(options[k])).join('&');

fetch(URL)
  .then(res => res.json())
  .then(function (data) {
    new_data = data;
    mainVideoID = data.items[0].snippet.resourceId.videoId;
    console.log(data);
    renderPlaylist(data);
  })


/*
Render main video
 */
function renderMainVideo(mainVideoID) {
  document.getElementById('youtube_feed').innerHTML = `
      <iframe width="1280" height="720" src="https://www.youtube.com/embed/${mainVideoID}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`
}

/*
Render Playlist
*/
function renderPlaylist(data) {
  console.log(data.items.length); //this gives you 20 which is correct

  //let's try loop through the array using for loop
  for (let i = 1; i < data.items.length; i++) {
    var thumbnail = data.items[i].snippet.thumbnails.medium.url;
    var title = data.items[i].snippet.title.substring(0, 50);
    var videoID = data.items[i].snippet.resourceId.videoId;


    document.getElementById('youtube_playlist').innerHTML += `
      <div class="individual_list_item" data-key = "${videoID}">
      <img src="${thumbnail}" alt="video_thumbnail_placeholder" class="thumbnails">
        <p class="playlist_titles">${title}</p>
    </div>
      `;
  }
}

/*
Call back function for the click event
*/

/* every time you click the video, you want to get the videoID of that specific video */

document.getElementById('youtube_playlist')
  .addEventListener('click', function (event) {
    const target = event.target;

    let mainVideoID;
    if (event.target.dataset.key) {
      mainVideoID = event.target.dataset.key;
      renderMainVideo(mainVideoID);
    } else {
      mainVideoID = event.target.parentElement.dataset.key;
      renderMainVideo(mainVideoID);
    }
  })

setTimeout(renderMainVideo, 1000);

Try logging out mainVideoID inside renderMainVideo, my guess is it’s being called before the fetch has finished so it’s undefined.

yeah It is the issue.
I couldn’t get it to print out output inside the renderMainVideo function, but that’s why the 1st time I showed you the code I put it inside the setTimeout function.

Oh wait, your relying on a global variable, there need to be a difference between the parameter name and the global variable.

I would really suggest you make an actual fetch function and store the results in an object before you try to reference the data from the fetch operation. Or make the fetch function return the data (or a promise) and then grab it where you need it. The fetch function should only do that and nothing else.

1 Like