Add and remove classes from Node in Javascript - javascript

I have a slider in my page and slider's indicators are dynamic, It bases on slider's elements' number and width of body.
My code block is:
function setIndicators(){
const indicator = document.createElement("div");
indicator.className = "indicator active";
indicatorContainer.innerHTML = "";
for(let i = 0;i <= maxIndex; i++){
indicatorContainer.appendChild(indicator.cloneNode(true));
}
updateIndicators();
}
which is working fine. But I want to show active indicator but I cannot manipulate elements' classes.
I tried this:
function updateIndicators(index) {
indicators.forEach((indicator) => {
indicator.classList.remove("active");
});
let newActiveIndicator = indicators[index];
newActiveIndicator.classList.add("active");
}
And I am not able to reach every indicators using index or anything I know/find. Also, it seems like NodeList not a HTML element.
Other things you may need:
const indicatorContainer = document.querySelector(".container-indicators");
const indicators = document.querySelectorAll(".indicator");
let maxScrollX = slider.scrollWidth - body.offsetWidth;
let baseSliderWidth = slider.offsetWidth;
let maxIndex = Math.ceil(maxScrollX / baseSliderWidth);

A better one I would suggest using the indicators in a different way. Since your HTML isn't shared, I have to assume a few things:
function clearAll() {
const activeOnes = document.querySelectorAll(".active");
activeOnes.forEach(function(activeOne) {
activeOne.classList.remove("active");
});
}
function chooseOne(index) {
clearAll();
const indicators = document.querySelectorAll(".indicator");
indicators[index].classList.add("active");
}
* {
font-family: 'Operator Mono', consolas, monospace;
}
.indicators {
border: 2px solid #ccc;
display: inline-block;
width: auto;
margin: 15px;
}
.indicators .indicator {
padding: 15px;
line-height: 1;
background-color: #fff;
flex-grow: 1;
text-align: center;
display: inline-block;
}
.indicator.active {
background-color: #f90;
}
<div class="indicators"><div class="indicator">I1</div><div class="indicator">I2</div><div class="indicator">I3</div><div class="indicator">I4</div><div class="indicator">I5</div></div>
<button onclick="chooseOne(2); return false">Select I3</button>
<button onclick="chooseOne(3); return false">Select I4</button>
I would have done this differently this way.
Preview

Related

How to add a smooth animation to the progress bar

