YouTube video not showing

Two errors
: Cannot read property ‘querySelector’ of null
Cannot read property ‘classList’ of null"

https://jsfiddle.net/qp8jgske/

I told you in the other thread what would happen if you moved the element. I said the js would not find it now.

const wrapper = cover.nextElementSibling;

The element you want is no longer the next Siblng.

Try changing nextElementSibling to previousElementSibling.

e.g.

function onYouTubeIframeAPIReady() {
  const wrapper = cover.previousElementSibling;
  const frameContainer = wrapper.querySelector(".video");
  videoPlayer.addPlayer(frameContainer);
}

(function iife() {
  "use strict";

  function show(el) {
    el.classList.remove("hide");
  }

  function coverClickHandler(evt) {
    const wrapper = evt.currentTarget.previousElementSibling;
    show(wrapper);
    videoPlayer.play();
  }

Relative element references are only useful when elements remain in the same place.

A successful technique that allows things to move around is to have one reference to an overall container element, then use CSS selectors to access everything else inside.

const videoWrapper = document.querySelector(".video-wrapper");
const cover = videoWrapper.querySelector(".jacket");

By using the element referenced by videoWrapper as a common ancestor from which which everything inside is accessed,

function onYouTubeIframeAPIReady() {
  // const wrapper = cover.nextElementSibling;
  // const frameContainer = wrapper.querySelector(".video");
  const frameContainer = videoWrapper.querySelector(".video");
  function coverClickHandler(evt) {
    // const wrapper = evt.currentTarget.nextElementSibling;
    const wrapper = videoWrapper.querySelector(".wrap");

you can then move the HTML elements around inside of that .video-wrapper element and everything still continues to work.
https://jsfiddle.net/3q4dzyaL/

Why does this technique work so well? It’s because you are effectively using the one element as an interface by which to access any of the other elements contained within.

Best of all, this same technique remains reliable when multiple players are involved too.

2 Likes

I got it

I didn’t know there was a previousElementSibling.

https://jsfiddle.net/xLjhbo8m/1/

function onYouTubeIframeAPIReady() {
  const wrapper = cover.previousElementSibling;
  const frameContainer = wrapper.querySelector(".video");
  videoPlayer.addPlayer(frameContainer);
}

(function iife() {
  "use strict";

  function show(el) {
    el.classList.remove("hide");
  }

  function coverClickHandler(evt) {
    const wrapper = evt.currentTarget.previousElementSibling;
    show(wrapper);
    videoPlayer.play();
  }

  cover.addEventListener("click", coverClickHandler);
}());

1 error in jslint

Redefinition of ‘cover’ from line 2.
const cover = evt.currentTarget;

Last working code. Only fixed the YouTube part, the middle part.
https://jsfiddle.net/4asuL6tx/

How is this fixed?

My attempt- Replicated how the other code was fixed.
https://jsfiddle.net/y2ojgdke/1/

2nd attempt
https://jsfiddle.net/t5wu2jqo/

changed:
const cover = videoWrapper.querySelector(".jacket");

to
const cover = document.querySelector(".jacket");

It’s the global cover variable that’s causing the trouble. There’s no good reason for it to be global. More than that, most variables shouldn’t be global.

We can solve that by making the cover variable not global, by moving it into the manageCover function, just before the addEventListener line.

const videoWrapper = document.querySelector(".video-wrapper");

(function manageCover() {
    ...
    const cover = videoWrapper.querySelector(".jacket");
    cover.addEventListener("click", coverClickHandler);
}());

This didn’t work:
https://jsfiddle.net/j9zq6t3f/


function onYouTubeIframeAPIReady() {
  const cover = document.querySelector(".video-wrapper");
  const wrapper = cover.parentElement;
  const frameContainer = videoWrapper.querySelector(".video");
  videoPlayer.addPlayer(frameContainer);
}

(function iife() {
  "use strict";

  function show(el) {
    el.classList.remove("hide");
  }

  function coverClickHandler(evt) {
    const wrapper = videoWrapper.querySelector(".wrap");
    show(wrapper);
    videoPlayer.play();
  }
  const cover = document.querySelector(".video-wrapper");
  cover.addEventListener("click", coverClickHandler);
}());

This didn’t work either.
https://jsfiddle.net/j9zq6t3f/3/

function onYouTubeIframeAPIReady() {
  const videoWrapper = document.querySelector(".video-wrapper");
  const wrapper = cover.parentElement;
  const frameContainer = videoWrapper.querySelector(".video");
  videoPlayer.addPlayer(frameContainer);
}

(function iife() {
  "use strict";

  function show(el) {
    el.classList.remove("hide");
  }

  function coverClickHandler(evt) {
    const wrapper = videoWrapper.querySelector(".wrap");
    show(wrapper);
    videoPlayer.play();
  }
  const cover = document.querySelector(".video-wrapper");
  cover.addEventListener("click", coverClickHandler);
}());

At the bottom of the code you have cover used too, in the iife code. The cover variable needs to be assigned in there as well.

(function iife() {
    ...
    const cover = videoWrapper.querySelector(".jacket");
    cover.addEventListener("click", coverClickHandler);
}());

Instead of using global variables, there is a better technique where the self-invoking code has an init function. That way information that’s needed by them can be passed in to that init function.

The main idea is that instead of the code blindly groping outside of the function for something that may or may not exist, all that the function needs to know about is instead passed to the init function instead. It’s a much more reliable way of coding.

2 Likes

What did I do wrong?
https://jsfiddle.net/aLvdq7t9/


function onYouTubeIframeAPIReady() {
  const cover = videoWrapper.querySelector(".jacket");
  const wrapper = cover.parentElement;
  const frameContainer = videoWrapper.querySelector(".video");
  videoPlayer.addPlayer(frameContainer);
}

(function iife() {
  "use strict";

  function show(el) {
    el.classList.remove("hide");
  }

  function coverClickHandler(evt) {
    const wrapper = videoWrapper.querySelector(".wrap");
    show(wrapper);
    videoPlayer.play();
  }
  const cover = videoWrapper.querySelector(".jacket");
  cover.addEventListener("click", coverClickHandler);
}());

What is the first function for? Nothing presented here is calling it.

The youtube iframe library automatically calls that function when the library is loaded. I’ll investigate further in a few hours.

The browser console tells me:

Uncaught ReferenceError: videoWrapper is not defined at manageCover

That code needs videoWrapper to be defined at the top of the code.

const videoWrapper = document.querySelector(".video-wrapper");

If we are to do without that global videoWrapper being defined there, it will take some work to more appropriately structure the code using init methods, so that the code can do without videoWrapper as a (hoik-ptui!) global variable.

Getting it to work without video-wrapper being at the top, how might that work?

const videoWrapper = document.querySelector(".video-wrapper");

https://jsfiddle.net/jgcq6rat/

How we do that is by making if global to fewer and fewer things, until we can eventually pass it into an init function.

videoWrapper is used by three things. The manageCover code, the onYouTubeIframeAPIReady function, and the iife at the end of the code.

To start with, we can remove videoWrapper and move it into each of those three places.

// const videoWrapper = document.querySelector(".video-wrapper");

(function manageCover() {
  "use strict";
  const videoWrapper = document.querySelector(".video-wrapper");
...
function onYouTubeIframeAPIReady() {
  const videoWrapper = document.querySelector(".video-wrapper");
...
(function iife() {
  "use strict";

  const videoWrapper = document.querySelector(".video-wrapper");

We have now turned one big problem into three smaller problems. We now need to remove videoWrapper from each of those places we put it into. I’ll start with the bottom set of code.

We want to add an init method to the iife code, so that we can init the code by passing videoWrapper to the init function.

We first need to assign the iife to a variable to achieve that. As the code manages the playing of the video, I’ll call it managePlayer.

const managePlayer = (function iife() {

We can move the code at the end of the function into an init function, and immediately call it afterwards as a way of testing that things still work.

  function init() {
    const cover = videoWrapper.querySelector(".jacket");
    cover.addEventListener("click", coverClickHandler);
  }
  init();

Instead of invoking the init function there, we should return it from the iife code so that we can instead call the init method.

  function init() {
    const cover = videoWrapper.querySelector(".jacket");
    cover.addEventListener("click", coverClickHandler);
  }
  return {
    init
  };
}());
managePlayer.init();

We can now make the videoWrapper variable capable of being updated by putting it into a config object.

const managePlayer = (function iife() {
  "use strict";

  const config = {
    videoWrapper: document.querySelector(".video-wrapper")
  };
...
  function coverClickHandler(evt) {
    const video = config.videoWrapper.querySelector(".wrap");
...

When we initialize managePlayer, we want it to update that videoWrapper property.

  const config = {
    videoWrapper: null
  };
..
  function init(wrapper) {
    config.videoWrapper = wrapper;
...
managePlayer.init(document.querySelector(".video-wrapper"));

That’s one of the 3 sets of code updated. You’ll notice that it’s all done in small steps, so that we achieve our goal of using the init function to setup the video wrapper variable, while still ensuring that the code keeps on working throughout the update.

https://jsfiddle.net/wnkv1Lpq/

The other two sets of code still need to be updated, but it’s about as easy to to do them too.

1 Like

The onYouTubeIframeAPIReady function is the second part of the code to be updated.

First of all that shouldn’t be hanging out in the middle of nowhere. It should instead be inside of the videoPlayer code. We just need to also make that function fully available so that the youtube player can find it.

const videoPlayer = (function makeVideoPlayer() {
...
  function onYouTubeIframeAPIReady() {
    const videoWrapper = document.querySelector(".video-wrapper");
    const cover = videoWrapper.querySelector(".jacket");
    const wrapper = cover.parentElement;
    const frameContainer = videoWrapper.querySelector(".video");
    videoPlayer.addPlayer(frameContainer);
  }
...
  window.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady;
  
  return {
    addPlayer,
    play
  };

We can now move that window assignment into an init function.

  function init() {
    window.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady;
  }
  
  return {
    addPlayer,
    play,
    init
  };
}());
videoPlayer.init();

and while we’re at it, we should put the iframe script loader code into a function so that init can run that too.

  function loadIframeScript() {
    const tag = document.createElement("script");
    tag.src = "https://www.youtube.com/iframe_api";
    const firstScriptTag = document.getElementsByTagName("script")[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  }
...
  function init() {
    loadIframeScript();
    window.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady;
  }

There are two things that still need to be done. One is to move the call to videoPlayer.init into the managePlayer code. The other is to pass videoWrapper to the videoPlayer init code.

First, we move the call to the videoPlayer init method, into the managePlayer init code.

  return {
    addPlayer,
    play,
    init
  };
}());
// videoPlayer.init();
...
const managePlayer = (function iife() {
  ...
  function init(videoWrapper) {
    config.videoWrapper = videoWrapper;

    videoPlayer.init();

    const cover = videoWrapper.querySelector(".jacket");
    cover.addEventListener("click", coverClickHandler);
  }

We can now pass videoWrapper to the videoPlayer unit code.

  function init(videoWrapper) {
    config.videoWrapper = videoWrapper;

    videoPlayer.init(videoWrapper);

As a reminder, the config.videoWrapper is for managePlayer code that sets up an event handler and need to know about videoWrapper when that event handler gets triggered.

With videoPlayer.init, we could use either config.videoWrapper or just videoWrapper, and I’ve chosen to use just videoWrapper.

We can now get that info from the videoPlayer init and use it to prepare the iframeReady code.

  function init(videoWrapper) {
    loadIframeScript();
    window.onYouTubeIframeAPIReady = prepareIframeReady(videoWrapper);
  }

All we need to do now is to return a nested function that calls the onYouTubeIframeAPIReady function.

  function prepareIframeReady(videoWrapper) {
    return function closureWrapper() {
      onYouTubeIframeAPIReady(videoWrapper);
    };
  }

That way, a technique called closure allows the videoWrapper variable to be remembered, so that it can be used when youtube calls the function.

We can now remove videoWrapper from the

  function onYouTubeIframeAPIReady(videoWrapper) {
    // const videoWrapper = document.querySelector(".video-wrapper");
    ...
  }

https://jsfiddle.net/wnkv1Lpq/1/

And that is the second set of code updated to work more appropriately with the videoWrapper variable.

1 Like

The third and last set of code that uses videoWrapper is right at the top, in the manageCover code.

Let’s deal with this one more directly, by attempting to push the videoWrapper variable into a manageCover init function.

First, in the managePlayer code we call the manageCover init function.

const managePlayer = (function iife() {
  ...
  function init(videoWrapper) {
    config.videoWrapper = videoWrapper;

    manageCover.init(videoWrapper);
    videoPlayer.init(videoWrapper);

But we don’t have a manageCover init function. Let’s make one.

const manageCover = (function makeManageCover() {
  ...
  function init() {
    const cover = videoWrapper.querySelector(".jacket");
    cover.addEventListener("click", coverClickHandler);
  }
  return {
    init
  };
}());

The manageCover init method needs to accept that videoWrapper variable:

  function init(videoWrapper) {
    const cover = videoWrapper.querySelector(".jacket");
    cover.addEventListener("click", coverClickHandler);
  }

And we can now remove the videoWrapper that we placed at the top of the manageCover code.

const manageCover = (function makeManageCover() {
  "use strict";

  // const videoWrapper = document.querySelector(".video-wrapper");

That’s all of the changes that are needed get it working without videoWrapper being at the top, as well as ensuring that we don’t have multiple copies of videoWrapper references being defined all over the place.

https://jsfiddle.net/wnkv1Lpq/2/

2 Likes