Hers's question which I need answer
Exercises: Level 1
Question 1
I tried to append the child dynamically and get the result vertical not in the compact manner as you will see in the question output when you go to the link
let hh = document.querySelector('h1')
hh.style.textAlign = 'center'
let hh1 = document.querySelector('h2')
hh1.style.textAlign = 'center'
let hh2 = document.querySelector('h3')
hh2.style.textAlign = 'center'
for (i = 0; i <= 100; i++) {
let p1 = document.createElement('div');
{
if (i % 2 == 0) {
p1.className = 'Container'
p1.style.fontSize = '25px'
p1.style.backgroundColor = '#91E537'
p1.textContent = i;
p1.style.padding = '55px'
p1.style.margin = '1px'
p1.style.textAlign = 'center'
p1.style.width = '20px'
document.body.appendChild(p1);
} else {
p1.className = 'Container'
p1.style.fontSize = '25px'
p1.textContent = i;
p1.style.backgroundColor = '#E5D037'
p1.style.padding = '55px'
p1.style.margin = '1px'
p1.style.textAlign = 'center'
p1.style.width = '20px'
document.body.appendChild(p1);
}
}
if (i >= 2) {
let flag = 0;
for (j = 2; j <= i / 2; j++) {
if (i % j == 0) {
flag = 1;
break;
}
}
if (flag == 0) {
p1.className = 'Container'
p1.style.fontSize = '25px'
p1.style.backgroundColor = '#E55137'
p1.textContent = i;
p1.style.padding = '55px'
p1.style.margin = '1px'
p1.style.textAlign = 'center'
p1.style.width = '20px'
document.body.appendChild(p1);
}
}
}
<h1>Number Generator</h1>
<h2>30 days of JavaScript</h2>
<h3>Author:ABC</h3>
<div class="container"></div>
You can see the code of both HTML and javascript above!!!
Do help with the code where I can easily append the data, and don't use box elements I need simple code for this.
I tried to do it with a few HTML styles but it didn't help me, also using insert-adjacent text also didn't work.
Try to make changes only on javascript code, not HTML,if it's possible else make minimum changes on HTML
I am using HTML5 AND CSS3
Rather than adding lots of inline style attributes to all the generate elements some simple CSS can be applied to the parent (container) and thus the children and then use Javascript to assign the classes to the elements based upon the given criteria of odd/even & prime. The comments throughout the code ought to help
// helper function to create basic DOM element with attributes
const node=( type='div', attr={} )=>{
let n = document.createElement(type);
Object.keys( attr ).forEach( a=>n.setAttribute( a, attr[a] ) );
return n;
};
// utility to test if a number is a "Prime Number"
const isprime=( n=0 )=>{
for( let i = 2; i < n; i++ ) {
if( n % i === 0 ) return false;
}
return n > 1;
};
// generate between these numbers
const iStart=1;
const iEnd=100;
// HTML container
const results = document.querySelector('#results');
// iterate through number range and add new DIV for each number
for( let i = iStart; i<=iEnd; i++ ) {
// calculate the className to assign
let cn=i % 2 === 0 ? 'even' : 'odd';
if( isprime( i ) ) cn='prime';
// create the DIV
let div=node( 'div', { 'class':cn, 'data-id':i } );
// append to output container
results.appendChild( div );
}
// generate Page headers before results / output
let headers={
h1:'Number Generator',
h2:'30 days of Javascript',
h3:'Geronimo Bogtrotter III'
};
// iterate over the keys/properties of the object and create
// a new header for each using the value assigned within the object.
Object.keys( headers ).forEach( prop=>{
let n=node( prop );
n.textContent=headers[ prop ];
// add the new header before the numbers grid
document.body.insertBefore( n, results );
});
/*
Some simple variables to help with layout.
Change here to modify displayed grid.
*/
:root{
--width:600px;
--m:0.25rem;
--r:0.35rem;
--wh:calc( calc( var( --width ) / 5 ) - calc( var( --m ) * 4 ) );
}
body{
font-family:monospace;
padding:0;
margin:0;
}
#results {
width: var(--width);
min-height: var(--width);
float: none;
margin: 1rem auto;
font-family:fantasy;
}
#results > div {
width: var( --wh );
height: var( --wh );
border: 1px solid black;
border-radius:var(--r);
margin: var(--m);
display: inline-block;
cursor:pointer;
}
/*
Use the pseudo element :after to
display the number of the square.
This uses the `data-id` attribute
assigned within Javascript.
*/
#results>div:after {
display:flex;
flex-direction:column;
justify-content:center;
align-content:center;
align-items:center;
flex:1;
margin:auto;
content: attr(data-id);
color:black;
text-shadow:0 0 15px rgba(255,255,255,1);
height:100px;
}
/*
modify the display for certain colours
to aid clarity
*/
#results>div.odd:after{
text-shadow:0 0 15px rgba(0,0,0,1);
}
#results>div.prime:after{
text-shadow:0 0 15px rgba(0,0,0,1);
color:white;
}
/*
The basic 3 colours to suit the odd/even/prime
status of each square.
*/
.even {
background: green;
}
.odd {
background: yellow
}
.prime {
background: red
}
h1,h2,h3{
text-align:center;
}
<div id='results'></div>
The idea here is to create a container for all of the blocks and set the display style attribute of this container to flex, style.flewrap to wrap and you can control how many blocks you want per line using style.width attribute.
After creating this element you would want to append to it your dynamically created blocks like p2.appendchild(p1);
Here is the code :
let p2 = document.createElement('div');
p2.className= 'p2';
p2.style.display = 'flex';
p2.style.flexwrap = 'wrap';
p2.style.width = '800px'
for (i = 0; i <= 101; i++) {
...
for every document.body.append(p1); --> p2.append(p1);
...
}
document.body.appendChild(p2);
Related
I am trying to build a simple minesweeper game in Javascript. It works propererly apart from the function to open the entire mine-free area when clicking on a mine-free tile. It starts checking the neighbouring tiles, but stops when the first neighbouring tile has a mine.
As you can see on the screenshot below (after clicking on tile 1/5) only the tiles until the first "1" are opened. It should actually open a much larger area:
It seems I am pretty close. THis is my code:
const gridSize = 10
// generate grid
const board = document.querySelector("#minesweeper");
// loop over num for rows
let header = 0;
for(let i = 0; i < gridSize+1; i++) {
const row = document.createElement("tr");
// loop over num for cols
for (let j = 0; j < gridSize+1; j++) {
// add col to row
if ( i === 0 ) {
row.insertAdjacentHTML("beforeend", `<th>${header}</th>`);
header += 1;
} else if (j === 0) {
row.insertAdjacentHTML("beforeend", `<th>${header-10}</th>`);
header += 1;
} else {
row.insertAdjacentHTML("beforeend", `<td class='unopened' dataset-column=${j}></td>`);
};
};
// add row to board
board.append(row);
};
// functions -------------------
function getNeighbour(tile, i, j) {
const column = tile.cellIndex; // so the columns get the cellIndex
const row = tile.parentElement.rowIndex; // row gets the rowIndex(tr)
const offsetY = row + i;
const offsetX = column + j;
return document.querySelector(`[data-row="${offsetY}"][data-column="${offsetX}"]`);
}
// count mines of neighbours
function countMines(tile) {
let mines = 0;
for(i = -1; i <= 1; i++) {
for(j = -1; j <= 1; j++ ) {
// check if neighbour has mine
// get cell values from neighbour in DOM
nb = getNeighbour(tile, i, j);
if (nb && nb.classList.contains('has-mine') || (nb && nb.classList.contains('mine'))) mines += 1; // if nb exists and has a mine increase mines
}
}
// write into DOM
if (mines === 0) {
tile.classList.add(`opened`);
} else {
tile.classList.add(`neighbour-${mines}`);
}
tile.classList.remove(`unopened`);
// if mines are 0, go to neigbours and count mines there
// console.log(tile.classList);
if (mines === 0) {
// alert("mines are zero");
for (i = -1; i <= 1; i+=1) {
for (j = -1; j <= 1; j+=1) {
nb = getNeighbour(tile, i, j);
if (nb && nb.classList.contains("unopened")) {
countMines(nb);
}
}
}
}
return mines;
}
// function open tile on click
function openTile(event) {
const tile = event.currentTarget;
// if there is a mine you lose
if (tile.classList.contains("has-mine")) {
document.querySelectorAll(".has-mine").forEach((cell) => {
cell.classList.remove("has-mine", "unopened");
cell.classList.add("mine", "opened");
});
alert("booooooooom!");
} else {
countMines(tile);
}
}
const tiles = document.querySelectorAll("td");
tiles.forEach((td) => {
td.dataset.column = td.cellIndex; // so the columns get the cellIndex
td.dataset.row = td.parentElement.rowIndex; // row gets the rowIndex(tr)
// add mines randomly
const freq = 0.1;
if (Math.random() < freq) {
td.classList.add("has-mine");
}
// eventlisteners per tile
td.addEventListener("click", openTile);
});
I have been thinking hours about it but could not find a way to work on with this code. Not sure if I am close or if I would need to modify the whole approach?
Many thanks for any ideas!
the principle is simple, for each empty cell, you must add all the adjacent empty cells.
it is also necessary to collect the number of adjacent mines of each cell
a) list the 8 adjacent cells, except for the cells placed at the edge
this is the prxElm() function in my code
b) count the mines present around a cell -> prxMne()
starting from the first cell
1- we count (a) nearby mines
2- it becomes the first element of a stack of cells to be mapped
3- if its number of nearby mines is zero, repeat this operation for all adjacent cells
the particularity of this algorithm is to use only one stack to accumulate the coordinates to be mapped.
it places the elements with adjacent mines at the top of the stack, and those with none at the end of the stack.
as there can be several cells without adjacent mines, we keep an indexiExp of the last empty cell processed.
of course when you add a cell with mines nearby at the start of the stack, this index is shifted.
the algorithm also take care not to add duplicate the same cell by checking before if this cell is not in the stack.
see .filter(x=>!explor.some(e=>e.p===x.p))
this ends when the exploration index iExp reaches the end of the stack.
here is the whole code, it is not completely finalized, but the essentials are there.
const
MinesCount = 17 // adjusted values to fit this snippet display area
, gridSz = { r:7, c:20 } // grid rows x cols
, gridMx = gridSz.r * gridSz.c
, proxim = [ {v:-1,h:-1}, {v:-1,h:0}, {v:-1,h:+1}, {v:0,h:-1}, {v:0,h:+1}, {v:+1,h:-1}, {v:+1,h:0}, {v:+1,h:+1} ]
, prxElm = (r,c) => proxim.reduce((a,{v,h})=>
{
let rv = r+v, ch = c+h;
if (rv>=0 && ch>=0 && rv<gridSz.r && ch<gridSz.c) a.push({p:((rv * gridSz.c) + ch), r:rv, c:ch} )
return a
},[])
, GenNbX = (nb,vMax) => [null].reduce(arr=>
{
while (arr.length < nb)
{
let numGen = Math.floor(Math.random() * vMax)
if (!arr.includes(numGen)) arr.push(numGen);
}
return arr //.sort((a,b)=>a-b)
},[])
, minesP = GenNbX( MinesCount, gridMx )
, prxMne = (r,c) => prxElm(r,c).reduce((a,{p})=>minesP.includes(p)?++a:a,0) // count mines arroub=nd
, td2rcp = td =>
{
let r = td.closest('tr').rowIndex -1 // -1 for thead count of rows
, c = td.cellIndex
, p = (r * gridSz.c) +c
return {r,c,p}
}
, p2rc = p =>({r: Math.floor(p / gridSz.c), c: (p % gridSz.c)})
, { timE, cFlags, minesArea } = drawTable('mines-area', gridSz, MinesCount )
;
const chrono = (function( timeElm )
{
const
one_Sec = 1000
, one_Min = one_Sec * 60
, twoDgts = t => (t<10) ? `0${t}` : t.toString(10)
, chronos =
{ timZero : null
, timDisp : timeElm
, timIntv : null
, running : false
}
, obj =
{ start()
{
if (chronos.running) return
chronos.timDisp.textContent = '00:00'
chronos.running = true
chronos.timZero = new Date().getTime()
chronos.timIntv = setInterval(() =>
{
let tim = (new Date().getTime()) - chronos.timZero
chronos.timDisp.textContent = `${Math.floor(tim/one_Min)}:${twoDgts(Math.floor((tim % one_Min)/one_Sec))}`
}
, 250);
}
, stop()
{
if (!chronos.running) return
chronos.running = false
clearInterval( chronos.timIntv )
}
}
return obj
}(timE))
function drawTable(tName, gSz, mines )
{
let table = document.getElementById(tName)
// table.innerHTML = '' // eraze table
let tHead = table.createTHead()
, tBody = table.createTBody()
, xRow = tHead.insertRow()
, timE = xRow.insertCell()
, cFlags = xRow.insertCell()
;
timE.setAttribute('colspan', gSz.c -4)
timE.className = 'time'
timE.textContent = '0:00'
cFlags.setAttribute('colspan', 4)
cFlags.className = 'flag'
cFlags.textContent = ' 0/' + mines
for (let r=gSz.r;r--;)
{
xRow = tBody.insertRow()
for(let c = gSz.c;c--;) xRow.insertCell()
}
return { timE, cFlags, minesArea: tBody }
}
minesArea.onclick = ({target}) =>
{
if (!target.matches('td')) return
if (target.hasAttribute('class')) return // already done
chrono.start()
let {r,c,p} = td2rcp(target)
if (minesP.includes(p)) // you are dead!
{
chrono.stop()
minesArea.className = 'Boom'
minesP.forEach(p=> // show mines
{
let {r,c} = p2rc(p)
let td = minesArea.rows[r].cells[c]
if (!td.hasAttribute('class')) td.className = 'mineOff'
})
minesArea.rows[r].cells[c].className = 'mineBoom' // this one is for you
minesArea.querySelectorAll('td:not([class]), td.flag') // jusr disable click
.forEach(td=>td.classList.add('off')) // and cursor
}
else
{
let explor = [ {p, r, c, m:prxMne(r,c) } ]
, iExp = 0
;
while (iExp < explor.length && explor[iExp].m === 0) // Open mine-free area
{
prxElm(explor[iExp].r,explor[iExp].c) // look around
.filter(x=>!explor.some(e=>e.p===x.p)) // if not already in
.forEach(x=>
{
M = prxMne(x.r,x.c)
if (M>0 ) { explor.unshift( { p:x.p, r:x.r, c:x.c, m:M} ); iExp++ }
else explor.push( { p:x.p, r:x.r, c:x.c, m:M} ) // mine-free space
})
iExp++
}
explor.forEach(({r,c,m})=>minesArea.rows[r].cells[c].className = 'm'+m )
}
if (checkEnd()) // some kind of victory!?
{
chrono.stop()
minesArea.querySelectorAll('td.flag').forEach(td=>td.classList.add('off'))
minesArea.className = 'win'
}
}
minesArea.oncontextmenu = e => // Yes, there is a right click for flag mines
{
if (!e.target.matches('td')) return
e.preventDefault()
let {r,c,p} = td2rcp( e.target)
, cell_rc = minesArea.rows[r].cells[c]
;
if (!cell_rc.hasAttribute('class')) cell_rc.className = 'flag'
else if (cell_rc.className === 'flag') cell_rc.removeAttribute('class')
let nbFlags = minesArea.querySelectorAll('td.flag').length
cFlags.textContent = ` ${nbFlags} / ${MinesCount}`
}
function checkEnd()
{ // what about us ?
let count = 0
, reject = 0
, tdNotSeen = minesArea.querySelectorAll('td:not([class])')
, flagPos = minesArea.querySelectorAll('td.flag')
;
cFlags.textContent = ` ${flagPos.length} / ${MinesCount}`
if (tdNotSeen.length > MinesCount ) return false
flagPos.forEach(td=>
{
let {r,c,p} = td2rcp(td)
if (minesP.includes(p)) count++ // correct place
else reject++
})
tdNotSeen.forEach(td=>
{
let {r,c,p} = td2rcp(td)
if (minesP.includes(p)) count++
else reject++ // no mines there
})
if (count != MinesCount || reject != 0 ) return false
tdNotSeen.forEach(td=>
{
let {r,c,p} = td2rcp(td)
minesArea.rows[r].cells[c].className = 'mineOff'
})
cFlags.textContent = ` ${MinesCount} / ${MinesCount}`
return true
}
body { background-color: #383947; } /* dark mode ? ;-) */
table {
border-collapse : collapse;
margin : 1em auto;
--szRC : 18px;
font-family : Arial, Helvetica, sans-serif;
}
table td {
border : 1px solid #1a1a1a80;
text-align : center;
}
table thead {
font-size : .8em;
background-color : #c3c5db;
}
table tbody {
background-color : #a39999;
cursor : cell;
}
table tbody td {
width : var(--szRC);
height : var(--szRC);
overflow : hidden;
}
.m0, .m1, .m2, .m3, .m4, .m5, .m6, .m7, .m8 { background-color: whitesmoke; font-size: 12px; font-weight: bold; cursor: default; }
.m1::after { content: '1'; color: #0000ff; }
.m2::after { content: '2'; color: #008000; }
.m3::after { content: '3'; color: #ff0000; }
.m4::after { content: '4'; color: #000080; }
.m5::after { content: '5'; color: #800000; }
.m6::after { content: '6'; color: #008080; }
.m7::after { content: '7'; color: #000000; }
.m8::after { content: '8'; color: #808080; }
.off { cursor: default; }
.Boom { background-color: yellow; cursor: default; }
.mineOff { cursor: default; padding: 0; }
.flag { background-color: lightgray; padding: 0; }
.mineBoom { color: crimson; padding: 0; }
.mineOff::after,
.mineBoom::after { content: '\2738'; }
.flag::before { content: '\2691'; color: crimson; }
.time::before { content: 'Time elapsed : '; color: darkblue; }
.win td { border-color: gold;}
<table id="mines-area"></table>
I don't think a recursive method is suitable for this kind of problem.
It requires having a complex strategy for exploring empty spaces.
For example, spiraling around the starting point.
But this strategy comes up against the problem of the island hindering this progress, and which requires, once crossed, to carry out a new spiral advance to recover the points hidden during the previous spiral, but having to avoid the points already explored during the previous spiral.
You can also move forward by sectors from an initial point, but you will still encounter the same problem of the islet and its backtracking, which also multiply here with each roughness on the explored shores.
This requires complex calculations that are difficult to master and debug, not to mention the fact that recursive methods intensively use call stacks which it adds up for each new branch.
(Without neglecting the risk of going crazy, by striving to develop its recursive algorithms.)
The larger the grid and the more its recursive lists will conflict with each other, and more the computationnal time will be affected.
Of course there are already winning heuristics on this type of strategy, they are of a very high level, and we are here just on a minesweeper game where hundreds of lines of code have nothing to do.
My method only uses a single stack, its algorithm is easy to understand and requires a few lines of code.
What more ?
I have a button with a default background color(black). I want to change the background color (from an array) of this button when hovering over it. I have it working at a fundamental level, but I want it to repeat the loop over and over.
this is what i have so far.
var color = ['#3e50a2', '#faa51a', '#ed1c24', '#2a9446'];
var i = -1;
document.querySelector('.customBtn').addEventListener('mouseover', function() {
i = 1 < color.length ? ++i : 0;
document.querySelector('.customBtn').style.background = color[i]
});
document.querySelector('.customBtn').addEventListener('mouseout', function() {
document.querySelector('.customBtn').style.background = '#000';
})
<a class="customBtn">Button</a>
I think you tried to reset the value of i with this i = 1 < color.length ? ++i : 0; but it doesn't. This will continuously increase the value never resetting it because 1 is always less than the length of the array.
I think you meant to increment the value and then reset if it's too big:
i = ++i < color.length ? i : 0;
Here's the complete code. I've refactored the query selector as there's no point to doing it more than once, and changed the mouseout background colour so you can read the button
var color = ['#3e50a2', '#faa51a', '#ed1c24', '#2a9446'];
var i = -1,
btn = document.querySelector('.customBtn');
btn.addEventListener('mouseover', function() {
i = ++i < color.length ? i : 0;
btn.style.background = color[i];
});
btn.addEventListener('mouseout', function() {
// revert to default colour
btn.style.background = '';
})
<input type="button" class="customBtn" value="My button" />
Just replace i = 1 < color.length ? ++i : 0; with i = (i+1 < color.length) ? ++i : 0;. That's all.
var color = ['#3e50a2', '#faa51a', '#ed1c24', '#2a9446'];
var i = -1;
document.querySelector('.customBtn').addEventListener('mouseover', function() {
i = (i+1 < color.length) ? ++i : 0;
document.querySelector('.customBtn').style.background = color[i]
});
document.querySelector('.customBtn').addEventListener('mouseout', function() {
document.querySelector('.customBtn').style.background = '#000';
})
<a class="customBtn">Button</a>
If you must use javascript, take a look at the second example.
Pure CSS solution
You could accomplish this with a CSS animation, which would be both more efficient and less error prone. Unless there's a specific reason you need to use javascript here I'd strongly recommend this approach.
This example could be modified to do hard transitions instead of fading from one color to the next, but here's a quick demo:
button {
background: black;
color: white;
border: none;
padding: 0.5em 1em;
}
button:hover {
animation: buttonhover 1s infinite;
}
#keyframes buttonhover {
0% {
background: #3e50a2;
}
25% {
background: #faa51a;
}
50% {
background: #ed1c24;
}
75% {
background: #2a9446;
}
}
<button>Hello</button>
Javascript Solution
If you must use javascript for whatever reason, you can use the % operator to keep from running off the end of the colors array:
const colors = ['#3e50a2', '#faa51a', '#ed1c24', '#2a9446'];
let index = 0;
let interval;
const hover = (e) => {
interval = setInterval(() => {
e.target.style.backgroundColor = colors[index];
index = (index + 1) % colors.length;
}, 300);
}
document.querySelector('button').addEventListener('mouseover', hover);
document.querySelector('button').addEventListener('mouseout', (e) => {
clearInterval(interval);
e.target.style.backgroundColor = 'black';
});
button {
background: black;
color: white;
border: none;
padding: 0.5em 1em;
}
<button>Hello</button>
I am working on an Etch-A-Scetch project. I created grid which contains a certain amount of squares of the same size (the user is able to type in the amount of squares which should be displayed). To create the squares I used CSS grid and a Javascript for loop. Now I want to add event listeners, which change the background of each Square when moving over it. Unfortunately, it always shows errors when I try to add some. The current code doesn't show an error, it just doesn't do anything.
The method createSquares() should just create and add the amount of squares to the DOM. The user types in an amount, for example 10, and the displayed squares are 10 in x-direction and 10 in y-direction --> makes 100 squares in total. After that I want to add an event listener, which changes the background color of the square the user hovers over (the background color should stay changed). I am thankful for any help, because I'm really clueless :D
let squareDiv = document.querySelector('.squareDiv');
let squares = document.getElementById('#squares')
let squareAmount = 10;
function blackColor() {
this.style.backgroundColor = '#000';
this.style.border = '0px';
}
function createSquares() {
for (i = 0; i < squareAmount * squareAmount; i++) {
squares = document.createElement('div');
squares.setAttribute("id", "squares");
// squares.setAttribute("onmouseover", "addEventListener")
squares.style.display = 'grid';
squareDiv.style.setProperty('--columns-amount', squareAmount);
squareDiv.style.setProperty('--rows-amount', squareAmount);
squareDiv.appendChild(squares);
}
}
createSquares();
if (squares) {
squares.addEventListener('mouseover', _ => {
squares.style.backgroundColor = blackColor;
});
}
<div class="squareDiv"></div>
<div id="squares"></div>
You likely need something like this
I fixed the script, now fix the CSS
let container = document.getElementById("container")
let squareAmount = 5;
function getRandom() {
return '#'+Math.floor(Math.random()*16777215).toString(16);
}
function colorIt(sq) {
sq.style.backgroundColor = document.getElementById("random").checked? getRandom() : '#000';
sq.style.border = '0px';
}
function createSquares() {
let grid = document.createElement('div');
grid.setAttribute("id","squares")
grid.classList.add("grid");
for (i = 0; i < squareAmount * squareAmount; i++) {
square = document.createElement('div');
square.classList.add("square");
grid.appendChild(square);
}
container.innerHTML="";
container.appendChild(grid)
}
createSquares();
container.addEventListener('mouseover',
e => {
const target = e.target;
if (target.matches(".square")) colorIt(target)
}
);
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
grid-auto-rows: 1fr;
}
.grid::before {
content: '';
width: 0;
padding-bottom: 100%;
grid-row: 1 / 1;
grid-column: 1 / 1;
}
.grid > *:first-child {
grid-row: 1 / 1;
grid-column: 1 / 1;
}
/* Just to make the grid visible */
.grid > * {
background: rgba(0,0,0,0.1);
border: 1px white solid;
}
<label><input type="checkbox" id="random" />Random</label>
<div id="container"></div>
You have already created the element in DOM, please remove this.
while creating the element using function createSquares assign class instead of ID. Since, you should have only element with one ID.
Move the addEventListener inside the function after you have created the element.
When creating similar html elements with same properties it is better to group them together with class and not id. This is good because it becomes simple to loop these html elements with forEach or other looping methods you may prefer.
let squareDiv = document.querySelector('.squareDiv');
let squares = document.getElementById('#squares')
let squareAmount = 10;
function blackColor() {
this.style.backgroundColor = '#000';
this.style.border = '0px';
}
function createSquares() {
for (i = 0; i < squareAmount * squareAmount; i++) {
squares = document.createElement('div');
squares.setAttribute("class", "squares");
squares.setAttribute("style", "width: 100px; height: 100px; background: #090; margin-bottom: .3rem;");
// squares.setAttribute("onmouseover", "addEventListener")
squares.style.display = 'grid';
squareDiv.style.setProperty('--columns-amount', squareAmount);
squareDiv.style.setProperty('--rows-amount', squareAmount);
squareDiv.appendChild(squares);
}
}
createSquares();
if (squares) {
squares.addEventListener('mouseover', _ => {
squares.style.backgroundColor = blackColor;
});
}
<div class="squareDiv"></div>
<div id="squares"></div>
I'm trying to practice my scripting by making a Battleship game. As seen here.
I'm currently trying to make the board 2D. I was able to make a for-loop in order to make the board, however, due to testing purposes, I'm just trying to make the board, upon clicking a square, it turns red... But, the bottom square always lights up. I tried to debug it by making the c value null, but then it just stops working. I know it's not saving the c value properly, but I'm wondering how to fix this.
Do I have to make 100 squares by my self or can I actually have the script do it?
maincansize = 400;
document.getElementById("Main-Canvas").style.height = maincansize;
document.getElementById("Main-Canvas").style.width = maincansize;
document.getElementById("Main-Canvas").style.position = "relative";
var ize = maincansize * .1;
for (var a = 0; a < 10; a++) {
for (var b = 0; b < 10; b++) {
var c = document.createElement("div");
var d = c;
c.onclick = function() {
myFunction()
};
function myFunction() {
console.log("A square was clicked..." + c.style.top); d.style.backgroundColor = "red";
}
c.style.height = ize;
c.style.width = ize;
c.style.left = b * ize;
c.style.top = a * ize;
c.style.borderColor = "green";
c.style.borderStyle = "outset";
c.style.position = "absolute";
console.log(ize);
document.getElementById('Main-Canvas').appendChild(c);
} //document.getElementById('Main-Canvas').innerHTML+="<br>";
}
#Main-Canvas {
background-color: #DDDDDD;
}
<div>
<div id="header"></div>
<script src="HeaderScript.js"></script>
</div>
<div id="Main-Canvas" style="height:400;width:400;">
</div>
Here's your code with some fixes:
adding 'px' to style assignment
passing the clicked element to myFunction
var maincansize = 400;
document.getElementById("Main-Canvas").style.height = maincansize;
document.getElementById("Main-Canvas").style.width = maincansize;
document.getElementById("Main-Canvas").style.position = "relative";
var ize = maincansize * .1;
for (var a = 0; a < 10; a++) {
for (var b = 0; b < 10; b++) {
var c = document.createElement("div");
c.onclick = function(ev) {
myFunction(ev.currentTarget);
};
function myFunction(el) {
console.log("A square was clicked...");
el.style.backgroundColor = "red";
}
c.style.height = ize+'px';
c.style.width = ize+'px';
c.style.left = (b * ize)+'px';
c.style.top = (a * ize)+'px';
c.style.borderColor = "green";
c.style.borderStyle = "outset";
c.style.position = "absolute";
document.getElementById('Main-Canvas').appendChild(c);
}
}
#Main-Canvas {
background-color: #DDDDDD;
}
<div id="Main-Canvas" style="height:400;width:400;">
</div>
Here's a solution with major revamps. Since you're using a set width for the container element of your board cells you can float the cells and they will wrap to the next line. Absolute positioning tends to be a bit of a bugger. If you want 10 items per row it's as easy as:
<container width> / <items per row> = <width>
Using document fragments is faster than appending each individual element one at a time to the actual DOM. Instead you append the elements to a document fragment that isn't a part of the DOM until you append it. This way you're doing a single insert for all the cells instead of 100.
I moved some of the styling to CSS, but could easily be moved back to JS if you really need to.
function onCellClick() {
this.style.backgroundColor = 'red';
console.log( 'selected' );
}
var main = document.getElementById( 'board' ),
frag = document.createDocumentFragment(),
i = 0,
len = 100;
for ( ; i < len; i++ ) {
div = document.createElement( 'div' );
div.addEventListener( 'click', onCellClick, false );
frag.appendChild( div );
}
main.appendChild( frag );
#board {
width: 500px;
height: 500px;
}
#board div {
float: left;
box-sizing: border-box;
width: 50px;
height: 50px;
border: 1px solid #ccc;
}
<div id="board"></div>
PURE JS ONLY PLEASE - NO JQUERY
I have a div with overflow scroll, the window (html/body) never overflows itself.
I have a list of anchor links and want to scroll to a position when they're clicked.
Basically just looking for anchor scrolling from within a div, not window.
window.scrollTo etc. don't work as the window never actually overflows.
Simple test case http://codepen.io/mildrenben/pen/RPyzqm
JADE
nav
a(data-goto="#1") 1
a(data-goto="#2") 2
a(data-goto="#3") 3
a(data-goto="#4") 4
a(data-goto="#5") 5
a(data-goto="#6") 6
main
p(data-id="1") 1
p(data-id="2") 2
p(data-id="3") 3
p(data-id="4") 4
p(data-id="5") 5
p(data-id="6") 6
SCSS
html, body {
width: 100%;
height: 100%;
max-height: 100%;
}
main {
height: 100%;
max-height: 100%;
overflow: scroll;
width: 500px;
}
nav {
background: red;
color: white;
position: fixed;
width: 50%;
left: 50%;
}
a {
color: white;
cursor: pointer;
display: block;
padding: 10px 20px;
&:hover {
background: lighten(red, 20%);
}
}
p {
width: 400px;
height: 400px;
border: solid 2px green;
padding: 30px;
}
JS
var links = document.querySelectorAll('a'),
paras = document.querySelectorAll('p'),
main = document.querySelector('main');
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', function(){
var linkID = this.getAttribute('data-goto').slice(1);
for (var j = 0; j < links.length; j++) {
if(linkID === paras[j].getAttribute('data-id')) {
window.scrollTo(0, paras[j].offsetTop);
}
}
})
}
PURE JS ONLY PLEASE - NO JQUERY
What you want is to set the scrollTop property on the <main> element.
var nav = document.querySelector('nav'),
main = document.querySelector('main');
nav.addEventListener('click', function(event){
var linkID,
scrollTarget;
if (event.target.tagName.toUpperCase() === "A") {
linkID = event.target.dataset.goto.slice(1);
scrollTarget = main.querySelector('[data-id="' + linkID + '"]');
main.scrollTop = scrollTarget.offsetTop;
}
});
You'll notice a couple of other things I did different:
I used event delegation so I only had to attach one event to the nav element which will more efficiently handle clicks on any of the links.
Likewise, instead of looping through all the p elements, I selected the one I wanted using an attribute selector
This is not only more efficient and scalable, it also produces shorter, easier to maintain code.
This code will just jump to the element, for an animated scroll, you would need to write a function that incrementally updates scrollTop after small delays using setTimeout.
var nav = document.querySelector('nav'),
main = document.querySelector('main'),
scrollElementTo = (function () {
var timerId;
return function (scrollWithin, scrollTo, pixelsPerSecond) {
scrollWithin.scrollTop = scrollWithin.scrollTop || 0;
var pixelsPerTick = pixelsPerSecond / 100,
destY = scrollTo.offsetTop,
direction = scrollWithin.scrollTop < destY ? 1 : -1,
doTick = function () {
var distLeft = Math.abs(scrollWithin.scrollTop - destY),
moveBy = Math.min(pixelsPerTick, distLeft);
scrollWithin.scrollTop += moveBy * direction;
if (distLeft > 0) {
timerId = setTimeout(doTick, 10);
}
};
clearTimeout(timerId);
doTick();
};
}());
nav.addEventListener('click', function(event) {
var linkID,
scrollTarget;
if (event.target.tagName.toUpperCase() === "A") {
linkID = event.target.dataset.goto.slice(1);
scrollTarget = main.querySelector('[data-id="' + linkID + '"]');
scrollElementTo(main, scrollTarget, 500);
}
});
Another problem you might have with the event delegation is that if the a elements contain child elements and a child element is clicked on, it will be the target of the event instead of the a tag itself. You can work around that with something like the getParentAnchor function I wrote here.
I hope I understand the problem correctly now: You have markup that you can't change (as it's generated by some means you have no control over) and want to use JS to add functionality to the generated menu items.
My suggestion would be to add id and href attributes to the targets and menu items respectively, like so:
var links = document.querySelectorAll('a'),
paras = document.querySelectorAll('p');
for (var i = 0; i < links.length; i++) {
links[i].href=links[i].getAttribute('data-goto');
}
for (var i = 0; i < paras.length; i++) {
paras[i].id=paras[i].getAttribute('data-id');
}