It is possible to remove all of the other sets of button code, but doing so must be performed in a more careful manner.
Click on the play button twice, it disappears:
Thatâs happening because âactiveâ is being used in the CSS styles, when âactivatedâ must be used instead.
Got it:
Both versions are done now.
What are the two versions?
Buttons C & E are different.
You have a lot of duplication in your code now.
I want to help you to remove some of that annoying duplication, which will also make it easier for you to manage your code.
The first thing you can to do help remove duplication is to rename hidea/hideb/hidec/⌠to just âhideâ in your HTML, CSS, and JavaScript.
That will leave you with several duplicate lines, such as:
el.classList.remove("hide");
el.classList.remove("hide");
el.classList.remove("hide");
el.classList.remove("hide");
el.classList.remove("hide");
el.classList.remove("hide");
You can now remove all of those duplicates, leaving you with just:
el.classList.remove("hide");
After other adjustments have been made, to the initial/play/pause/speaker class names, and adding a âplayButtonâ class to each button (for the purpose of scripting), we are now ready to combine two scripts together.
The getButtonContainer() and pauseAllButtons() functions can be made the same across all of the code by using â.pauseButtonâ instead of pauseButtona, pauseButtonb, etcâŚ
The upTo() statements can all use the generic â.playButtonâ classname too.
Other things that the diff between code A and code B reveals, is that code B uses playButton when it should use togglePlayButton.
We can now remove code A, and have code B handle things for both buttons. Code A has no initial overlay, so we check if that initial element exists before using that.
playButtons.forEach(function (button) {
if (button.querySelector(".initial")) {
button.addEventListener("click", initialOverlayClickHandler);
} else {
button.addEventListener("click", playButtonClickHandler);
}
});
And we now have one set of code that controls both the A and B buttons. https://jsfiddle.net/zfs5qqq9/230/
We can now extend that further to the button C.
Comparing the A+B code with C, thereâs a lot of formatting differences between them. Running all the code through JSBeautifier makes it much easier to see in DiffChecker that thereâs very little thatâs different about both sets of code.
Code C is missing code that deals with the initial overlay, which is fine, and when âactiveâ and âactivatedâ from code A+B are added to the C buttons, nothing will happen because there are no class declarations for the C button that will be affected by the âactiveâ and âactivatedâ class names.
We do need to add mouseover and mouseout events to the A+B code, which results in some duplication between the initialOverlayClickHandler and the other code further below that adds events, so weâll create a new function to add the playbutton handlers.
function addPlayButtonHandlers(button) {
button.addEventListener("click", playButtonClickHandler);
button.addEventListener("mouseover", playButtonMouseoverHandler);
button.addEventListener("mouseout", playButtonMouseoutHandler);
}
function initialOverlayClickHandler(evt) {
var button = getButtonContainer(evt.target);
hideInitialOverlay(button);
button.removeEventListener("click", initialOverlayClickHandler);
// button.addEventListener("click", playButtonClickHandler);
// button.addEventListener("mouseover", playButtonMouseoverHandler);
// button.addEventListener("mouseout", playButtonMouseoutHandler);
addPlayButtonHandlers(button);
// ...
}
// ...
if (button.querySelector(".initial")) {
button.addEventListener("click", initialOverlayClickHandler);
} else {
// button.addEventListener("click", playButtonClickHandler);
addPlayButtonHandlers(button);
}
Because of Button A, we donât want those mouseover/mouseout event handlers being assigned if there is no speaker icon in the button, so we can use an if statement to guard against that.
function addPlayButtonHandlers(button) {
button.addEventListener("click", playButtonClickHandler);
if (button.querySelector(".speaker")) {
button.addEventListener("mouseover", playButtonMouseoverHandler);
button.addEventListener("mouseout", playButtonMouseoutHandler);
}
}
And, the A+B code works properly once more with the A and B buttons.
We can now have that first set of code also support the C buttons.
var playButtons = document.querySelectorAll(".playButtona, .playButtonb, .playButtonc");
We now have one set of code that controls buttons A+B+C https://jsfiddle.net/zfs5qqq9/231/
When that code controls all of the buttons, we can replace those separate classnames with just the .playButton
class name instead. That canât occur though until the first set of code supports buttons, D, E, and F too.
Is this true?
Be aware that the class name .active can easily be confused with pseudo :active
.playButtona.active
The classname might be confused by a person, that reads â.activeâ as if it might be â:activeâ
If that really becomes a problem, you can rename âactiveâ to something else such as âplayingâ
Using DiffChecker to compare the A+B+C code to the D code, thereâs nothing extra in the D code that needs to be supported, so we can tell the A+B+C to support D too, and after removing the D code, everything all works.
var playButtons = document.querySelectorAll(".playButtona, .playButtonb, .playButtonc, .playButtond");
Code E has some significant differences though.
Code A+B+C+D (we can call the Main code now), uses getButtonContainer() and code E uses the upTo() function. We can combine those together nicely by having getButtonContainer() use the upTo() function.
function upTo(el, selector) {
while (el.matches(selector) === false) {
el = el.parentNode;
}
return el;
}
function getButtonContainer(el) {
return upTo(el, ".playButton");
// while (el.classList.contains("playButton") === false) {
// el = el.parentNode;
// }
// return el;
}
The links are the only other thing to be dealt with. Currently they being controlled by a separate âinactiveâ class, but they can just as easily be controlled by the âactivatedâ classname instead.
/* .linkse.inactive a { */
.linkse a {
display: none;
}
.linkse.activated a {
display: initial;
}
function hideInitialOverlay(button) {
var link = upTo(button, ".linkse");
// link.classList.remove("inactive");
hide(button.querySelector(".initial"));
button.classList.add("activated");
link.classList.add("activated");
}
// document.querySelector(".linkse").classList.add("inactive");
We can also rename linkse to just links, and things there are tidied up too.
The main code can now be updated, to add âactivatedâ to all link elements.
function getLinks(button) {
var links = upTo(button, ".links");
if (links && links.classList.contains("links")) {
return links;
}
}
// ...
function hideInitialOverlay(button) {
var links = getLinks(button);
hide(button.querySelector(".initial"));
if (links) {
links.classList.add("activated");
}
showPlayButton(button);
}
And because links canât be found from all buttons, the upTo function needs to be updated so that it can deal with not finding what itâs been asked for.
function upTo(el, selector) {
while (el.nodeName !== "HTML" && el.matches(selector) === false) {
el = el.parentNode;
}
if (el.nodeName !== "HTML") {
return el;
}
}
Button E isnât supposed to play when you first click on it either, so we can add an `initialPause" class to that button, and look for that from the initialOverlayClickHandler() function.
<div class="playButton playButtone initialPause">
function initialOverlayClickHandler(evt) {
// ...
addPlayButtonHandlers(button);
playButtonClickHandler(evt);
if (button.classList.contains("initialPause")) {
togglePlayButton(button);
}
}
That gives us main code that works with the Buttons A through to E https://jsfiddle.net/zfs5qqq9/234, and itâs just a matter of looking at the last button now.
The changes for button E have resulted in a minor issue, where playing some other button then clicking on button Eâs initial overlay, causes that other button to stop playing.
To fix that, button E needs to not play then pause when we first click on it.
function initialOverlayClickHandler(evt) {
// ...
addPlayButtonHandlers(button);
if (button.classList.contains("initialPause")) {
return;
}
playButtonClickHandler(evt);
// if (button.classList.contains("initialPause")) {
// togglePlayButton(button);
// }
}
And, the play button doesnât show, because the CSS needs to be fixed for the E button.
.playButtone.activated {
Clicking to remove the initial image doesnât trigger activated on the play button, so we need to check if the links are activated instead.
.links.activated .playButtone {
And everything now works properly with button E. https://jsfiddle.net/zfs5qqq9/235/
With the last button, when we update that main set of code to also handle the last button, the code is so capable now that no further changes are required.
var playButtons = document.querySelectorAll(".playButtona, .playButtonb, .playButtonc, .playButtond, .playButtone, .playButtonf");
Because all of the buttons are now being supported, we donât need to individually name them, and can just get all of the .playButton
elements.
var playButtons = document.querySelectorAll(".playButton");
And the JavaScript code for it all is:
(function iife() {
"use strict";
function show(el) {
el.classList.remove("hide");
}
function hide(el) {
el.classList.add("hide");
}
function upTo(el, selector) {
while (el.nodeName !== "HTML" && el.matches(selector) === false) {
el = el.parentNode;
}
if (el.nodeName !== "HTML") {
return el;
}
}
function getButtonContainer(el) {
return upTo(el, ".playButton");
}
function hideAllButtons(button) {
button.querySelectorAll(".initial, .play, .pause, .speaker").forEach(hide);
}
function getLinks(button) {
var links = upTo(button, ".links");
if (links && links.classList.contains("links")) {
return links;
}
}
function getPlay(button) {
return button.querySelector(".play");
}
function getPause(button) {
return button.querySelector(".pause");
}
function getSpeaker(button) {
return button.querySelector(".speaker");
}
function showPlayButton(button) {
var play = getPlay(button);
hideAllButtons(button);
show(play);
button.classList.remove("active");
}
function hideInitialOverlay(button) {
var links = getLinks(button);
hide(button.querySelector(".initial"));
if (links) {
links.classList.add("activated");
}
showPlayButton(button);
}
function isPlaying(button) {
var play = getPlay(button);
return play.classList.contains("hide");
}
function pauseAllButtons() {
var buttons = document.querySelectorAll(".playButton");
buttons.forEach(function hidePause(button) {
if (isPlaying(button)) {
showPlayButton(button);
}
});
}
function showPauseButton(button) {
var pause = getPause(button);
pauseAllButtons();
hideAllButtons(button);
show(pause);
button.classList.add("active");
button.classList.add("activated");
}
function showSpeakerButton(button) {
var speaker = getSpeaker(button);
hideAllButtons(button);
show(speaker);
}
function getAudio() {
return document.querySelector("audio");
}
function playAudio(player, button) {
var src = button.getAttribute("data-audio");
player.volume = 1.0;
player.setAttribute("src", src);
player.play();
}
function pauseAudio(player) {
player.pause();
}
function togglePlayButton(button) {
var player = getAudio();
if (isPlaying(button)) {
showPlayButton(button);
pauseAudio(player);
} else {
showPauseButton(button);
playAudio(player, button);
}
}
function showPause(button) {
if (isPlaying(button)) {
showPauseButton(button);
}
}
function showSpeaker(button) {
if (isPlaying(button)) {
showSpeakerButton(button);
}
}
function playButtonClickHandler(evt) {
var button = getButtonContainer(evt.target);
togglePlayButton(button);
}
function playButtonMouseoverHandler(evt) {
var button = getButtonContainer(evt.target);
showPause(button);
}
function playButtonMouseoutHandler(evt) {
var button = getButtonContainer(evt.target);
showSpeaker(button);
}
function addPlayButtonHandlers(button) {
button.addEventListener("click", playButtonClickHandler);
if (button.querySelector(".speaker")) {
button.addEventListener("mouseover", playButtonMouseoverHandler);
button.addEventListener("mouseout", playButtonMouseoutHandler);
}
}
function initialOverlayClickHandler(evt) {
var button = getButtonContainer(evt.target);
hideInitialOverlay(button);
button.removeEventListener("click", initialOverlayClickHandler);
addPlayButtonHandlers(button);
if (button.classList.contains("initialPause")) {
return;
}
playButtonClickHandler(evt);
}
var playButtons = document.querySelectorAll(".playButton");
playButtons.forEach(function addHandler(button) {
if (button.querySelector(".initial")) {
button.addEventListener("click", initialOverlayClickHandler);
} else {
addPlayButtonHandlers(button);
}
});
}());
Thatâs only 171 lines of code, and less complexity to deal with, compared with the 692 lines of code that you were having to deal with earlier.
The full updated code for handling all of the buttons is at https://jsfiddle.net/zfs5qqq9/236/
Iâm having an issue with Player B
Itâs supposed to stay on the blue image after you click off it.
Instructions:
Click on Player B, then click on another player, Player B should stay on the blue image and not change.
Iâm using image sprites with the code, do you understand what the issue is, or maybe I should ask someone in the html spot.
It was set to active when it shouldâve been activated.
Fixed.
This is not good, right?
.playButtonb.activated
.playButtone.activated
I should change one of the activatedâs to a different name, right?
Donât change them to different names. CSS gets more powerful when the same name is used to easily reduce duplication.
There was interference last time, and so, thatâs why we changed one from active to to activated.
This may not be what I want:
How would I add a
TimeRanges.start()
to the code?
How would I delay the start of the audio after I click play?
How would I add a 1, 2, or 3 second delay until the audio starts playing?