Remove manageCover from the managePlayer code

Commented code removed out: https://jsfiddle.net/h2a8ekmv/

Work can now begin on Step 4.

Move parts of the functions back in to manageCover and managePlayer

function createPlayer(videoWrapper, playerOptions = {}) {
    const video = videoWrapper.querySelector(".video");
    const options = combinePlayerOptions(defaults, playerOptions);
    return videoPlayer.addPlayer(video, options);
  }

  return {
    createPlayer,
    show
  };
}());

function onYouTubeIframeAPIReady() {

  function createCoverClickHandler(playerOptions) {
    return function coverClickHandler(evt) {
      const cover = evt.currentTarget;
      const wrapper = cover.nextElementSibling;
      managePlayer.show(wrapper);
      const player = managePlayer.createPlayer(wrapper, playerOptions);
      wrapper.player = player;
    };
  }

  function addPlayer(coverSelector, playerOptions) {
    const clickHandler = createCoverClickHandler(playerOptions);
    manageCover.addCoverHandler(coverSelector, clickHandler);
  }

  addPlayer(".playa", {});
  addPlayer(".playb", {});
  addPlayer(".playc", {});
  addPlayer(".playd", {});
  addPlayer(".playe", {
    playerVars: {
      playlist: "0dgNc5S8cLI,mnfmQe8Mv1g,-Xgi_way56U,CHahce95B1g"
    }
  });
  addPlayer(".playf", {});
  addPlayer(".playg", {});
  addPlayer(".playh", {});
  addPlayer(".playi", {});

Here are the steps that were initially conceived.

Step 1: Change needed private functions to be public
Step 2: Copy unsuitable functions to onYouTubeIframeAPIReady
Step 3: Rename managePlayer.add to use the addPlayer function in onYouTubeIframeAPIReady
Step 4: Move parts of the functions back in to manageCover and managePlayer
Step 5: Rename addPlayer in onYouTubeIframeAPIReady back to managePlayer.add
Step 6: Change public functions that no longer need to be public back to private
Step 7: Remove as much of the onYouTubeIframeAPIReady functions as we can

Step 4 Move parts of the functions back in to manageCover and managePlayer

Step 4 Part 1, Examining the problem

We don’t touch the code for a while and instead try to spend about 90% of the time thinking and considering different options before even touching the code.

A separation between manageCover and managePlayer needs to occur. The manageCover code must know nothing about managePlayer, and the managePlayer code must know nothing about manageCover.

Right now we have some code that uses aspects of both manageCover and managePlayer. To help visualize this, I’ve used orange to highlight cover code, and blue to highlight player code. The red highlight is where a conflict of both occurs.

image

It’s quite clear that the createCoverClickHandler function should be split up, but just putting some of it in manageCover and some of it in managePlayer is not entirely suitable, as we need to connect the two together in a way that doesn’t break the separation of concerns in regard to manageCover and managePlayer.

With the addPlayer function, as the addCoverHandler code is already in manageCover, there doesn’t seem to be much else from there that can be added to wither manageCover or managePlayer.

Bearing in mind that writing code right now is the completely wrong thing to do, I’ll sleep on a few different options and ponder on the implications of them before doing anything else with this step.

1 Like

Step 4 Part 2: Extracting player code

It seems to be quite clear that in the https://jsfiddle.net/h2a8ekmv/ code, we need to separate apart the cover code from the player code in the createCoverClickHandler function.

Normally that would be as easy as returning a value from the cover code and passing that value to the player code. We have a problem here though because this is a click handler that’s being returned instead.

When we remove the player code we won’t need playerOptions as a function parameter. Instead of having playerOptions there, we could do some other preparation and have a callback there instead. That way the coverClickHandler can run that callback code, and it doesn’t need to know anything about the player, as that can all be handled by the callback code instead.

Looking at the manageCover code I see that we already have an addCoverHandler function that does exactly that, which is already being used in the addPlayer code.

I think that I can already now visualize the structure of the new code, but I’ll do it step by step here to try and avoid misunderstandings.

First we move the player code shown in blue, out to a separate function. We don’t have to give the function a proper name yet, we can just put the code into a badly named function and get the code working. After it’s working in the function we will then have less distractions and can think properly about naming it after that.

      const wrapper = cover.nextElementSibling;
      function extractedCode(wrapper) {
          managePlayer.show(wrapper);
          const player = managePlayer.createPlayer(wrapper, playerOptions);
          wrapper.player = player;
      }
      extractedCode(wrapper);

Now that the extracted code is working as a function, we can think more clearly about what to call it. As the code is using the wrapper to show the wrapper and create a player on that wrapper, a name of initPlayer seems to fit well there.

      const wrapper = cover.nextElementSibling;
      function initPlayer(wrapper, playerOptions) {
          managePlayer.show(wrapper);
          const player = managePlayer.createPlayer(wrapper, playerOptions);
          wrapper.player = player;
      }
      initPlayer(wrapper, playerOptions);

That initPlayer function can now be properly moved into the managePlayer code, with a public method being added so that the coverClickHandler code can access that addPlayer function.

    function initPlayer(wrapper, playerOptions) {
        managePlayer.show(wrapper);
        const player = managePlayer.createPlayer(wrapper, playerOptions);
        wrapper.player = player;
    }

    return {
        init: initPlayer,
        createPlayer,
        show
    };
...
        return function coverClickHandler(evt) {
            const cover = evt.currentTarget;
            const wrapper = cover.nextElementSibling;
            managePlayer.init(wrapper, playerOptions);
        };

And now that initPlayer is inside of managePlayer where it’s supposed to be, we can remove the public methods for show and createPlayer.

    function initPlayer(wrapper, playerOptions) {
        // managePlayer.show(wrapper);
        show(wrapper);
        // const player = managePlayer.createPlayer(wrapper, playerOptions);
        const player = createPlayer(wrapper, playerOptions);
        wrapper.player = player;
    }

    return {
        init: initPlayer
        // createPlayer,
        // show
    };

Next up is to get that wrapper out of the coverClickHandler function, ideally using a callback function so that the coverClickHandler code can be entirely removed. I’ll attempt to take care of that in my next post.

1 Like

Step 4 Part 3: Extracting clickHandler code

Here is the updated code that we are working with in the onYouTubeIframeAPIReady function:

    function createCoverClickHandler(playerOptions) {
        return function coverClickHandler(evt) {
            const cover = evt.currentTarget;
            const wrapper = cover.nextElementSibling;
            managePlayer.init(wrapper, playerOptions);
        };
    }

    function addPlayer(coverSelector, playerOptions) {
        const clickHandler = createCoverClickHandler(playerOptions);
        manageCover.addCoverHandler(coverSelector, clickHandler);
    }

As we are using the addCoverHandler function in manageCover, there doesn’t seem to be anything else useful to add to the manageCover code. As a result, names relating to the cover don’t seem to be of any further use in the createCoverClickHandler code. Let’s work at removing cover from that code.

The function names are a good first step when it comes to removing cover:

    // function createCoverClickHandler(playerOptions) {
    function createClickHandler(playerOptions) {
        // return function coverClickHandler(evt) {
        return function clickHandler(evt) {
            const cover = evt.currentTarget;
            const wrapper = cover.nextElementSibling;
            managePlayer.init(wrapper, playerOptions);
        };
    }

    function addPlayer(coverSelector, playerOptions) {
        // const clickHandler = createCoverClickHandler(playerOptions);
        const clickHandler = createClickHandler(playerOptions);
        manageCover.addCoverHandler(coverSelector, clickHandler);
    }

The only cover-related thing remaining in the clickHandler function is the cover variable. Let’s move that out to the addPlayer function, so that we can pass wrapper to createClickHandler instead.

    // function createClickHandler(playerOptions) {
    function createClickHandler(wrapper, playerOptions) {
        return function clickHandler(evt) {
            // const cover = evt.currentTarget;
            // const wrapper = cover.nextElementSibling;
            managePlayer.init(wrapper, playerOptions);
        };
    }

    function addPlayer(coverSelector, playerOptions) {
        const cover = document.querySelector(coverSelector);
        const wrapper = cover.nextElementSibling;
        // const clickHandler = createClickHandler(playerOptions);
        const clickHandler = createClickHandler(wrapper, playerOptions);
        manageCover.addCoverHandler(coverSelector, clickHandler);
    }

It now makes good sense to move the createClickHandler function back into the managePlayer code.

    function createClickHandler(wrapper, playerOptions) {
        return function clickHandler(evt) {
            managePlayer.init(wrapper, playerOptions);
        };
    }

    return {
        createClickHandler,
        init: initPlayer
    };
...
function onYouTubeIframeAPIReady() {

    // function createClickHandler(wrapper, playerOptions) {
    //     return function clickHandler(evt) {
    //         managePlayer.init(wrapper, playerOptions);
    //     };
    // }

    function addPlayer(coverSelector, playerOptions) {
        const cover = document.querySelector(coverSelector);
        const wrapper = cover.nextElementSibling;
        // const clickHandler = createClickHandler(wrapper, playerOptions);
        const clickHandler = managePlayer.createClickHandler(wrapper, playerOptions);
        manageCover.addCoverHandler(coverSelector, clickHandler);
    }

And because that createClickHandler is now in the right place, we can remove the public method pointing to initPlayer.

    function createClickHandler(wrapper, playerOptions) {
        return function clickHandler(evt) {
            // managePlayer.init(wrapper, playerOptions);
            initPlayer(wrapper, playerOptions);
        };
    }

    return {
        createClickHandler
        // init: initPlayer
    };

We just have the addPlayer code in the onYouTubeIframeAPIReady function to deal with now, which I’ll take care of in the next post.

1 Like

Step 4 part 4: Extracting playerAdder

Here is what remains of the addPlayer code in the onYouTubeIframeAPIReady function, using color-coding that shows:

  • yellow for cover code
  • blue for player code
  • red for conflicts across the two

2021-10-19

There are several problems seen there.

  • The top red line involves both the cover and the player. It’s not possible to remove or rename coverSelector as that goes directly to the manageCover code at the bottom of the function.
  • The first yellow line is a long way away from the other yellow line, and there doesn’t seem to be any reasonable way to bring them together by moving away the code that’s between them.
  • The second red line is also a conflict, because it’s dealing with the cover, but is using nextElementSibling to gain a reference to the player wrapper.

A suitable solution presents itself though when it comes to the coverSelector. We don’t have to call it coverSelector. Instead, if we accept that the player expects to reference things in terms of the wrapper, we can extract out some of that addPlayer code to a separate function:

We can’t call that function addPlayer because there’s already one with that same name in the onYouTubeIframeAPIReady function, but there’s a bigger reason why we can’t call it something like that. When we call it add or addPlayer, there is an expectation that the player gets added to something, and it doesn’t. Instead you need to run the returned function, either directly or by assigning it to an event handler.

What is really happening here is that a callback function is being returned. It’s not an addPlayer function because it doesn’t add a player. Instead, it’s a playerAdder callback because you must run the returned function to actually add the player. We can call the function playerAdder instead.

    function playerAdder(wrapper, playerOptions) {
        const clickHandler = managePlayer.createClickHandler(wrapper, playerOptions);
        return clickHandler;
    }

We can now move that playerAdder function back in to the managePlayer code, but we can’t use add or addPlayer for its public method name. When we use add, there is an expectation that something gets added, but that doesn’t occur with this code. You can run the playerAdder all day and nothing gets added. That’s because it only returns a function instead of adding anything. It is that returned function must later on be run to actually add the thing. The onYouTubeIframeAPIReady function adds a clickHandler to the page so calling that function addPlayer is suitable, but the managePlayer code doesn’t actually add a player. Instead it needs to be run later on to add the player. That’s why the names of add and addPlayer aren’t suitable, and another name such as adder is much more suitable for the name instead.

    function playerAdder(wrapper, playerOptions) {
        const clickHandler = createClickHandler(wrapper, playerOptions);
        return clickHandler;
    }

    return {
        adder: playerAdder
    };

function onYouTubeIframeAPIReady() {

    // function playerAdder(wrapper, playerOptions) {
    //     const clickHandler = managePlayer.createClickHandler(wrapper, playerOptions);
    //     return clickHandler;
    // }

    function addPlayer(coverSelector, playerOptions) {

Lastly with the player code, the clickHandler shouldn’t be there either. The player code has no business knowing about click handlers in regard to the cover. Instead it’s a callback function that’s being returned, which later on is used by the cover code as a click handler.

Here are the current clickhandler and player adder functions:

    function createClickHandler(wrapper, playerOptions) {
        return function clickHandler() {
            initPlayer(wrapper, playerOptions);
        };
    }

    function playerAdder(wrapper, playerOptions) {
        const clickHandler = createClickHandler(wrapper, playerOptions);
        return clickHandler;
    }

We need to remove that clickHandler information from the player code, and just refer to it as a callback instead.

    function createCallback(wrapper, playerOptions) {
        return function callback() {
            initPlayer(wrapper, playerOptions);
        };
    }

    function playerAdder(wrapper, playerOptions) {
        const callback = createCallback(wrapper, playerOptions);
        return callback;
    }

Because playerAdder is basically passing through the function parameters to the click handler, we don’t need that createCallback code anymore and can move it into the playerAdder code.

    // function createCallback(wrapper, playerOptions) {
    //     return function callback() {
    //         initPlayer(wrapper, playerOptions);
    //     };
    // }

    function playerAdder(wrapper, playerOptions) {
        // const callback = managePlayer.createCallback(wrapper, playerOptions);
        return function callback() {
            initPlayer(wrapper, playerOptions);
        };
        // return callback;
    }

We now have a better understanding of that playerAdder code. It doesn’t actually add anything when it runs. Instead it delays that adding by returning a callback function, which gets run later on.

The onYouTubeIframeAPIReady function also needs updating to remove all ideas of click handler until we actually get to the cover code.

function onYouTubeIframeAPIReady() {

    function addPlayer(coverSelector, playerOptions) {
        const cover = document.querySelector(coverSelector);
        const wrapper = cover.nextElementSibling;
        // const clickHandler = managePlayer.adder(wrapper, playerOptions);
        const callback = managePlayer.adder(wrapper, playerOptions);
        manageCover.addCoverHandler(coverSelector, callback);
    }

The last part of this Step 4 work is some final tidying up of the onYouTubeIframeAPIReady addPlayer code in regard to the nextElementSibling that helps to resolve more conflicts between cover and player, which I’ll get to in the next post.

1 Like

Step 4 Part 5: The wrapper

Let’s now take a look in at the wrapper in onYouTubeIframeAPIReady’s addPlayer function.

    function addPlayer(coverSelector, playerOptions) {
        const cover = document.querySelector(coverSelector);
        const wrapper = cover.nextElementSibling;
        const callback = managePlayer.adder(wrapper, playerOptions);
        manageCover.addCoverHandler(coverSelector, callback);
    }

Here is the cover and the wrapper from the HTML code.

  <div class="container with-curtain">
    <button class="playa thePlay" type="button" aria-label="Open">
      ...
    </button>
    <div class="inner-container curtain curtain1">
      <div class="ratio-keeper">
        <div class="wrapa">
          <div class="video video-frame" data-id="CHahce95B1g"></div>
        </div>
        ...
      </div>

The addPlayer code is not actually getting the wrapper, which is the “wrapa” class. Instead the addPlayer code is getting the curtain element.

Is cover.nextElementSibling supposed to be getting the wrapper instead? Does the code work when it’s actually the wrapper that’s being used? We can find out, by deliberately making sure that it is the wrap element being obtained.

    function addPlayer(coverSelector, playerOptions) {
        const cover = document.querySelector(coverSelector);
        const wrapper = document.querySelector(".wrapa");

Good, that still works with the first video. Having multiple similar classnames such as wrapa and wrapb and wrapc is an extremely bad idea, as it multiplies the amount of work that needs to be done. Looking at the HTML code there is no wrapb and wrapc, instead wrapa is used throughout in many different places, which leads to even more confusion.

Renaming wrapa to just wrap, and updating the code so that the wrap class is used to find the wrapper is what needs to be done.

Renaming wrapa to be just wrap throughout all of the HTML code gives us HTML code that looks like this:

      <div class="ratio-keeper">
        <div class="wrap">

We can now properly update addPlayer to get that wrap element.

By going up a parent from the cover we can do a general search down below for a wrap element.

    function addPlayer(coverSelector, playerOptions) {
        const cover = document.querySelector(coverSelector);
        // const parent = cover.parentElement;
        // const wrapper = cover.nextElementSibling;
        const wrapper = parent.querySelector(".wrap");
        const callback = managePlayer.adder(wrapper, playerOptions);
        manageCover.addCoverHandler(coverSelector, callback);
    }

Now that we have a reliable way to get the wrapper, that doesn’t involve anything to do with the cover, we can move that “wrap” search into the playerAdder function.

    // function playerAdder(wrapper, playerOptions) {
    function playerAdder(parent, playerOptions) {
        const wrapper = parent.querySelector(".wrap");
        return function callback() {
            initPlayer(wrapper, playerOptions);
        };
    }
...
    function addPlayer(coverSelector, playerOptions) {
        // const cover = document.querySelector(coverSelector);
        // const wrapper = parent.querySelector(".wrap");
        // const callback = managePlayer.adder(wrapper, playerOptions);
        const parent = document.querySelector(coverSelector).parentElement;
        const callback = managePlayer.adder(parent, playerOptions);
        manageCover.addCoverHandler(coverSelector, callback);
    }

Because we are using addPlayer with a cover selector and player options, that’s about as simple as we’re going to get with that code there. The addPlayer code doesn’t belong in either the manageCover or the managePlayer code, so that can stay where it is as a helper function inside of onYouTubeIframeAPIReady.

That’s a lot of detail for Step 4, so let me know how you’re coming along with it.

1 Like

Step 4 Part 2: Extracting player code

I did that here: https://jsfiddle.net/eg12cp3y/

function initPlayer(wrapper, playerOptions) {
    // managePlayer.show(wrapper);
    show(wrapper);
    // const player = managePlayer.createPlayer(wrapper, playerOptions);
    const player = createPlayer(wrapper, playerOptions);
    wrapper.player = player;
  }

  return {
    init: initPlayer,
    // createPlayer,
    // show
  };
}());

function onYouTubeIframeAPIReady() {

  function createCoverClickHandler(playerOptions) {
    return function coverClickHandler(evt) {
      const cover = evt.currentTarget;
      const wrapper = cover.nextElementSibling;
      managePlayer.init(wrapper, playerOptions);
    };
  }

Step 4 Part 3: Extracting clickHandler code

I did that here: https://jsfiddle.net/389j51ku/

function createClickHandler(wrapper, playerOptions) {
    return function clickHandler(evt) {
      // managePlayer.init(wrapper, playerOptions);
      initPlayer(wrapper, playerOptions);
    };
  }

  function initPlayer(wrapper, playerOptions) {
    // managePlayer.show(wrapper);
    show(wrapper);
    // const player = managePlayer.createPlayer(wrapper, playerOptions);
    const player = createPlayer(wrapper, playerOptions);
    wrapper.player = player;
  }

  return {
    createClickHandler
    // init: initPlayer
  };
}());

function onYouTubeIframeAPIReady() {

  function addPlayer(coverSelector, playerOptions) {
    const cover = document.querySelector(coverSelector);
    const wrapper = cover.nextElementSibling;
    // const clickHandler = createClickHandler(wrapper, playerOptions);
    const clickHandler = managePlayer.createClickHandler(wrapper, playerOptions);
    manageCover.addCoverHandler(coverSelector, clickHandler);
  }

Step 4 part 4: Extracting playerAdder

I did that here: https://jsfiddle.net/2c9wqapu/

function createCallback(wrapper, playerOptions) {
    return function callback() {
      initPlayer(wrapper, playerOptions);
    };
  }

  function playerAdder(wrapper, playerOptions) {
    // const callback = managePlayer.createCallback(wrapper, playerOptions);
    return function callback() {
      initPlayer(wrapper, playerOptions);
    };
    // return callback;
  }

  function initPlayer(wrapper, playerOptions) {
    // managePlayer.show(wrapper);
    show(wrapper);
    // const player = managePlayer.createPlayer(wrapper, playerOptions);
    const player = createPlayer(wrapper, playerOptions);
    wrapper.player = player;
  }

  return {
    createCallback,
    adder: playerAdder
    // init: initPlayer
  };
}());

function onYouTubeIframeAPIReady() {

  function addPlayer(coverSelector, playerOptions) {
    const cover = document.querySelector(coverSelector);
    const wrapper = cover.nextElementSibling;
    // const clickHandler = managePlayer.adder(wrapper, playerOptions);
    const callback = managePlayer.adder(wrapper, playerOptions);
    manageCover.addCoverHandler(coverSelector, callback);
  }

Step 4 Part 5: The wrapper

I did that here: https://jsfiddle.net/wdnv8567/

Cannot read properties of null (reading ‘classList’)"

function createCallback(wrapper, playerOptions) {
    return function callback() {
      initPlayer(wrapper, playerOptions);
    };
  }

  // function playerAdder(wrapper, playerOptions) {
  function playerAdder(parent, playerOptions) {
    const wrapper = parent.querySelector(".wrap");
    return function callback() {
      initPlayer(wrapper, playerOptions);
    };
  }

  function initPlayer(wrapper, playerOptions) {
    // managePlayer.show(wrapper);
    show(wrapper);
    // const player = managePlayer.createPlayer(wrapper, playerOptions);
    const player = createPlayer(wrapper, playerOptions);
    wrapper.player = player;
  }

  return {
    createCallback,
    adder: playerAdder
    // init: initPlayer
  };
}());

function onYouTubeIframeAPIReady() {

  function addPlayer(coverSelector, playerOptions) {
    // const cover = document.querySelector(coverSelector);
    // const wrapper = parent.querySelector(".wrap");
    // const callback = managePlayer.adder(wrapper, playerOptions);
    const parent = document.querySelector(coverSelector).parentElement;
    const callback = managePlayer.adder(parent, playerOptions);
    manageCover.addCoverHandler(coverSelector, callback);
  }

Step 4 completed: https://jsfiddle.net/50wsbjvz/

Are there instructions on how to do Step 5?

Rename addPlayer in onYouTubeIframeAPIReady back to managePlayer.add

After doing that, the code does not work.

I am thinking, there is more to it than just renaming addPlayer back to managePlayer.add. There would need to be other stuff that would need to be done also for the code to work.

Because lots of changes were made in Step 4, would I be doing something different in Step 5, or, just the instructions would need to be made for Step 5 so that managePlayer.add is able to work?

Is the plan still to Rename addPlayer in onYouTubeIframeAPIReady back to managePlayer.add?

If yes, what would the instructions be to do that?

The list of steps was written up before details of the actual work was done. Things are liable to change as additional things are learned. As Sun Tzu once said: “No plan survives first contact with the enemy.”

Here’s an update to that set of steps:

Step 1: Change needed private functions to be public
Step 2: Copy unsuitable functions to onYouTubeIframeAPIReady
Step 3: Rename managePlayer.add to use the addPlayer function in onYouTubeIframeAPIReady
Steps 1-3: Achieved as 8 easy steps
Step 4: Move parts of the functions back in to manageCover and managePlayer

Step 5: Rename addPlayer in onYouTubeIframeAPIReady back to managePlayer.add
Step 6: Change public functions that no longer need to be public back to private
Step 7: Remove as much of the onYouTubeIframeAPIReady functions as we can

Step 5 has been achieved in terms of playerAdder, and Steps 6 and 7 have already been done by cleaning up as we go. So we’re all done in this regard.

1 Like

We can now update the UML class to remove the link that was between managePlayer and manageCover.

Each of those manage sections is now self-contained. The updated code helps to demonstrate the proper way that information is passed from one area to another, in this case using the callback.

function onYouTubeIframeAPIReady() {
    function addPlayer(coverSelector, playerOptions) {
        const parent = document.querySelector(coverSelector).parentElement;
        const callback = managePlayer.adder(parent, playerOptions);
        manageCover.addCoverHandler(coverSelector, callback);
    }
...
}

The onYouTubeIframeAPIReady function acts as a controller. The manageCover code need information from managePlayer, so information is returned from the managePlayer code, that being the callback function, and it’s given to the mangeCover code.

That is a much more reliable and consistent way of programming, that lets an incredibly rich range of things occur, without increasing the overall complexity.

Is more being done to the code here? https://jsfiddle.net/50wsbjvz/

Wait, maybe you are just updating the UML Diagram with everything that was done in the code.

Which was last changed in post #2

First UML Diagram was posted here: post #62

Step 4 part 4: Extracting playerAdder

This function gets deleted: https://jsfiddle.net/ma4g8qLy/

I forgot to do that all this time, and I never knew about it until now.

    function createCallback(wrapper, playerOptions) {
        return function callback() {
            initPlayer(wrapper, playerOptions);
        };
    }

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.