When I click I want to smoothly add segments to the progress bar. They are added but instantly. What could be the problem?
I tried to implement a smooth animation with setInterval, but nothing comes out. Percentages are also added instantly.
let progressBar = document.querySelector(".progressbar");
let progressBarValue = document.querySelector(".progressbar__value");
const body = document.querySelector("body");
let progressBarStartValue = 0;
let progressBarEndValue = 100;
let speed = 50;
body.addEventListener("click", function(e) {
if (progressBarStartValue === progressBarEndValue) {
alert("you have completed all the tasks");
} else {
let progress = setInterval(() => {
if (progressBarStartValue != 100) {
progressBarStartValue += 10;
clearInterval(progress);
}
progressBarValue.textContent = `${progressBarStartValue}%`;
progressBar.style.background = `conic-gradient(
#FFF ${progressBarStartValue * 3.6}deg,
#262623 ${progressBarStartValue * 3.6}deg
)`;
}, speed);
}
});
.progressbar {
position: relative;
height: 150px;
width: 150px;
background-color: #262623;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.progressbar::before {
content: "";
position: absolute;
height: 80%;
width: 80%;
background-color: #0f0f0f;
border-radius: 50%;
}
.progressbar__value {
color: #fff;
z-index: 9;
font-size: 25px;
font-weight: 600;
}
<main class="main">
<section class="statistic">
<div class="container">
<div class="statistic__inner">
<div class="statistic__text">
<h2 class="statistic__title">You're almost there!</h2>
<p class="statistic__subtitle">keep up the good work</p>
</div>
<div class="progressbar"><span class="progressbar__value">0%</span></div>
</div>
</div>
</section>
</main>
This may not be exactly what you're looking for, but with the conic-gradient() implementation you're using, I'd recommend checking out a library call anime.js.
Here's an example with your implementation (same html and css):
// your.js
let progressBar = document.querySelector(".progressbar");
let progressBarValue = document.querySelector(".progressbar__value");
const body = document.querySelector("body");
// Switched to object for target in anime()
let progressBarObject = {
progressBarStartValue: 0,
progressBarEndValue: 100,
progressBarAnimationValue: 0 * 3.6 // New value needed for smoothing the progress bar, since the progress value needs to be multiplied by 3.6
}
// Not necessary, but I recommend changing the event listener to pointerup for better support
// Also not necessary, I changed function to arrow function for my own preference
body.addEventListener("pointerup", e => {
e.preventDefault()
if (progressBarObject.progressBarStartValue === progressBarObject.progressBarEndValue) {
alert("you have completed all the tasks");
} else {
let newValue = 0 // Needed so we can set the value, before it's applied in anime()
if (progressBarObject.progressBarStartValue != 100) {
// Math.ceil() allows us to round to the nearest 10 to guarantee the correct output
newValue = Math.ceil((progressBarObject.progressBarStartValue + 10) / 10) * 10;
}
// Optional: Prevents accidentally going over 100 somehow
if (newValue > 100) {
newValue = 100
}
anime({
targets: progressBarObject,
progressBarStartValue: newValue,
progressBarAnimationValue: newValue * 3.6,
easing: 'easeInOutExpo',
round: 1, // Rounds to nearest 1 so you don't have 0.3339...% displayed in progressBarValue
update: () => {
progressBar.style.backgroundImage = `conic-gradient(
#FFF ${progressBarObject.progressBarAnimationValue}deg,
#262623 ${progressBarObject.progressBarAnimationValue}deg)`;
progressBarValue.textContent = `${progressBarObject.progressBarStartValue}%`;
},
duration: 500
});
}
});
Here's a CodePen using the anime.js CDN: Circular Progress Bar Smoothing
If you don't want to use a javascript library, then I'd recommend switching from the conic-gradient() to something else. I hear using an .svg circle with stroke and stroke-dasharray can work great with CSS transition.
You shouldn't setInterval your progress variable like this. instead, put it as a global variable outside the function then use it to gradually add 1 as long as the start value is less than progress, and you still can control the speed with your speed variable.
let progressBar = document.querySelector(".progressbar");
let progressBarValue = document.querySelector(".progressbar__value");
const body = document.querySelector("body");
let progressBarStartValue = 0;
let progressBarEndValue = 100;
let speed = 50;
let progress = 0;
body.addEventListener("click", function(e) {
if (progressBarStartValue === progressBarEndValue) {
alert("you have completed all the tasks");
} else {
progress += 10;
setInterval(() => {
if (progressBarStartValue < progress) {
progressBarStartValue += 1;
clearInterval();
}
progressBarValue.textContent = `${progressBarStartValue}%`;
progressBar.style.background = `conic-gradient(
#FFF ${progressBarStartValue * 3.6}deg,
#262623 ${progressBarStartValue * 3.6}deg
)`;
}, speed);
}
});
.progressbar {
position: relative;
height: 150px;
width: 150px;
background-color: #262623;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid red;
}
.progressbar::before {
content: "";
position: absolute;
height: 80%;
width: 80%;
background-color: #0f0f0f;
border-radius: 50%;
border: 3px solid blue;
}
.progressbar__value {
color: #fff;
z-index: 9;
font-size: 25px;
font-weight: 600;
}
<main class="main">
<section class="statistic">
<div class="container">
<div class="statistic__inner">
<div class="statistic__text">
<h2 class="statistic__title">You're almost there!</h2>
<p class="statistic__subtitle">keep up the good work</p>
</div>
<div class="progressbar"><span class="progressbar__value">0%</span></div>
</div>
</div>
</section>
</main>

Collapse an accordion menu

I have this simple collapsible menu on www.keokuk.com
I would like for the previous menu to close when you click on the next one.
this is the javascript:
<script>
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function () {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
</script>
I worked on a solution on your website.
But it appears you set max-height manually in an other javascript function so you can just do the same thing in the commented line.
document.querySelectorAll('.collapsible').forEach(el => {
el.addEventListener('click', (e) => {
document.querySelectorAll('.collapsible').forEach(e => {
e.classList.remove('active');
e.nextSibling.nextElementSibling.style.maxHeight = "0px";
});
e.target.classList.toggle('active');
e.target.nextSibling.nextElementSibling.style.maxHeight =
`${el.nextSibling.nextElementSibling.scrollHeight}px`;
});
});
Details are commented in example
// Collect all .switch into an array
const switches = [...document.querySelectorAll(".switch")];
// Bind each .switch to the click event
switches.forEach(s => s.addEventListener("click", openClose));
// Event handler passes Event Object as default
function openClose(event) {
// Reference the tag proceeding clicked tag
const content = this.nextElementSibling;
// Get the height of content
let maxHt = content.scrollHeight + 'px';
// Find the index position of clicked tag
let index = switches.indexOf(this);
// The clicked tag will toggle .active class
this.classList.toggle('active');
// Remove .active class from all .switch
switches.forEach((btn, idx) => {
/*
If current index does NOT equal index of
clicked tag...
...remove .active
*/
if (idx != index) {
btn.classList.remove('active');
}
});
/*
If clicked has .active class...
...set style property of max-height using CSS variables
*/
if (this.classList.contains('active')) {
content.style.setProperty('--maxH', maxHt + 'px');
} else {
content.style.setProperty('--maxH', '0px');
}
}
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box
}
:root {
font: 300 1.5ch/1.2 'Segoe UI';
--maxH: 0px;
}
body {
width: 100%;
min-height: 200%;
padding: 15px;
}
header {
width: max-content;
margin: 10px 0 0;
padding: 5px 10px;
border: 3px ridge black;
border-radius: 4px;
background: #aaa;
cursor: pointer;
}
section {
position: relative;
max-height: var(--maxH);
margin: 0;
padding: 5px 10px;
border: 3px ridge black;
border-radius: 4px;
background: #ddd;
opacity: 0;
pointer-events: none;
}
.active+section {
z-index: 1;
opacity: 1.0;
}
<header class='switch'>Read more...</header>
<section>
<p>Merchandise Morty, your only purpose in life is to buy & consume merchandise and you did it, you went into a store an actual honest to god store and you bought something, you didn't ask questions or raise ethical complaints you just looked into
the bleeding jaws of capitalism and said 'yes daddy please' and I'm so proud of you, I only wish you could have bought more, I love buying things so much Morty. Morty, you know outer space is up right? Are you kidding? I'm hoping I can get to both
of them, Rick! And there's no evidence that a Latino student did it.</p>
</section>
<header class='switch'>Read more...</header>
<section>
<p>Oh, I'm sorry Morty, are you the scientist or are you the kid who wanted to get laid? Why don't you ask the smartest people in the universe, Jerry? Oh yeah you can't. They blew up. Looossseeerrrrr. I am not putting my father in a home! He just came
back into my life, and you want to, grab him and, stuff him under a mattress like last month's Victoria's Secret?!
</p>
</section>

