Search code examples
javascriptyoutubeframeworksgloballazy-evaluation

Accessing lazy global variable from API in stimulus framework


I'm trying to make a Stimulus controller which would access global Youtube Player API variable

Youtube Player API is lazy loading - it's class is loaded asynchronously

// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

The problem is, that Stimulus doesn't want to access YT variable.

I tried to create a function, that loads script, and after it, runs given function, and imports it to stimulus

export function loadScript (url, callback) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    if (script.readyState) { // only required for IE <9
        script.onreadystatechange = function () {
            if (script.readyState === "loaded" || script.readyState === "complete") {
                script.onreadystatechange = null;
                callback();
            }
        };
    } else { //Others
        script.onload = function () {
            callback();
        };
    }

    script.src = url;
    document.getElementsByTagName("head")[0].appendChild(script);
}

But as you can see, it only creates new script object, so Stimulus still won't be able to access it.

Is there a way in which I could make Stimulus access loaded script, and variables that are declared in it?

Here is a full controller code (don't mind bad practices, it's just a fast try):

import {
    Controller
} from 'stimulus';

import $ from 'jquery';

export default class extends Controller {
    static targets = ['collapseButton', 'player']

    connect() {
        this.initPlayer();
    }

    initPlayer() {
        let player = $('#youtubePlayer');

        // Code taken straight from https://developers.google.com/youtube/iframe_api_reference?hl=pl
        player = new YT.Player(player, {
            videoId: 'Va297erJjJ4',
            events: {
                'onReady': onPlayerReady,
                'onStateChange': onPlayerStateChange
            }
        });

        function onPlayerReady(event) {
            event.target.playVideo();
        }

        var done = false;

        function onPlayerStateChange(event) {
            if (event.data == YT.PlayerState.PLAYING && !done) {
                setTimeout(stopVideo, 6000);
                done = true;
            }
        }

        function stopVideo() {
            player.stopVideo();
        }
    }
}
<div class="youtube-player-modal" data-controller="youtubeplayer">
    <div id="youtubePlayer"></div>
</div>

Solution

  • I wrote a solution to your problem here, there you must first call the ready function and then give the callback. https://codesandbox.io/s/funny-browser-n3m3j?file=/src/Player.js

    // loadScript.js
    import Player from "./Player";
    
    function loadScript(url, callback) {
      var script = document.createElement("script");
      script.type = "text/javascript";
      if (script.readyState) {
        // only required for IE <9
        script.onreadystatechange = function () {
          if (script.readyState === "loaded" || script.readyState === "complete") {
            script.onreadystatechange = null;
            callback();
          }
        };
      } else {
        //Others
        script.onload = function () {
          callback();
        };
      }
    
      script.src = url;
      document.getElementsByTagName("head")[0].appendChild(script);
    }
    
    loadScript("https://www.youtube.com/iframe_api", new Player().initPlayer);
    
    
    // Player.js
    import { Controller } from "stimulus";
    
    import $ from "jquery";
    
    export default class extends Controller {
      static targets = ["collapseButton", "player"];
    
      connect() {
        this.initPlayer();
      }
    
      initPlayer() {
        let player = $("#youtubePlayer");
        window.YT.ready(function () {
          player = new window.YT.Player("youtubePlayer", {
            height: "360",
            width: "640",
            videoId: "Va297erJjJ4",
            events: {
              onReady: onPlayerReady,
              onStateChange: onPlayerStateChange
            }
          });
        });
    
        function onPlayerReady(event) {
          event.target.playVideo();
        }
    
        var done = false;
    
        function onPlayerStateChange(event) {
          if (event.data === window.YT.PlayerState.PLAYING && !done) {
            setTimeout(stopVideo, 6000);
            done = true;
          }
        }
    
        function stopVideo() {
          player.stopVideo();
        }
      }
    }
    <!DOCTYPE html>
    <html>
      <head>
        <title>Parcel Sandbox</title>
        <meta charset="UTF-8" />
      </head>
    
      <body>
        <div
          class="youtube-player-modal"
          data-controller="youtubeplayer"
        >
          <div id="youtubePlayer"></div>
        </div>
    
        <script src="src/loadScript.js"></script>
      </body>
    </html>