I'm beginning my self-studying on web development. Currently working out on JS and trying to create a little "etch-a-sketch" app. I wrote a code which creates canvas with predefined "pixel" size. It work's fine to me. However, in order to make script file as narrow as possible I'm wondering if I can set size properties of pixels entirely by Flexbox properties in CSS. Mathematically speaking I'd like every new element inside parent container to have properties: Width = parentsWidth/nthChild, Height = Width (so every item could always be the biggest possible square regardless of items number).
let initialValue = 100;
const radioValues = document.querySelectorAll('input[name=size]');
let pixelSize = initialValue;
// event listener for chosing size value and setting new canvas
for (let i = 0; i < 3; i++) {
radioValues[i].addEventListener('click', function(e){
initialValue = Number((e.target.value));
pixelSize = initialValue;
//erasing existing pixels
if (document.querySelectorAll('.pixel').length > 0) {
const existingPixel = document.querySelectorAll('.pixel')
for(let i = 0; i < existingPixel.length;i++){
existingPixel[i].remove();
}
}
// building new pixels
for (let i = 0; i < pixelSize; i++){
let container = document.querySelector('.container');
let pixel = document.createElement('div');
pixel.setAttribute('class', 'pixel');
// part to get rid of
if (pixelSize == 400) {
pixel.style.height = "25px";
pixel.style.width = "25px";
}
if (pixelSize == 1600) {
pixel.style.height = "12.5px";
pixel.style.width = "12.5px";
}
container.appendChild(pixel);
}
})
}
.container {
width: 500px;
height: 500px;
margin-left: auto;
margin-right: auto;
border: 1px solid grey;
margin-top: 10%;
display: flex;
flex-wrap: wrap;
padding: 0;
}
.pixel {
height: 50px;
width: 50px;
border:1px solid grey;
margin: 0;
box-sizing: border-box;
}
<form>
<input class="size_selection" type="radio" name="size" value="100" checked> <label for="size_selection">10x10</label>
<input class="size_selection" type="radio" name="size" value="400"> <label for="size_selection">20x20</label>
<input class="size_selection" type="radio" name="size" value="1600"> <label for="size_selection">40x40</label>
</form>
<div class="container"></div>
<button id="clear">Clear</button>
I just refactored your script to use css grid and the corresponding grid-template-rows and grid-template-columns properties to match your grid.
Using grid in this instance makes it much more easy to get the result you want.
Just look around, I made some changes not only to CSS but also to HTML and JS. Hope it helps
let initialValue = 100;
const radioValues = document.querySelectorAll('input[name=size]');
let pixelSize = initialValue;
let container = document.getElementById('container');
// event listener for chosing size value and setting new canvas
for (let i = 0; i < 3; i++) {
radioValues[i].addEventListener('click', function(e) {
initialValue = Number((e.target.value));
pixelSize = initialValue;
//erasing existing pixels
if (document.querySelectorAll('.pixel').length > 0) {
const existingPixel = document.querySelectorAll('.pixel')
for (let i = 0; i < existingPixel.length; i++) {
existingPixel[i].remove();
}
}
// building new pixels
// setting the correct number of columns and rows in the grid
container.style.gridTemplateColumns = 'repeat(' + pixelSize + ', 1fr)';
container.style.gridTemplateRows = 'repeat(' + pixelSize + ', 1fr)';
for (let i = 0; i < pixelSize * pixelSize; i++) {
let pixel = document.createElement('div');
pixel.classList.add('pixel');
container.appendChild(pixel);
}
})
}
radioValues[0].click();
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
form {
text-align: center;
margin: 2rem auto;
}
#container {
width: 300px;
height: 300px;
margin: 2rem auto;
border: 1px solid grey;
display: grid;
}
.pixel {
border: 1px solid gray;
}
<body>
<form>
<input class="size_selection" type="radio" name="size" value="10" checked> <label for="size_selection">10x10</label>
<input class="size_selection" type="radio" name="size" value="20"> <label for="size_selection">20x20</label>
<input class="size_selection" type="radio" name="size" value="40"> <label for="size_selection">40x40</label>
</form>
<div id="container">
</div>
<button id="clear">Clear</button>
</body>
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
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>
I am trying to make use of createElement, createTextNode and appendChild to rewrite an outdated simple to-do list code example.
The code example requires the use of the array join() method so, unfortunately, this can't be removed. The old version just wrote the HTML for the ul list in code fragments.
I am unsure how to proceed where I have entered to comment at line 30 of the js: " need to render the tasks (from stringToInsert) as a list to div id="output" here "
I have referred to the following stackoverflow articles to help me rewrite the code:
js-how-to-concatenate-variables-inside-appendchild -This example uses join() and appendChild but not list items.
create-ul-and-li-elements-in-javascript-
From that one, I copied the code from a Fiddle and put it into function createul() in my example codepen
Once I have the function addTask() working createul() and it's associated HTML elements (such as the render list button) will be removed.
// tasks.js #2
// This script manages a to-do list.
// Need a global variable:
var tasks = [];
function addTask() {
'use strict';
console.log("addTask started");
// Get the task:
var task = document.getElementById('task');
// Reference to where the output goes:
var output = document.getElementById('output');
if (task.value) {
tasks.push(task.value);
// Update the page:
//var message = '<h2>To-Do</h2>';
var stringToInsert = tasks.join(' : ');
console.log(stringToInsert);
var taskUl = document.createElement('ul');
taskUl.setAttribute('id', 'autoTask');
document.getElementById('output').appendChild(taskUl);
/* need to render the tasks (from stringToInsert) as a list to div id ="output" here */
document.getElementById("task").value = '';
}
// Return false to prevent submission:
return false;
} // End of addTask() function.
function createul() {
var ul = document.createElement('ul');
ul.setAttribute('id', 'proList');
var t, tt;
productList = ['Electronics Watch', 'House wear Items', 'Kids wear', 'Women Fashion'];
document.getElementById('renderList').appendChild(ul);
productList.forEach(renderProductList);
function renderProductList(element, index, arr) {
var li = document.createElement('li');
li.setAttribute('class', 'item');
ul.appendChild(li);
t = document.createTextNode(element);
li.innerHTML = li.innerHTML + element;
}
}
function init() {
document.getElementById('renderbtn').addEventListener("click", createul);
document.getElementById('theForm').onsubmit = addTask;
}
window.addEventListener('load', init);
/* css (I have simplified this a little for this example and I am sorry I haven't cut it down further) */
form {
margin: 0 auto;
width: 400px;
padding: 14px;
background-color: #ffffff;
border: solid 2px #425955;
}
/* ----------- stylized ----------- */
h1 {
font-size: 14px;
font-weight: bold;
margin-bottom: 8px;
}
p {
font-size: 11px;
color: #666666;
margin-bottom: 20px;
border-bottom: solid 1px #BFBD9F;
padding-bottom: 10px;
}
label {
display: block;
font-weight: bold;
text-align: right;
width: 140px;
float: left;
}
select {
float: left;
font-size: 12px;
padding: 4px 2px;
border: solid 1px #BFBD9F;
width: 200px;
margin: 2px 0 20px 10px;
}
input {
float: left;
font-size: 12px;
padding: 4px 2px;
border: solid 1px #BFBD9F;
width: 200px;
margin: 2px 0 20px 10px;
}
#submit {
clear: both;
margin-left: 150px;
width: 125px;
height: 31px;
background: #F1F2D8;
text-align: center;
line-height: 20px;
color: #000000;
font-size: 12px;
font-weight: bold;
}
#output {
clear: both;
margin-bottom: 10px;
color: blue;
}
<form action="#" method="post" id="theForm">
<div><label for="task">Task</label><input type="text" name="task" id="task" required></div>
<input type="submit" value="Add It!" id="submit"><br>
<button type="button" id="renderbtn">render list</button>
<div id="renderList"></div>
<div id="output"></div>
edit: I can just convert it back to an array with something like the following if there is no other way of doing it.
var ar = stringToInsert.split(' : ');
or something based on:
stringToInsert.split(' : ').forEach ... or if that doesn't work I could try map()
I'm going to show you a different approach that may help clear things up -
function ul (nodes)
{ const e = document.createElement("ul")
for (const n of nodes)
e.appendChild(n)
return e
}
function li (text)
{ const e = document.createElement("li")
e.textContent = text
return e
}
function onSubmit (event)
{ event.preventDefault()
tasks.push(f.taskInput.value)
f.taskInput.value = ""
render()
}
function render ()
{ const newList = ul(tasks.map(li))
f.firstChild.replaceWith(newList)
}
const tasks = [ "wash dishes", "sweep floors" ] // <- initial tasks
const f = document.forms.main // <- html form
f.addButton.addEventListener("click", onSubmit) // <- button listener
render() // <- first render
<h3>todo list</h3>
<form id="main">
<ul></ul>
<input name="taskInput" placeholder="example: paint fence">
<button name="addButton">Add Task</button>
</form>
And here's a more modern approach using a DOM library like React -
const { useState, useRef } = React
const { render } = ReactDOM
function TodoList ({ initTasks = [] })
{ const [ tasks, updateTasks ] =
useState(initTasks)
const inputEl =
useRef(null)
function onSubmit () {
updateTasks([ ...tasks, inputEl.current.value ])
inputEl.current.value = ""
}
return <div>
<h3>todo list</h3>
<ul>{tasks.map(t => <li children={t} />)}</ul>
<input ref={inputEl} placeholder="ex: paint fence" />
<button onClick={onSubmit}>add</button>
</div>
}
render
( <TodoList initTasks={[ "wash dishes", "sweep floors" ]}/>
, document.body
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
I'm learning how to code javascript, please do not reply that this can be done by using someone else's code that already exists. I am doing this to learn.
I have a situation whereby the below CSS and HTML code:
CSS:
div.miniblock {
font-size: 12px;
background-color: #333333;
text-align: center;
vertical-align: middle;
border: thin dotted #CCCCCC;
color: #FFFFFF;
padding: 2px;
margin: 5px;
cursor: move;
position: relative;
}
div.miniblock_unsaved {
font-size: 12px;
background-color: #55AAAA;
text-align: center;
vertical-align: middle;
border: thin dotted #000;
color: #000;
padding: 2px;
margin: 5px;
cursor: move;
position: relative;
}
div.dropinto {
font-size: 12px;
background-color: #575757;
text-align: center;
vertical-align: middle;
border: thin dotted #AAAAAA;
color: #FFFFFF;
padding: 2px;
margin: 5px;
}
div.dropinto_over {
font-size: 12px;
background-color: #FFFFFF;
text-align: center;
vertical-align: middle;
border: thin dotted #000000;
color: #000000;
padding: 2px;
margin: 5px;
}
div.moving {
font-size: 12px;
background-color: #CCCCCC;
text-align: center;
vertical-align: middle;
border: thin dotted #000000;
color: #000000;
z-index:1;
height: 80px;
width: 80px;
opacity: 0.7;
padding: 5px;
cursor: move;
}
div.OPAQUE {
font-size: 12px;
background-color: #FFF;
text-align: center;
vertical-align: middle;
color: #000000;
opacity: 0.5;
}
HTML:
<INPUT TYPE="HIDDEN" NAME="block_side[3]" VALUE="" ID="block_side0">
<INPUT TYPE="HIDDEN" NAME="block_order[3]" VALUE="" ID="block_order0">
<INPUT TYPE="HIDDEN" NAME="block_side[4]" VALUE="" ID="block_side1">
<INPUT TYPE="HIDDEN" NAME="block_order[4]" VALUE="" ID="block_order1">
<INPUT TYPE="HIDDEN" NAME="block_side[5]" VALUE="" ID="block_side2">
<INPUT TYPE="HIDDEN" NAME="block_order[5]" VALUE="" ID="block_order2">
<INPUT TYPE="HIDDEN" NAME="block_side[6]" VALUE="" ID="block_side3">
<INPUT TYPE="HIDDEN" NAME="block_order[6]" VALUE="" ID="block_order3">
<INPUT TYPE="HIDDEN" NAME="block_side[2]" VALUE="L" ID="block_side=4">
<INPUT TYPE="HIDDEN" NAME="block_order[2]" VALUE="1" ID="block_order4">
<INPUT TYPE="HIDDEN" NAME="block_side[1]" VALUE="L" ID="block_side=5">
<INPUT TYPE="HIDDEN" NAME="block_order[1]" VALUE="2" ID="block_order5">
<DIV CLASS="OPAQUE" ID="instruct"></DIV>Set your preferences for the blocks of information and their display location here.<TABLE height="100%" width="100%"><TR ><TH COLSPAN="2">Assigned Blocks</TH></TR><TR ><TD COLSPAN="2">Here are the blocks that are currently displayed during your experience.</TD></TR><TR ><TD WIDTH="50%" VALIGN="TOP"><DIV CLASS="dropinto" ID="leftblocks" SLOT="L">Left Blocks<div onmousedown="grabobj(4)" onmousemove="dragobj(4)" onmouseup="dropobj(4)" id="4" name="2" class="miniblock">Quick Links [2]</div><div onmousedown="grabobj(5)" onmousemove="dragobj(5)" onmouseup="dropobj(5)" id="5" name="1" class="miniblock">Session Information [1]</div></DIV></TD><TD WIDTH="50%" VALIGN="TOP"><DIV CLASS="dropinto" ID="rightblocks" SLOT="L">Right Blocks</DIV></TD></TR><TR ><TH COLSPAN="2">Unassigned Blocks</TH></TR><TR ><TD COLSPAN="2" ID="unassigned_blocks">Here are the blocks that are not currently displayed during your experience.<div onmousedown="grabobj(0)" onmousemove="dragobj(0)" onmouseup="dropobj(0)" id="0" name="3" class="miniblock">Another Block [3]</div><div onmousedown="grabobj(1)" onmousemove="dragobj(1)" onmouseup="dropobj(1)" id="1" name="4" class="miniblock">And Another [4]</div><div onmousedown="grabobj(2)" onmousemove="dragobj(2)" onmouseup="dropobj(2)" id="2" name="5" class="miniblock">Poll Voting [5]</div><div onmousedown="grabobj(3)" onmousemove="dragobj(3)" onmouseup="dropobj(3)" id="3" name="6" class="miniblock">SysAdmin Block [6]</div></TD></TR></TABLE>
and the below Javascript:
window.instruct = function(id, instr){
var el = document.getElementById(id);
el.innerHTML = instr;
}
window.blockprefs = function(id, thisblock){
// determine which slot thisblock belongs to
s = id.getAttribute('SLOT');
// identify all the child nodes associated to this slot
c = id.childNodes;
for(var i=1;i < c.length; i++) { // I set i = 1 here because I don't care about the parent element at this time.
name = c[i].getAttribute('NAME');
// console.log(c.length,c[i]);
pos = document.getElementById('block_side'+name);
ord = document.getElementById('block_order'+name);
pos.setAttribute('VALUE',s);
ord.setAttribute('VALUE',i);
console.log(name,pos,ord,c[i]);
}
};
window.grabobj = function(id){
// console.log('moving object: '+id);
// console.log(document.getElementById(id));
// console.log(event.clientX+', '+event.clientY);
thisblock = document.getElementById(id);
thisblock.setAttribute('CLASS','moving');
thisblock.setAttribute('STATUS','click');
thisblock.style.position = 'absolute';
thisblock.style.left = event.pageX - 40;
thisblock.style.top = event.pageY - 20;
// id.addEventListener('mousemove',function(){console.log('moving mouse: x('+event.clientX)+') y('+event.clientY+')';},false);
};
window.drop = function(id, hit) {
id.setAttribute('STATUS','drop');
id.setAttribute('CLASS','miniblock_unsaved');
id.setAttribute('STYLE','');
instruct('instruct','You have unsaved changes, be sure to commit your changes below.');
};
window.dropobj = function(id){
thisblock = document.getElementById(id);
if(thisblock.getAttribute('STATUS') == 'drag' || thisblock.getAttribute('STATUS') == 'click'){
// determine if the block was dropped within one of the drop object targets
// if it was not, return it to the original location, otherwise, drop it into the new location
var left = document.getElementById("leftblocks"),
right = document.getElementById('rightblocks'),
leftbounds = left.getBoundingClientRect(),
rightbounds = right.getBoundingClientRect(),
t = window.pageYOffset || document.documentElement.scrollTop,
l = window.pageXOffset || document.documentElement.scrollLeft;
if(event.clientX >= leftbounds.left && event.clientX <= leftbounds.right && event.clientY >= leftbounds.top && event.clientY <= leftbounds.bottom){
// hit on the left blocks bounds, drop it into the left block
left.appendChild(thisblock);
//thisblock.insertBefore(left.firstchild);
drop(thisblock);
// now assign the hidden form element the correct values based on the current config in the left block then
// here is what I think will have to happen:
// identify all child nodes of the parent node
// identify all of the hidden form fields associated to those child nodes
// update all of the hidden form fields associated to those child nodes with
// the correct order and L / R flag.
// not sure how to complete those tasks at this time.
// console.log( document.getElementById('block_order' + thisblock.getAttribute('ID')));
// console.log( left.childElementCount );
blockprefs(left, thisblock);
} else if(event.clientX >= rightbounds.left && event.clientX <= rightbounds.right && event.clientY >= rightbounds.top && event.clientY <= rightbounds.bottom){
// hit on the right blocks bounds, drop it into the right block
right.appendChild(thisblock);
//thisblock.insertBefore(right.firstchild);
drop(thisblock);
// now assign the hidden form element the correct values based on the current config in the left block then
} else {
// user missed dropping into the left or right box, drop it back into unassigned.
var u = document.getElementById("unassigned_blocks");
u.appendChild(thisblock);
drop(thisblock);
}
}
};
window.dragobj = function(id){
thisblock = document.getElementById(id);
if(thisblock.getAttribute('STATUS') == 'click' || thisblock.getAttribute('STATUS') == 'drag'){
thisblock.style.left = event.pageX - 40;
thisblock.style.top = event.pageY - 20;
thisblock.setAttribute('STATUS','drag');
}
};
window.onload = function() {
// on mouseover change color of leftdiv or right div
var left = document.getElementById("leftblocks");
var right = document.getElementById('rightblocks');
console.log(left.nodeValue);
function block_over(block){ // set the attribute of the block on mouse over
// console.log('setting property of block: '+block);
block.setAttribute('CLASS','dropinto_over');
}
function block_out(block){ // set the attribute of the block on mouse out
block.setAttribute('CLASS','dropinto');
}
left.addEventListener('mouseover',function(){block_over(left); },true); // listener to set the left block's attributes
left.addEventListener('mouseout',function(){block_out(left); },true);
right.addEventListener('mouseover',function(){block_over(right); },true); // listener to set the right block's attributes
right.addEventListener('mouseout',function(){block_out(right); },true);
};
I attempted to put this into a JSFIDDLE https://jsfiddle.net/vt1hcLL6/ but the frames were throwing it off so it might have to just go into a flat html file sorry.
As a user, the intent of this code is to be able to pick up the blocks and move them into the slots (either the left or the right side) blocks, or remove them from those blocks.
After doing so, javascript will set some values in the hidden form fields so that upon saving the page php will grab those values.
Currently it's only setup to do so if you move a block into the left side and yes once I get this all nailed from a hardcoded perspective I will abstract it further, for now some of this is hard coded.
My problem is that upon the first instance of dropping a block into the Left Side Block, everything works fine all the HIDDEN form fields update correctly with the SLOT="L" Attribute and the ORDER="i" attribute (i being the number corresponding to the child iteration.
Great! It works! Now... once a second block is added to that set from down below, the code breaks and I can't figure out why.
The code that is performing this functionality is in the blockprefs( ) function where I iterate through all the child nodes of the Left block and attempt to bring in the hidden form elements belonging to each child. I get a:
divblock.js:33 Uncaught TypeError: Cannot read property 'setAttribute' of null