Needing help forcing the div to be stuck right under this other div in CSS

So this is day 2 of making an HTML game. I am honestly convinced I'm making a lot of progress and I am, except, yet again, I run into another styling problem.
So there is a grid in the game that updates every time the game is loaded. Basically, the grid's length and width to the word with the most letters, as shown below:
As you can see, the word everyday is 8 letters long, so the game puts 8 spaces available.
Now here's two problems with this in general:
I want the word bank to be directly UNDER the grid, no matter the length of the grids.
I want the grids to have NO space under them, so you see the little space between every new row? Basically that needs to go poof, and not be there.
What have you tried so far?
Placing the word bank div under the game area div didn't work, so I started to look up some solutions on Google, and it told me to try to add position: absolute; and position: relative; to div 1 and 2, but that just created a mess when it came to the word bank (spacing it out WAY too much) and did nothing to the grids. Also, display: block; can't help, because the code is already using flex for a different reason.
I also tried using margin-bottom for the grid space problem, but did nothing.
Code:
// definitely didn't get the grid part from Stack Overflow
var score = 0;
var scoreDisplay = document.getElementById("score");
scoreDisplay.innerHTML = "<p>Score: " + score;
var wordBank = document.getElementById("wordBank")
var gameArea = document.getElementById("gameArea")
let rows = document.getElementsByClassName("gridRow");
let cells = document.getElementsByClassName("cell");
// sparing you word array, nobody wants to read that list to the very last bits
var selectedWords = []
for (let i = 0; i < 5; i++) {
const selectedWord = words[Math.floor(Math.random() * words.length)]
if (selectedWord.length <= 9) {
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
selectedWords.push(selectedWord)
}
}
var longestWord = selectedWords.reduce((a, b) => a.length < b.length ? b : a, "")
var charCount = longestWord.length
function makeRows(rowNum) {
for (r = 0; r < rowNum; r++) {
let row = document.createElement("div");
gameArea.appendChild(row).className = "gridRow";
};
};
function makeColumns(cellNum) {
for (i = 0; i < rows.length; i++) {
for (j = 0; j < cellNum; j++) {
let newCell = document.createElement("div");
rows[j].appendChild(newCell).className = "cell";
};
};
};
function defaultGrid() {
makeRows(charCount);
makeColumns(charCount);
}
defaultGrid();
body {
margin: 0px;
}
.content {
width: 512px;
height: 512px;
margin-left: auto;
margin-right: auto;
font-family: Arial;
}
.score {
font-size: 24px;
text-align: right;
}
.wordBank {
border: 2.5px solid black;
border-radius: 5px;
font-size: 24px;
display: flex;
width: 100%;
justify-content: space-between;
height: 13%;
}
.wordBank> :nth-of-type(even) {
align-self: flex-end;
}
.gameArea {
width: 100%;
height: 70%;
}
.cell {
border: 1px solid black;
width: 50px;
height: 50px;
display: inline-block;
}
<div class="content" id="content">
<div class="gameArea" id="gameArea">
</div>
<div class="wordBank" id="wordBank">
</div>
<div class="score" id="score">
</div>
</div>
How can I fix this issue? Any help is appreciated!
(Example for David):
One approach is below, with explanatory comments in the code:
// replaced all uses of 'var' with either let (if I anticipated the value would change), or const
// (if the value was likely to be unchanging):
let score = 0;
const scoreDisplay = document.getElementById("score");
const wordBank = document.getElementById("wordBank")
const gameArea = document.getElementById("gameArea")
const rows = document.getElementsByClassName("gridRow");
const cells = document.getElementsByClassName("cell");
// created an Array of words (though ideally a minimal, demonstrative Array would have been in the
// posted MCVE demo code); obviously: replace with your own Array:
const words = ['hello', 'thrifty', 'gaol', 'maester', 'mandible', 'osteoarthritic', 'venerable', 'the', 'cursive'];
let selectedWords = []
// moved this line out of the variable assignments/initialisation, in order that it's easier to
// maintain the code, because related things/actions are in the same/similar place(s):
scoreDisplay.innerHTML = "<p>Score: " + score;
// the rest of the JavaScript I left alone, with the exception of adding a 'let' declaration in the
// for loops after this first one:
for (let i = 0; i < 5; i++) {
const selectedWord = words[Math.floor(Math.random() * words.length)]
if (selectedWord.length <= 9) {
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
selectedWords.push(selectedWord)
}
}
let longestWord = selectedWords.reduce((a, b) => a.length < b.length ? b : a, "")
let charCount = longestWord.length
function makeRows(rowNum) {
for (let r = 0; r < rowNum; r++) {
let row = document.createElement("div");
gameArea.appendChild(row).className = "gridRow";
}
}
function makeColumns(cellNum) {
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < cellNum; j++) {
let newCell = document.createElement("div");
rows[j].appendChild(newCell).className = "cell";
}
}
}
function defaultGrid() {
makeRows(charCount);
makeColumns(charCount);
}
defaultGrid();
/* added a simple, minimal CSS reset to normalise all element defaults
to a similar layout-sizing method, and font-family: */
*,
::before,
::after {
box-sizing: border-box;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
/* added this, to help lay out the various elements more clearly: */
.content {
display: grid;
/* defining three rows, each of which is sized to the maximum size
needed to clearly display the content within: */
grid-template-rows: repeat(3, max-content);
/* setting a margin around the element on the block-axis, which is
perpendicular to the inline-axis, the inline-axis being the
direction of writing in the local language; so in left-to-right
languages this results in a top, and bottom, margin of 1em: */
margin-block: 1em;
/* setting a margin of auto on the inline-axis, the left and right
margins of the element in a left-to-right language: */
margin-inline: auto;
/* I retained the width, but removed the height constraint: */
width: 512px;
}
.score {
font-size: 24px;
text-align: right;
}
.wordBank {
border: 2.5px solid #000;
border-radius: 5px;
display: flex;
/* I left this part more ore less alone, other than adjusting
the font-size to an 'em' based sizing for responsive purposes: */
font-size: 1.6em;
/* added a minimum height, in order to allow room for the words
to move to the end within the space: */
min-height: 3em;
justify-content: space-between;
padding: 0.25em;
}
.wordBank span:nth-child(even) {
align-self: end;
}
.gameArea {
/* removing the spaces below/between each .gridRow element, which are caused by
the newline and whitespace characters between the .gridRow elements: */
font-size: 0;
/* placing the game area 'board' horizontally centered in the layout */
justify-self: center;
max-width: 100%;
}
.cell {
border: 1px solid black;
width: 50px;
/* resetting the font-size, so that text is visible once more (despite the parent
having a font-size of 0): */
font-size: 1rem;
height: 50px;
display: inline-block;
}
<div class="content" id="content">
<div class="gameArea" id="gameArea">
</div>
<div class="wordBank" id="wordBank">
</div>
<div class="score" id="score">
</div>
</div>
JS Fiddle demo.
Further to the question in the comments (below):
[...]one problem, why do only four words appear [in] certain instances?
This is a result of your loop, and its check:
// here, i is initialised to 0 (first iteration),
// the assessment is then executed; if it evaluates
// to true the loop runs an iteration, otherwise
// if the assessment returns false the loop stops;
// after the assessment i is incremented:
for (let i = 0; i < 5; i++) {
// selecting a random word:
const selectedWord = words[Math.floor(Math.random() * words.length)]
// testing the length of that random word:
if (selectedWord.length <= 9) {
// if the 'if' statement evaluates to true:
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
// adding the selectedWord to the selectedWords Array
selectedWords.push(selectedWord)
// if the 'if' statement evaluates to false nothing
// happens, the loop runs another iteration; this
// 'consumes' a loop but no word was added hence
// a smaller selectedWords Array
}
}
To guard against this, you could modify your loop:
let score = 0;
const scoreDisplay = document.getElementById("score");
const wordBank = document.getElementById("wordBank")
const gameArea = document.getElementById("gameArea")
const rows = document.getElementsByClassName("gridRow");
const cells = document.getElementsByClassName("cell");
const words = ['hello', 'thrifty', 'gaol', 'maester', 'mandible', 'osteoarthritic', 'venerable', 'the', 'cursive'];
let selectedWords = [];
scoreDisplay.innerHTML = "<p>Score: " + score;
// using a while() loop, and testing the length of the selectedWords Array, so that
// while the condition is true (and the Array-length is less than 5) the loop will
// continue running:
while (selectedWords.length < 5) {
// select random word:
const selectedWord = words[Math.floor(Math.random() * words.length)];
// test the length of that word is less than 9 characters:
if (selectedWord.length <= 9) {
// adding content to the wordBank element:
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
// pushing the word to the Array:
selectedWords.push(selectedWord);
// if no word is added to the Array, the length of the Array doesn't change
// and so the while loop will run again.
}
}
let longestWord = selectedWords.reduce((a, b) => a.length < b.length ? b : a, "")
let charCount = longestWord.length
function makeRows(rowNum) {
for (let r = 0; r < rowNum; r++) {
let row = document.createElement("div");
gameArea.appendChild(row).className = "gridRow";
}
}
function makeColumns(cellNum) {
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < cellNum; j++) {
let newCell = document.createElement("div");
rows[j].appendChild(newCell).className = "cell";
}
}
}
function defaultGrid() {
makeRows(charCount);
makeColumns(charCount);
}
defaultGrid();
*,
::before,
::after {
box-sizing: border-box;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.content {
display: grid;
grid-template-rows: repeat(3, max-content);
margin-block: 1em;
margin-inline: auto;
width: 512px;
}
.score {
font-size: 24px;
text-align: right;
}
.wordBank {
border: 2.5px solid #000;
border-radius: 5px;
display: flex;
font-size: 1.6em;
min-height: 3em;
justify-content: space-between;
padding: 0.25em;
}
.wordBank span:nth-child(even) {
align-self: end;
}
.gameArea {
font-size: 0;
justify-self: center;
max-width: 100%;
}
.cell {
border: 1px solid black;
width: 50px;
font-size: 1rem;
height: 50px;
display: inline-block;
}
<div class="content" id="content">
<div class="gameArea" id="gameArea">
</div>
<div class="wordBank" id="wordBank">
</div>
<div class="score" id="score">
</div>
</div>
JS Fiddle demo.
Note that there is an infinitesimally small chance that this may lead to an infinite loop – though to do so would require that every iteration of the while loop selects a random word longer than 9 characters in length – so it may be worth modifying further, to filter the Array and first remove all words with more than 9 characters:
// you didn't include your own Array, so I'm not sure how
// it's assigned; but you should be able to use
// Array.prototype.filter():
const words = ['hello', 'thrifty', 'gaol', 'maester', 'mandible', 'osteoarthritic', 'venerable', 'the', 'cursive']
// here we use an Arrow function to filter the words
// of the words Array:
.filter(
// passing in a reference to the current Array-element
// ('word') of the Array over which we're iterating;
// here we're testing that the length of the current
// word is less than 9; if so this assessment returns
// Boolean true, and the word is retained in the Array,
// otherwise it returns false and the word is discarded:
(word) => word.length < 9
);
// ...code omitted for brevity...
// again, using a while loop, to ensure that we
// have five Array-elements in the selectedWords
// Array:
while (selectedWords.length < 5) {
// no 'if' to check the length, as it's now
// unnecessary to do so:
const selectedWord = words[Math.floor(Math.random() * words.length)];
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
selectedWords.push(selectedWord);
}
let score = 0;
const scoreDisplay = document.getElementById("score");
const wordBank = document.getElementById("wordBank")
const gameArea = document.getElementById("gameArea")
const rows = document.getElementsByClassName("gridRow");
const cells = document.getElementsByClassName("cell");
const words = ['hello', 'thrifty', 'gaol', 'maester', 'mandible', 'osteoarthritic', 'venerable', 'the', 'cursive'];
let selectedWords = [];
scoreDisplay.innerHTML = "<p>Score: " + score;
// using a while() loop, and testing the length of the selectedWords Array, so that
// while the condition is true (and the Array-length is less than 5) the loop will
// continue running:
while (selectedWords.length < 5) {
// select random word:
const selectedWord = words[Math.floor(Math.random() * words.length)];
// test the length of that word is less than 9 characters:
if (selectedWord.length <= 9) {
// adding content to the wordBank element:
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
// pushing the word to the Array:
selectedWords.push(selectedWord);
// if no word is added to the Array, the length of the Array doesn't change
// and so the while loop will run again.
}
}
let longestWord = selectedWords.reduce((a, b) => a.length < b.length ? b : a, "")
let charCount = longestWord.length
function makeRows(rowNum) {
for (let r = 0; r < rowNum; r++) {
let row = document.createElement("div");
gameArea.appendChild(row).className = "gridRow";
}
}
function makeColumns(cellNum) {
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < cellNum; j++) {
let newCell = document.createElement("div");
rows[j].appendChild(newCell).className = "cell";
}
}
}
function defaultGrid() {
makeRows(charCount);
makeColumns(charCount);
}
defaultGrid();
*,
::before,
::after {
box-sizing: border-box;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.content {
display: grid;
grid-template-rows: repeat(3, max-content);
margin-block: 1em;
margin-inline: auto;
width: 512px;
}
.score {
font-size: 24px;
text-align: right;
}
.wordBank {
border: 2.5px solid #000;
border-radius: 5px;
display: flex;
font-size: 1.6em;
min-height: 3em;
justify-content: space-between;
padding: 0.25em;
}
.wordBank span:nth-child(even) {
align-self: end;
}
.gameArea {
font-size: 0;
justify-self: center;
max-width: 100%;
}
.cell {
border: 1px solid black;
width: 50px;
font-size: 1rem;
height: 50px;
display: inline-block;
}
<div class="content" id="content">
<div class="gameArea" id="gameArea">
</div>
<div class="wordBank" id="wordBank">
</div>
<div class="score" id="score">
</div>
</div>
JS Fiddle demo.
You probably want to do something like this:
So, what I did was to place the .game-grid(the n by n grid) and .words-wrapper (the zig zag word cloud) in a .container. This .container is a flex that flows in a column. This shows the 2 items inside the .container one by one from top to bottom.
.game-grid itself is a grid. This lets you easily create a grid.
grid-template-colums: repeat(8, 1fr) tells the browser that this grid is going to have 8 columns (this you will have to control by the length of the longest word). I set the grid to have a fixed size and all the items inside have place-items: stretch which means they take all the available space, so they will all be equal size.
Hope this helps.
.container {
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
align-items: center;
}
.game-grid {
width: 50vh;
height: 50vh;
display: grid;
grid-template-columns: repeat(8, 1fr);
place-items: stretch;
place-content: stretch;
}
.game-grid-item {
border-width: 1px;
border-style: solid;
border-color: chocolate;
width: 100%;
height: 100%;
display: grid;
place-items: center;
}
.words-wrapper {
display: flex;
width: 100%;
justify-content: space-between;
height: 10vh;
border-width: 1px;
border-style: solid;
border-color: blueviolet;
}
.even {
align-self: flex-end;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div class="container">
<div class="game-grid">
<div class="game-grid-item">1</div>
<div class="game-grid-item">2</div>
<div class="game-grid-item">3</div>
<div class="game-grid-item">4</div>
<div class="game-grid-item">5</div>
<div class="game-grid-item">6</div>
<div class="game-grid-item">7</div>
<div class="game-grid-item">8</div>
<div class="game-grid-item">9</div>
<div class="game-grid-item">10</div>
<div class="game-grid-item">11</div>
<div class="game-grid-item">12</div>
<div class="game-grid-item">13</div>
<div class="game-grid-item">14</div>
<div class="game-grid-item">15</div>
<div class="game-grid-item">16</div>
<div class="game-grid-item">1</div>
<div class="game-grid-item">2</div>
<div class="game-grid-item">3</div>
<div class="game-grid-item">4</div>
<div class="game-grid-item">5</div>
<div class="game-grid-item">6</div>
<div class="game-grid-item">7</div>
<div class="game-grid-item">8</div>
<div class="game-grid-item">9</div>
<div class="game-grid-item">10</div>
<div class="game-grid-item">11</div>
<div class="game-grid-item">12</div>
<div class="game-grid-item">13</div>
<div class="game-grid-item">14</div>
<div class="game-grid-item">15</div>
<div class="game-grid-item">16</div>
<div class="game-grid-item">1</div>
<div class="game-grid-item">2</div>
<div class="game-grid-item">3</div>
<div class="game-grid-item">4</div>
<div class="game-grid-item">5</div>
<div class="game-grid-item">6</div>
<div class="game-grid-item">7</div>
<div class="game-grid-item">8</div>
<div class="game-grid-item">9</div>
<div class="game-grid-item">10</div>
<div class="game-grid-item">11</div>
<div class="game-grid-item">12</div>
<div class="game-grid-item">13</div>
<div class="game-grid-item">14</div>
<div class="game-grid-item">15</div>
<div class="game-grid-item">16</div>
<div class="game-grid-item">1</div>
<div class="game-grid-item">2</div>
<div class="game-grid-item">3</div>
<div class="game-grid-item">4</div>
<div class="game-grid-item">5</div>
<div class="game-grid-item">6</div>
<div class="game-grid-item">7</div>
<div class="game-grid-item">8</div>
<div class="game-grid-item">9</div>
<div class="game-grid-item">10</div>
<div class="game-grid-item">11</div>
<div class="game-grid-item">12</div>
<div class="game-grid-item">13</div>
<div class="game-grid-item">14</div>
<div class="game-grid-item">15</div>
<div class="game-grid-item">16</div>
</div>
<div class="words-wrapper">
<span class="item">multiply</span>
<span class="item even">step</span>
<span class="item">kiss</span>
<span class="item even">force</span>
<span class="item">ago</span>
</div>
</div>
</body>
</html>

Changing innerHTML based on specific button click using assigned values returns undefined

I have a form that I'm using for orders. I'm using the same form for every order but I want to replace the last word in the text string of the header (in this case "Food") to show what the customer is ordering based on the button they clicked. For this example, I've just made a simple layout with three buttons that are each assigned a value inside their button tags. The same button style is going to be used all throughout, it's just going to have a different name and value assigned to it for the form. I've also made a close button to simulate the user closing the form and forcing the last word in the header back to its default Food just in case they manage to open the form without actually clicking a button so they don't see something like "Order Your null". So ideally, the user would click their choice of food and the header title would change to "Order Your [Food Value]"
The problem I'm having is that I can't figure out how to collect the values and change the innerHTML based on what was clicked using javascript. If it were only three buttons I could write three separate expressions but on my project, I've got about 40 items and buttons total so I'd rather label 40 buttons with values and use one script to power them all as opposed to writing an expression for every button (completely possible but not very efficient for myself or the user's browser I would imagine...).
The script runs but returns an "undefined" value. I've tried changing the let food = document. attribute to just about anything I could find on Google and nothing is returning a value (or an error for that matter).
https://jsfiddle.net/astombaugh/kf2vgq0p/550/
<body>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="foodHeader">Order Your<div id="foodHeader">Food</div>
</div>
<div class="btnContainer">
<a class="orderBtn" name="foodItem" value="Cheeseburger">Order Your Cheeseburger</a>
<a class="orderBtn" name="foodItem" value="Salad" style="background-color: green">Order Your Salad</a>
<a class="orderBtn" name="foodItem" value="Sub" style="background-color: blue">Order Your Sub</a>
</div>
<div class="closeBtnContainer">
<a class="closeBtn">
X
</a>
</div>
</body>
.foodHeader {
display: block;
font-family: Helvetica Neue, Helvetica, Arial;
font-weight: 700;
text-align: center;
color: black;
font-size: 3rem;
margin: 0 auto;
}
.btnContainer {
display: flex;
flex-direction: row;
}
.orderBtn {
display: block;
font-family: Helvetica Neue, Helvetica, Arial;
font-weight: 700;
text-align: center;
color: white;
width: 200px;
height: 50px;
margin: 0 auto;
background-color: red;
font-size: 1rem;
padding: 10px 10px;
line-height: 3rem;
outline: 1px solid black;
}
.closeBtnContainer {
display: block;
margin: 20px auto;
text-align: center;
}
.closeBtn{
background-color: black;
color: white;
width: 20px;
height: 20px;
margin: 0px auto;
padding: 10px;
text-align: center;
cursor: pointer;
}
let food = document.querySelectorAll('foodItem').value;
let foodHeaderText = document.getElementById('foodHeader');
const orderBtn = document.getElementsByClassName('orderBtn');
const closeBtn = document.getElementsByClassName('closeBtn');
for (let order = 0; order < orderBtn.length; order++) {
orderBtn[order].addEventListener('click', function() {
foodHeaderText.innerHTML = food;
})
}
for (let close = 0; close < closeBtn.length; close++) {
closeBtn[close].addEventListener('click', function() {
foodHeaderText.innerHTML = "Food";
})
}
The function you pass to the event listeners accepts a parameter, that is the event object. You can find more here. Given that, you can arrange a solution like the following:
let foodHeaderText = document.getElementById('foodHeader');
const orderBtn = document.getElementsByClassName('orderBtn');
const closeBtn = document.getElementsByClassName('closeBtn');
for (let order = 0; order < orderBtn.length; order++) {
orderBtn[order].addEventListener('click', function(event) {
foodHeaderText.innerHTML = event.target.attributes["value"]["value"];
})
}
for (let close = 0; close < closeBtn.length; close++) {
closeBtn[close].addEventListener('click', function(event) {
foodHeaderText.innerHTML = "Food";
})
}
Reason why you are getting undefined is because
let food = document.querySelectorAll('foodItem').value;
definitely is undefined: querySelectorAll return a collection, and has no property value. Besides, you want to dynamically get the value at each click, not once for all.
Replace code buy:
function(e) {
foodHeaderText.innerHTML = e.target.getAttribute("value") || "food";
I believe your issue is in this line:
let food = document.querySelectorAll('foodItem').value;
You could try this:
let food = Array.from(document.querySelectorAll('[name="foodItem"]')).map(el => el.getAttribute('value'))
Hope this helps.
food isn't defined as a string. In fact, it's undefined because you're trying to retrieve all foodItem elements at once before they're even clicked. As others have said - that's a NodeList and won't have a value property.
You code can be updated to:
for (let order = 0; order < orderBtn.length; order++) {
orderBtn[order].addEventListener('click', function(e) {
foodHeaderText.innerHTML = e.target.value;
});
}
Or, to be a little more modern:
document.getElementsByClassName("orderBtn").forEach(orderBtn => {
orderBtn.addEventListener("click", e => {
foodHeaderText.textContent = e.target.value;
});
});
Or, even better, don't use tons of listeners - just add one to the parent container:
document.querySelector(".btnContainer").addEventListener("click", e => {
if (e.target.matches(".orderBtn")) {
foodHeaderText.textContent = e.target.value;
}
}
your code should work
// just change this line
foodHeaderText.innerHTML = food;
// with this one
foodHeaderText.innerHTML = this.innerHTML;
// or with this one
foodHeaderText.innerHTML = orderBtn[order].innerHTML;
and get rid of that food variable
It's not correct, and you don't have any foodItem classes in your html file, even though you've already selected them as orderBtn.

Animation within animation

I have animation that works like this:
var words_array = [];
words_array[0] = ['FUN', 'CREATIVE', 'INNOVATIVE'];
words_array[1] = ['WEB', 'WORLD'];
var words = ['We are <span class="words" style="background:#F33B65; font-weight:bold; padding: 0 10px;">FUN</span>',
'We like the <span class="words" style="background:#8be32d; font-weight:bold; padding: 0 10px;">WEB</span>'
];
$('#caption').html(words[0]);
var i = 0;
setInterval(function() {
$('#caption').animate({
width: 'toggle'
}, {
duration: 400,
done: function() {
$('#caption').html(words[i = (i + 1) % words.length]);
}
}).delay(300).animate({
width: 'toggle'
}, 400);
}, 5000);
body {
background: #333;
}
#caption {
height: 200px;
font-size: 80px;
line-height: 100px;
color: #fff;
overflow: hidden;
vertical-align: top;
white-space: nowrap;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div id="caption"></div>
Every 5 seconds you get the toggle change of the words array. What I'd like to create, but I'm failing, is to have the toggle, then change few words in the .words span that are located in the words_array, and then after I've changed all the words, the toggle will happen, to the second sentence in the words array, and now I'll change the .words with the associated words_array and so on (if I have more sentences/words).
So the animation goes like this:
First 'slide': We are FUN
CREATIVE <- only this changes
INNOVATIVE
Slide toggle to second 'slide': We like the WEB
WORLD
And I could add as much words/slides as I want.
Doing one (just changing the words) or the other (sliding the sentence) is rather easy, but combining them is where I am stuck :\
EDIT:
I using the solution provided I tweaked the code a bit:
var words_array = [];
words_array[0] = ['FUN', 'CREATIVE', 'INNOVATIVE'];
words_array[1] = ['WEB', 'WORLD'];
var words = ['We are <span class="words" style="background:#F33B65; font-weight:bold; padding: 0 10px;">FUN</span>',
'We like the <span class="words" style="background:#8be32d; font-weight:bold; padding: 0 10px;">WEB</span>'
];
var $caption = $('#caption'),
i = 1,
w = 0,
$replace = $caption.find('.words');
function switchSentence() {
$caption.animate({
width: 'toggle'
}, {
duration: 400,
done: function() {
i = (i + 1) % words.length;
w = 0;
$caption.html(words[i]);
$replace = $caption.find('.words');
}
}).delay(300).animate({
width: 'toggle'
}, 400).delay(300);
}
switchSentence();
function switchWord() {
if (w >= words_array[i].length - 1) {
switchSentence();
w = 0;
} else {
w += 1;
}
if (words_array[i]) {
$replace.animate({
width: 'toggle'
}, {
duration: 400,
done: function() {
$replace.text(words_array[i][w]);
}
}).delay(300).animate({
width: 'toggle'
}, 400);
}
}
switchWord();
setInterval(switchWord, 2500);
body {
background: #333;
}
#caption {
height: 200px;
font-size: 80px;
line-height: 100px;
color: #fff;
overflow: hidden;
white-space: nowrap;
display: inline-block;
vertical-align: top;
}
.words {
display: inline-block;
vertical-align: top;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="caption"></div>
Added another animation in the words toggle. Thanks somethinghere for all the help!!
How about adding another timeout that will simply loop through the current available words? When you switch the arrays, simple reset the loop and let it check the correct amount of words. Notice in the snippet how the function switchSentence and switchWords are entirely unrelated. The switchWords function makes use of the currently selected sentence, and the swicthSentence function does the changing of the sentence, as the name suggests. This way you don't really have to know how to align them properly, they will do their job regardless. Have a look at the snippet:
var words_array = [
['FUN', 'CREATIVE', 'INNOVATIVE'],
['WEB', 'WORLD']
];
var words = [
'We are <span class="words fun">FUN</span>',
'We like the <span class="words like">WEB</span>'
];
var caption = $('#caption'),
i = 1,
w = 0,
replace = caption.find('span');
function switchSentence() {
caption.animate({width: 'toggle'},{
duration: 400,
done: function() {
i = (i + 1) % words.length;
w = 0;
caption.html(words[i]);
replace = caption.find('span');
}
}).delay(300).animate({width: 'toggle'}, 400);
}
switchSentence();
setInterval(switchSentence, 5000);
function switchWord(){
if(w >= words_array[i].length - 1) w = 0;
else w += 1;
if(words_array[i]) replace.text(words_array[i][w])
}
switchWord();
setInterval(switchWord, 500);
body {
background: #333;
}
#caption {
height: 200px;
font-size: 80px;
line-height: 100px;
color: #fff;
overflow: hidden;
vertical-align: top;
white-space: nowrap;
}
.fun, .like { font-weight: bold; }
.fun { background: #F33B65; }
.like { background: #8be32d; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div id="caption"></div>
I also decided to clean up your code a bit to make it more legible and useable. I moved the two switching functions into separate functions and passed them to the interval listeners separately. This is so I could immediately kickstart them by calling them once myself. I also streamlined your array, and moved the style declaration into your CSS instead of inline styles (which makes both your JS and CSS look a lot cleaner).

Categories