JavaScript: stop a function when another one is active - javascript

I am working on a sketchpad and basically I would like to allow the user to switch between hover-draw (hovering over divs will change their color, it's the default mode) and click-draw (you have to actually click the divs to color them).
I added a button which change the value of clickOn/hoverOn and I want the functions to work only if their associated variables are "on".
However when I click the button and hoverOn value becomes false, the hover function still executes and the divs get colored anyway.
Can anyone point me in the right direction?
EDIT: full code:
const container = document.querySelector('.container');
const newSize = document.querySelector('#grid-resize');
const clearBtn = document.querySelector('#clear');
const resetBtn = document.querySelector('#reset');
const randomBtn = document.querySelector('#random-color');
const modeBtn = document.querySelector('#change-mode');
const eraser = document.querySelector('#eraser');
let size = 16;
let hoverOn = true;
let clickOn = false;
let color = '#72A0C1';
newSize.addEventListener('input', changeSize);
clearBtn.addEventListener('click', clearGrid);
resetBtn.addEventListener('click', resetGrid);
modeBtn.addEventListener('click', changeMode);
randomBtn.addEventListener('click', drawRainbow);
eraser.addEventListener('click', () => {
if (color == 'white') {
color = '#72A0C1';
eraser.style.border = 'none';
} else {
color = 'white';
eraser.style.border = '2px dotted #FFC0CB';
}
});
let isMousedown = false;
container.addEventListener('mousedown', ()=>{isMousedown = true;});
container.addEventListener('mouseup', ()=>{isMousedown = false;});
function changeSize() {
const cells = document.querySelectorAll('.cell');
cells.forEach(cell => {
cell.remove();
});
size = newSize.value;
generateDiv(size);
}
function clearGrid() {
const cells = document.querySelectorAll('.cell');
cells.forEach(cell => {
cell.style.backgroundColor = 'white';
});
}
function resetGrid() {
const cells = document.querySelectorAll('.cell');
cells.forEach(cell => {
cell.remove();
});
size = 16;
newSize.value = size;
generateDiv();
}
function changeMode() {
if (hoverOn) {
hoverOn = false;
clickOn = true;
} else if (clickOn) {
clickOn = false;
hoverOn = true;
}
console.log (hoverOn, clickOn)
}
function setHoverMode() {
if (hoverOn == true) {
container.addEventListener('mouseover', function (e) {
e.target.style.background = `${color}`;
});
}
}
function setClickMode() {
if (clickOn == true) {
container.addEventListener('mousemove', (e) => {
if (isMousedown) {
e.target.style.backgroundColor = `${color}`;
}
});
container.addEventListener('mousemove', e => e.preventDefault());
}
}
function generateColor() {
let randomColor = '#';
let characters = 'ABCDEF0123456789';
for(let i=0; i < 6; i++) {
randomColor += characters.charAt(Math.floor(Math.random() * characters.length));
color = randomColor;
}
}
function drawRainbow() {
container.addEventListener('mousemove', ()=>{
generateColor();
});
}
function generateDiv() {
container.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
container.style.gridTemplateRows = `repeat(${size}, 1fr)`;
for (let i = 0; i < size * size; i++) {
let cell = document.createElement('div');
cell.classList.add('cell');
container.appendChild(cell);
}
}
generateDiv(size);
html, body {
margin: 0;
padding: 20px;
width: 100%;
height: 100%;
box-sizing: border-box;
text-align: center;
}
h1 {
margin-top: 0;
}
.content {
display: flex;
justify-content: center;
gap: 5em;
}
.content .settings {
display: flex;
flex-direction: column;
width: 15%;
}
.content .grid-resize {
text-align: start;
margin-bottom: 2em;
}
.content .buttons {
display: flex;
flex-wrap: wrap;
gap: 1.5em;
}
.content .buttons button {
border: none;
box-shadow: 1px 1px rgba(128, 128, 128, 0.41);
border-radius: 15px;
width: 70px;
height: 70px;
}
.content .buttons button:hover {
box-shadow: 2px 2px #176fae;
background-color: #72A0C1;
color: white;
transform: scale(1.2);
}
.content .buttons button:active {
box-shadow: none;
background-color: #72A0C1;
color: white;
transform: scale(1);
}
.content .buttons button .eraser-border {
border: 1px solid black;
}
.container {
display: grid;
width: 400px;
height: 400px;
}
.container .cell {
background-color: white;
border: 1px solid rgba(220, 220, 220, 0.3);
}
<body>
<div class="main">
<h1>Draw something nice!</h1>
<div class="content">
<div class="settings">
<div class="grid-resize">
<label for="grid-resize">Change the grid size:</label>
<input type="range" id="grid-resize" min="4" max="100">
</div>
<div class="buttons">
<button id="clear" class="settings-buttons" title="Click here to clear the board">Clear All</button>
<button id="reset" class="settings=buttons" title="Click here to go back to original settings - this will clear the board">Reset</button>
<button id="change-mode" class="settings-buttons" title="Click here to change the mode">Draw Mode</button>
<button id="random-color" class="settings-buttons">Random Color</button>
<button id="black-shades" class="settings-buttons">Black Shades</button>
<button id="eraser" class="settings-buttons"><img src="images/rubber.svg">Eraser</button>
</div>
</div>
<div class="container">
</div>
</div>
</div>

You were close, just move mouseover/mousemove listeners outside of setHoverMode/setClickMode functions and do your condition checks inside those event listeners like below
container.addEventListener("mouseover", function (e) {
if (hoverOn) {
e.target.style.background = `${color}`;
}
});
container.addEventListener("mousemove", (e) => {
if (clickOn && isMousedown) {
e.target.style.backgroundColor = `${color}`;
}
});

Related

forEach eventListener iteration increases every time the function is called

I'm creating a simple etch-a-sketch that colors the created squares on a click and drag event.
When you run below code please note that in the very beginning, every div.square that is being catched by eventListener returns exactly one div.square and I consider this as a normal behaviour.
Now, every time you click on a "Rainbow mode" button a function listening('rainbow') is being called and now a single div.square is being returned twice (I added a console.log to catch it). When you press "Rainbow mode" again a single div.square will be called 3 times and it increases in this manner.
Why is it happening? Why when I run a debugger I can see that eventListener section of listening() function is being iterated several times and the number of iterations increases every time listening() function is being called?
const board = document.querySelector('#board');
let color = 'black';
function createSquares(squareSize) {
squareSize = (480/squareSize);
for (i=0; i<Math.pow((480/squareSize), 2); i++) {
const square = document.createElement('div');
square.classList.add('square');
square.style.cssText = `
width: ${squareSize}px;
height: ${squareSize}px;
background-color: transparent;`;
board.appendChild(square);
};
}
function listening(color) {
if (color === 'rainbow') {
console.log("rainbow selected");
const squaresAll = document.querySelectorAll('div.square');
squaresAll.forEach((square) => {
square.addEventListener('mouseover', (e) => {
if (e.buttons === 1 || e.buttons === 3) {
console.log(e.currentTarget);
let rainbowColor = "#" + ((1<<24)*Math.random() | 0).toString(16);
e.currentTarget.style.backgroundColor = rainbowColor;
return;
}
})
})
}
else {
console.log("other color selected");
const squaresAll = document.querySelectorAll('div.square');
squaresAll.forEach((square) => {
square.addEventListener('mouseover', (e) => {
if (e.buttons == 1 || e.buttons == 3) {
console.log(e.currentTarget);
e.target.style.backgroundColor = color;
}
})
})
}
}
function resetBoard() {
const squaresAll = document.querySelectorAll('div.square');
squaresAll.forEach((square) => {
square.style.backgroundColor = 'transparent';
})
}
const selectSizeButton = document.querySelector('#selectSizeButton');
selectSizeButton.addEventListener(('click'), () => {
let squareSize = prompt(`Enter the number of squares per side of a grid`, "50");
const squaresAll = document.querySelectorAll('div.square');
squaresAll.forEach((square) => {
square.remove();
});
createSquares(squareSize);
})
const resetButton = document.querySelector('#resetButton');
resetButton.addEventListener(('click'), resetBoard);
const rainbowButton = document.querySelector('#rainbowButton');
rainbowButton.addEventListener(('click'), function() { listening('rainbow');});
createSquares(30);
listening(color);
body {
margin: 0 auto;
display: flex;
justify-content: flex-start;
align-items: center;
width: 100vw;
height: 100vh;
flex-direction: column;
}
.controls {
width: 480px;
height: 50px;
flex: 0 0 auto;
background-color: aquamarine; /*to be deleted*/
margin-top: 10vh;
margin-bottom: 5vh;
display: flex;
align-items: center;
justify-content: center;
}
#board {
width: 480px;
height: 480px;
flex: 0 0 auto;
background-color:#f5f0ff;
display: flex;
flex-flow: row wrap;
margin: 0;
padding: 0;
}
.square {
border-color:#d7d3e1;
border-style: solid;
box-sizing: border-box;
border-width: 1px;
padding: 0;
margin: 0;
flex: 0 0 auto;
}
<!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>Etch-a-Sketch</title>
<script src="./script.js" defer></script>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="controls">
<button id="selectSizeButton">Change size</button>
<button id="resetButton">Reset</button>
<button id="rainbowButton">Rainbow mode</button>
</div>
<div id="board">
</div>
</body>
</html>
You have addEventListener inside an event listener - every time you click, you add another listener
Please delegate, it makes everything so much simplerr
const board = document.getElementById('board');
const controls = document.querySelector('.controls');
let color = 'black';
let rainbow = false;
controls.addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.matches("#selectSizeButton")) {
let squareSize = prompt(`Enter the number of squares per side of a grid`, "50");
const squaresAll = document.querySelectorAll('div.square');
squaresAll.forEach((square) => {
square.remove();
});
createSquares(squareSize);
} else if (tgt.matches("#resetButton")) {
board.querySelectorAll(".square").forEach((square) => {
square.style.backgroundColor = 'transparent';
})
} else if (tgt.matches("#rainbowButton")) {
rainbow = !rainbow;
if (rainbow) {
console.log("rainbow selected");
} else {
console.log("other color selected");
}
}
})
board.addEventListener("mouseover", function(e) {
const tgt = e.target;
if (e.buttons === 1 || e.buttons === 3) {
//console.log(e.currentTarget);
color = rainbow ? "#" + ((1 << 24) * Math.random() | 0).toString(16) : color;
tgt.style.backgroundColor = color;
}
})
const createSquares = squareSize => {
squareSize = (480 / squareSize);
console.log(squareSize );
for (i = 0; i < Math.pow((480 / squareSize), 2); i++) {
const square = document.createElement('div');
square.classList.add('square');
square.style.cssText = `
width: ${squareSize}px;
height: ${squareSize}px;
background-color: transparent;`;
board.appendChild(square);
};
};
createSquares(10);
body {
margin: 0 auto;
display: flex;
justify-content: flex-start;
align-items: center;
width: 100vw;
height: 100vh;
flex-direction: column;
}
.controls {
width: 480px;
height: 50px;
flex: 0 0 auto;
background-color: aquamarine;
/*to be deleted*/
margin-top: 10vh;
margin-bottom: 5vh;
display: flex;
align-items: center;
justify-content: center;
}
#board {
width: 480px;
height: 480px;
flex: 0 0 auto;
background-color: #f5f0ff;
display: flex;
flex-flow: row wrap;
margin: 0;
padding: 0;
}
.square {
border-color: #d7d3e1;
border-style: solid;
box-sizing: border-box;
border-width: 1px;
padding: 0;
margin: 0;
flex: 0 0 auto;
}
<div class="controls">
<button id="selectSizeButton">Change size</button>
<button id="resetButton">Reset</button>
<button id="rainbowButton">Rainbow mode</button>
</div>
<div id="board">
</div>

Is there any other way to sort a drag and drop todo list without using the index of the items?

I'm working on a javascript to-do list where you can view all the elements on the list or you can view just the active items or the completed items.
Each of the views has its own array which I sorted out using the index of each element
but when I reorder the list on one of the views, the change is not implemented in the other views.
How do I rectify this?
const dragArea1 = document.querySelector('#task1');
const dragArea2 = document.querySelector('#task2');
const dragArea3 = document.querySelector('#task3');
const addnew = document.querySelector('[name="addnew"]')
const add = document.querySelector('[name="new"]')
const countIt = document.querySelector('#count')
var all = [];
var active = [];
var complete = [];
var lists = document.querySelectorAll('ul');
var views = document.querySelectorAll('.action .views a');
var mobileViews = document.querySelectorAll('#views a');
var list = document.querySelector('.list');
countIt.innerHTML = active.length;
addnew.addEventListener('click', () => {
var newItem
if (addnew.checked == true) {
newItem = {
val: add.value,
checked: false
}
all.push(newItem);
active.push(newItem);
window.setTimeout(() => {
addnew.checked = false;
add.value = '';
}, 300);
displayAll();
count();
}
})
list.addEventListener('click', (ev) => {
if (ev.target.tagName === 'LABEL' || ev.target.tagName === 'P' || ev.target.tagName === 'LI') {
ev.target.classList.toggle('checked');
sortAllList();
if (lists[1].style.display == 'block') {
activeToComplete();
}
if (lists[2].style.display == 'block') {
completeToActive();
}
sortActive();
sortComplete();
}
if (all.length == 0) {
var htmlCode = `<em style="text-align: center; width: 100%; padding: 20px;">add a todo item</em>`;
lists[0].innerHTML = htmlCode;
}
if (active.length == 0) {
var htmlCode = `<em style="text-align: center; width: 100%; padding: 20px;">add a todo item</em>`;
lists[1].innerHTML = htmlCode;
}
if (complete.length == 0) {
var htmlCode = `<em style="text-align: center; width: 100%; padding: 20px;">complete a todo item</em>`;
lists[2].innerHTML = htmlCode;
}
// console.log(ev.target.tagName);
})
function count() {
// to keep count of active items
countIt.innerHTML = active.length;
}
views[0].classList.add('view')
mobileViews[0].classList.add('view')
function displayAll() {
sortActive();
sortComplete();
var htmlCode = "";
if (all.length !== 0) {
for (let i = 0; i < all.length; i++) {
htmlCode += `
<li draggable="true">
<div class="check">
<input type="checkbox" name="listItem" id="item${i}">
<label for="item${i}"></label>
</div>
<p class="itemdesc">${all[i].val}</p>
<span onclick="del(${i})">╳</span>
</li>
`
}
lists[0].innerHTML = htmlCode;
}
lists[0].style.display = 'block';
lists[1].style.display = 'none';
lists[2].style.display = 'none';
views[0].classList.add('view')
views[1].classList.remove('view')
views[2].classList.remove('view')
mobileViews[0].classList.add('view')
mobileViews[1].classList.remove('view')
mobileViews[2].classList.remove('view')
count()
keepChecked();
}
function sortActive() {
// to add active items to the active array
var fit
fit = all.filter(el => el.checked == false)
active = fit
count();
}
function sortComplete() {
//to add completed items to the complete array
var com
com = all.filter(el => el.checked == true)
complete = com
// console.log('complete', complete);
}
function sortAllList() {
// to sort the items into active and completed
const items = document.querySelectorAll('#task1 li');
for (let i = 0; i < all.length; i++) {
if (items[i].classList.contains('checked') == true) {
all[i].checked = true
} else {
all[i].checked = false
}
}
}
function activeToComplete() {
let newA
const items = document.querySelectorAll('#task2 li')
for (let i = 0; i < active.length; i++) {
if (items[i].classList.contains('checked') == true) {
active[i].checked = true;
// active.splice(i,1);
// console.log(active.splice());
} else {
active[i].checked = false
}
}
newA = active.filter(el => el.checked !== true)
console.log(newA);
active = newA;
}
function keepChecked() {
// to keep the completed items checked afetr changing views
const allItems = document.querySelectorAll('#task1 li');
for (let i = 0; i < all.length; i++) {
if (all[i].checked == true) {
allItems[i].classList.add('checked')
}
}
}
function completeToActive() {
const items = document.querySelectorAll('#task3 li')
for (let i = 0; i < complete.length; i++) {
if (items[i].classList.contains('checked') == true) {
complete[i].checked = true;
} else {
complete[i].checked = false
complete.splice(i, 1);
console.log(complete.splice());
}
}
}
function displayActive() {
sortAllList();
sortActive();
var htmlCode = "";
if (active.length !== 0) {
for (let i = 0; i < active.length; i++) {
htmlCode += `
<li draggable="true">
<div class="check">
<input type="checkbox" name="listItem" id="item${i}">
<label for="item${i}"></label>
</div>
<p class="itemdesc">${active[i].val}</p>
<span onclick="del(${i})">╳</span>
</li>
`
}
lists[1].innerHTML = htmlCode;
}
lists[1].style.display = 'block';
lists[0].style.display = 'none';
lists[2].style.display = 'none';
views[1].classList.add('view')
views[0].classList.remove('view')
views[2].classList.remove('view')
mobileViews[1].classList.add('view')
mobileViews[0].classList.remove('view')
mobileViews[2].classList.remove('view')
count()
}
function displayCompleted() {
let unique = [...new Set(complete)]
// console.log(unique[0].val);
var htmlCode = "";
if (unique.length !== 0) {
for (let i = 0; i < unique.length; i++) {
htmlCode += `
<li draggable="true" class="checked">
<div class="check">
<input type="checkbox" name="listItem" id="item${i}">
<label for="item${i}"></label>
</div>
<p class="itemdesc">${unique[i].val}</p>
<span onclick="del(${i})">╳</span>
</li>
`
}
lists[2].innerHTML = htmlCode;
}
lists[2].style.display = 'block';
lists[1].style.display = 'none';
lists[0].style.display = 'none';
views[2].classList.add('view')
views[0].classList.remove('view')
views[1].classList.remove('view')
mobileViews[2].classList.add('view')
mobileViews[1].classList.remove('view')
mobileViews[0].classList.remove('view')
count()
}
function clearComplete() {
var htmlCode = `<em style="text-align: center; width: 100%; padding: 20px;">complete a todo item</em>`;
complete = [];
lists[2].innerHTML = htmlCode;
}
function del(theIndex) {
let i = theIndex;
if (lists[0].style.display == 'block') {
all.splice(i, 1);
displayAll();
}
if (lists[1].style.display == 'block') {
active.splice(i, 1);
let removeFromAll = all.find(el => {
el == active.splice()
})
all.splice(removeFromAll, 1);
displayActive();
}
if (lists[2].style.display == 'block') {
complete.splice(i, 1);
let removeFromAll = all.find(el => {
el == complete.splice()
})
all.splice(removeFromAll, 1);
displayCompleted();
}
sortActive();
sortComplete();
}
new Sortable(dragArea1, {
animation: 350
})
new Sortable(dragArea2, {
animation: 350
})
new Sortable(dragArea3, {
animation: 350
})
:root {
--blue: hsl(220, 98%, 61%);
/* vd -> Very Drak */
--vdblue: hsl(235, 21%, 11%);
--vdDesaturatedblue: hsl(235, 24%, 19%);
--lightgrayblue: hsl(234, 39%, 85%);
--lightgrayblueh: hsl(236, 33%, 92%);
--darkgrayblue: hsl(234, 11%, 52%);
--vdGrayishblueh: hsl(233, 14%, 35%);
--vdGrayishblue: hsl(237, 14%, 26%);
--checkbg: linear-gradient(rgba(87, 221, 255, .7), rgba(192, 88, 243, .7));
--font: 'Josefin Sans', sans-serif;
font-size: 18px;
}
* {
padding: 0;
margin: 0;
font-family: var(--font);
/* font-weight: 700; */
}
*,
*::after,
*::before {
box-sizing: border-box;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus,
select:-webkit-autofill,
select:-webkit-autofill:hover,
select:-webkit-autofill:focus {
border: none;
-webkit-text-fill-color: white;
background-color: transparent !important;
-webkit-box-shadow: 0 0 0px 1000px #00000000 inset;
transition: background-color 5000s ease-in-out 0s;
}
input:focus, input:active, input:visited, textarea:focus, textarea:active, textarea:visited{
background-color: transparent;
border: none;
outline: none;
}
a, em, span{
display: inline-block;
cursor: pointer;
}
a{
text-decoration: none;
display: inline-block;
}
header, main, footer{
width: 100%;
max-width: 30rem;
padding: 10px;
}
main {
display: flex;
flex-direction: column;
gap: 30px;
align-items: center;
}
main #new,
li {
display: flex;
align-items: center;
gap: 20px;
padding: 1rem;
width: 100%;
}
main section,
main #views {
width: 100%;
}
main section,
main #new,
main #views {
border-radius: 5px;
}
main .list {
min-height: 2.5rem;
max-height: 20rem;
/* height: 10rem; */
position: relative;
overflow-y: auto;
}
main .list ul {
/* position: absolute; */
/* top: 20px; */
width: 100%;
display: none;
}
main .list ul:nth-child(1) {
display: block;
}
main #new input[name="new"] {
padding: 10px;
height: inherit;
}
input {
background-color: transparent;
width: calc(100% - 70px);
border: none;
font-size: 1rem;
}
li {
justify-content: flex-start;
}
li .check {
position: relative;
}
main #new .check input,
li .check input {
display: none;
}
main #new .check label,
li .check label {
width: 30px;
height: 30px;
border-radius: 30px;
display: inline-block;
}
main #new .check input:checked~label,
li.checked .check label {
background-image: var(--checkbg), url(images/icon-check.svg);
background-position: center center;
background-repeat: no-repeat;
}
li p {
width: 85%;
}
li.checked label {
background-color: #66666696;
}
li.checked p {
text-decoration: line-through;
}
li span {
/* justify-self: flex-end; */
display: none;
}
li:hover span {
display: flex;
}
main .action {
display: flex;
justify-content: space-between;
/* gap: 2rem; */
padding: 1.1rem;
font-size: .8rem;
}
.views a,
#views a {
font-weight: 700;
}
.action a.view {
color: var(--blue);
}
main #views {
padding: .8rem;
text-align: center;
font-size: .8rem;
display: none;
}
#views a.view {
color: var(--blue);
}
main #views+p {
font-size: .7rem;
}
li,
em {
border-bottom: 1px solid var(--darkgrayblue);
}
li,
li p,
main .action a:hover {
color: var(--lightgrayblue);
}
a,
em,
li.checked p,
p,
span,
input,
li span {
color: var(--darkgrayblue);
}
header img {
content: url("images/icon-sun.svg");
}
main #new {
background-color: var(--vdDesaturatedblue);
}
main #new .check label,
li .check label {
border: 1px solid var(--vdGrayishblue);
}
main #new .check label:hover,
li .check label:hover {
border: 1px solid var(--vdGrayishblue);
}
main section,
main #views {
background-color: var(--vdDesaturatedblue);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.0/Sortable.min.js"></script>
<main role="main">
<div id="new">
<div class="check">
<input type="checkbox" name="addnew" id="addnew">
<label for="addnew"></label>
</div>
<input type="text" name="new" placeholder="Create a new todo...">
</div>
<section>
<div class="list">
<ul id="task1">
<em style="text-align: center; width: 100%; padding: 20px;">add a todo item</em>
</ul>
<ul id="task2">
<em style="text-align: center; width: 100%; padding: 20px;">add a todo item</em>
</ul>
<ul id="task3">
<em draggable="true" style="text-align: center; width: 100%; padding: 20px;">complete a todo item</em>
</ul>
</div>
<div class="action">
<p>
<span id="count"></span> items left
</p>
<div class="views">
All
Active
Completed
</div>
Clear Completed
</div>
</section>
<div id="views">
All
Active
Completed
</div>
<p>Drag and drop to reorder list</p>
</main>

How to activate mouseover on click/mousedown?

I am working on the etch-a-sketch project with The Odin Project and there's one thing I can't figure out. In the original project the grid elements will start to change colors once I hover over them but what I want to do is to only start the hovering function after I press the left mouse button.
Here is a working example of what I want to achieve.
const container = document.getElementById('container');
const reset = document.getElementById('reset');
const eraser = document.querySelector('#eraser');
const pencil = document.querySelector('#pencil');
const rainbow = document.querySelector('#rainbow');
const pickColor = document.querySelector('#color');
const shading = document.querySelector('#shading')
const gridSize = document.querySelector('#range');
let gridValue = document.querySelector('.grid-size');
let mode = '';
gridValue.textContent = `50x50`; // display default size of the grid
// create grid
function createGrid(size) {
size = gridSize.value;
container.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
container.style.gridTemplateRows = `repeat(${size}, 1fr)`;
for (let i = 0; i < size * size; i++) {
const grid = document.createElement('div');
grid.setAttribute('id', 'grid');
container.appendChild(grid);
grid.addEventListener('mouseover', changeColor);
}
}
createGrid();
// change grid size depending on the user's choice
gridSize.addEventListener('input', function (e) {
let currentSize = e.target.value;
gridValue.textContent = `${currentSize}x${currentSize}`;
resetGrid();
});
// change color
function changeColor(e) {
if (mode === 'pencil') {
e.target.classList.add('black');
e.target.classList.remove('white');
e.target.style.backgroundColor = null;
e.target.style.opacity = null;
} else if (mode === 'rainbow') {
const green = Math.floor(Math.random() * 250);
const blue = Math.floor(Math.random() * 250);
const red = Math.floor(Math.random() * 250);
e.target.style.backgroundColor = `rgb(${red}, ${green}, ${blue})`;
e.target.style.opacity = null;
} else if (mode === 'erase') {
e.target.classList.add('white');
e.target.style.backgroundColor = null;
e.target.style.opacity = null;
} else if (mode === 'pickColor') {
e.target.style.backgroundColor = pickColor.value;
e.target.style.opacity = null;
} else if (mode === 'shading') {
e.target.classList.remove('white');
e.target.classList.add('black');
e.target.style.opacity = Number(e.target.style.opacity) + 0.2;
} else {
e.target.classList.add('black')
}
}
// reset grid
reset.addEventListener('click', () => resetGrid());
function resetGrid() {
container.innerHTML = '';
createGrid();
}
// erase
eraser.addEventListener('click', () => eraseMode());
function eraseMode() {
mode = 'erase';
}
// pencil
pencil.addEventListener('click', () => pencilMode());
function pencilMode() {
mode = 'pencil';
}
// rainbow
rainbow.addEventListener('click', () => rainbowMode());
function rainbowMode() {
mode = 'rainbow';
}
// pick a color
pickColor.addEventListener('click', () => pickColorMode());
function pickColorMode() {
mode = 'pickColor';
}
// shading
shading.addEventListener('click', () => shadeMode());
function shadeMode() {
mode = 'shading';
}
#import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght#700;800&display=swap');
body {
text-align: center;
background-color: #4d56bd;
font-family: 'Inconsolata', monospace;
color: white;
}
h1 {
font-weight: 800;
font-size: 40px;
text-shadow: 3px 3px #1d1b1e;
color: #f4ce45
}
#container {
width: 600px;
height: 600px;
border: 3px solid #1d1b1e;
display: grid;
margin: 20px auto;
background-color: white;
box-shadow: 5px 5px #303030;
}
#grid {
border: none;
}
.black {
background-color: black;
}
.white {
background-color: white;
}
.button {
font-family: inherit;
padding: 10px;
font-size: 1em;
background-color: #303030;
box-shadow: 2px 2px #1d1b1e;
color: white;
margin: 0 5px;
}
#reset {
background-color: #f4ce45;
color: black;
}
.button:hover,
#reset:hover {
cursor: pointer;
background-color: #e7455a;
}
.ownColor {
margin: 10px;
}
.pickColor:hover {
cursor: auto;
background-color: #303030;
}
#color {
cursor: pointer;
}
.slider {
-webkit-appearance: none;
width: 30%;
height: 10px;
border-radius: 5px;
background: #1d1b1e;
margin: 10px;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #f4ce45;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: #f4ce45;
cursor: pointer;
}
.grid-size {
font-size: 1.5rem;
margin-top: 20px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
<title>Etch-a-Sketch</title>
</head>
<body>
<h1>ETCH A SKETCH</h1>
<button id="reset" class="button">Reset</button>
<button id="pencil" class="button">Pencil</button>
<button id="eraser" class="button">Eraser</button>
<button id="rainbow" class="button">Rainbow</button>
<button id="shading" class="button">Shading</button>
<div class="ownColor">
<button class="button pickColor">Choose your own color:
<input type="color" id="color"></button>
</div>
<div class="grid-size"></div>
<input type="range" id="range" min="5" max="100" class="slider">
<div id="container"></div>
</body>
<script src="script.js"></script>
</html>
Well basically you can achieve this with a variable isMouseDown.
let isMouseDown;
document.addEventListener('mousedown', () => isMouseDown = true);
document.addEventListener('mouseup', () => isMouseDown = false);
then, use it where you need, for example:
// change color
function changeColor(e) {
if(!isMouseDown) return;
// ... rest of the code
}

How to make an image slider using JavaScript with user generated images?

I am working on a project which:
Asks the user to choose images from their device
The selected images would show up in the image slider
const slider = document.querySelector('.slider');
// load user chosen files
fileInp.addEventListener('input', (e) =>
{
const files = e.target.files;
for (let file of files) {
const img = new Image();
const reader = new FileReader();
reader.addEventListener('load', (e) =>
{
img.src = e.target.result;
slider.appendChild(img);
img.classList.add('slide');
})
reader.readAsDataURL(file);
}
})
// slider
const slides = document.querySelectorAll('.slide');
let counter = 0;
slides.forEach((slide, index) =>
{
slide.style.left =
`${index * 100}%`
})
nextBtn.onclick = () => {
counter++;
move();
}
prevBtn.onclick = () => {
counter--;
move();
}
function move() {
if (counter <= 0) {
counter = slides.length - 1;
}
if (counter === slides.length) {
counter = 0;
}
slides.forEach((slide, index) => {
slide.style.transform =
`translateX(-${counter * 100}%)`
})
}
#import url("https://fonts.googleapis.com/css2?family=Nunito:wght#400;900");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Nunito", sans-serif;
}
body {
display: grid;
place-items: center;
height: 100vh;
}
.box {
display: flex;
flex-direction: column;
padding: 1rem;
gap: 1rem;
}
.slider {
min-width: 300px;
min-height: 70px;
display: flex;
overflow: hidden;
position: relative;
}
.slide {
position: absolute;
height: 100%;
width: 100%;
object-fit: cover;
object-position: center;
transition: transform 250ms ease;
}
h1 {
margin-bottom: 0.25rem;
}
.controls {
display: flex;
gap: 0.5rem;
width: 100%;
align-items: center;
}
.btn {
background-color: #333;
border: 0;
padding: 0.5rem 0.75em;
width: 12ch;
text-transform: uppercase;
letter-spacing: 1px;
color: #fff;
}
.ph {
background-color: #08b;
width: max-content;
}
.btn:hover {
border: 2px solid #000;
border-radius: .25rem;
}
<div class="box">
<h1>Gallery</h1>
<div class="slider"></div>
<div class="controls">
<button class="btn" id="prevBtn">previous</button>
<input hidden type="file" id="fileInp" multiple />
<!-- Just a placeholder to easily style the file input -->
<button onclick="fileInp.click()" class="ph btn">choose files</button>
<button class="btn" id="nextBtn">next</button>
</div>
</div>
i am adding a snippet so you can understand it clearly,
When using static HTML tags, the slider works properly but when I generate images from file input using JavaScript it gives unwanted results. Where am I going wrong?
it because on page load const slides = document.querySelectorAll('.slide'); is empty, move it to file load event
const slider = document.querySelector('.slider');
let slides = [];
// load user chosen files
fileInp.addEventListener('input', (e) => {
const files = e.target.files;
for (let file of files) {
const img = new Image();
const reader = new FileReader();
reader.addEventListener('load', (e) => {
img.src = e.target.result;
slider.appendChild(img);
img.classList.add('slide');
slides = document.querySelectorAll('.slide');
slides.forEach((slide, index) => {
slide.style.left =
`${index * 100}%`
})
})
reader.readAsDataURL(file);
}
})
// slider
let counter = 0;
nextBtn.onclick = () => {
counter++;
move();
}
prevBtn.onclick = () => {
counter--;
move();
}
function move() {
if (counter <= 0) {
counter = slides.length - 1;
}
if (counter === slides.length) {
counter = 0;
}
slides.forEach((slide, index) => {
slide.style.transform =
`translateX(-${counter * 100}%)`
})
}
.slide {
position: absolute;
height: 100%;
width: 100%;
top: 110px;
object-fit: cover;
object-position: center;
transition: transform 250ms ease;
}
h1 {
margin-bottom: 0.25rem;
}
.controls {
display: flex;
gap: 0.5rem;
width: 100%;
align-items: center;
}
.btn {
background-color: #333;
border: 0;
padding: 0.5rem 0.75em;
width: 12ch;
text-transform: uppercase;
letter-spacing: 1px;
color: #fff;
}
.ph {
background-color: #08b;
width: max-content;
}
.btn:hover {
background-color: #222;
}
<div class="box">
<h1>Gallery</h1>
<div class="slider"></div>
<div class="controls">
<button class="btn" id="prevBtn">previous</button>
<input hidden type="file" id="fileInp" multiple />
<!-- Just a placeholder to easily style the file input -->
<button onclick="fileInp.click()" class="ph btn">choose files</button>
<button class="btn" id="nextBtn">next</button>
</div>

JavaScript Todo List - When I click 'edit' button, it selects the wrong text input, but still edits the correct item

const todoList = {
todos: [],
addTodo: function(todoText) {
this.todos.push({
todoText: todoText,
completed: false
});
view.displayTodos();
},
changeTodo: function(position, newTodoText) {
this.todos[position].todoText = newTodoText;
view.displayTodos();
},
deleteTodo: function(position) {
this.todos.splice(position, 1);
view.displayTodos();
},
toggleCompleted: function(position) {
let todo = this.todos[position];
todo.completed = !todo.completed;
},
toggleAll: function() {
const allTodos = this.todos.length;
let completedTodos = 0;
for (let i = 0; i < allTodos; i++) {
if (this.todos[i].completed === true) {
completedTodos++;
}
}
if (completedTodos === allTodos) {
for (let i = 0; i < allTodos; i++) {
this.todos[i].completed = false;
}
} else {
for (let i = 0; i < allTodos; i++) {
this.todos[i].completed = true;
}
}
view.displayTodos();
}
};
const handlers = {
toggleAll: function() {
todoList.toggleAll();
},
addTodo: function(e) {
if (e.keyCode === 13) {
e.preventDefault(); // Ensure it is only this code that rusn
let todoTextInput = document.getElementById('todoTextInput');
todoList.addTodo(todoTextInput.value);
todoTextInput.value = '';
}
},
deleteTodo: function(position) {
todoList.deleteTodo(position);
}
};
const view = {
displayTodos: function() {
let todosUl = document.getElementById('todoList');
todosUl.innerHTML = '';
for (let i = 0; i < todoList.todos.length; i++) {
let todoLi = document.createElement('li');
let todoLiText = document.createElement('input');
todoLiText.type = "text";
todoLiText.disabled = true;
todoLiText.id = 'textInput';
let todoTextWithCompletion = todoList.todos[i].todoText;;
let check = document.createElement('input');
check.type = "checkbox";
check.id = "checkbox";
check.className = "checkbox";
check.checked = '';
todoLi.id = i;
todoLiText.value = todoTextWithCompletion;
todoLi.appendChild(check);
todoLi.appendChild(todoLiText);
todoLi.appendChild(this.createDeleteButton());
todoLi.appendChild(this.createEditButton());
todosUl.appendChild(todoLi);
if (document.getElementById('checkbox').checked === true) {
todoList.toggleCompleted(i);
};
if (todoList.todos[i].completed === true) {
todoLiText.style.textDecoration = "line-through";
};
}
},
createDeleteButton: function() {
let deleteButton = document.createElement('a');
deleteButton.href = "#";
deleteButton.textContent = "Delete";
deleteButton.className = 'x';
return deleteButton;
},
createEditButton: function() {
let editButton = document.createElement('a');
editButton.href = "#";
editButton.textContent = "edit";
editButton.className = 'edit';
return editButton;
},
setUpEventListeners: function() {
let todosUl = document.getElementById('todoList');
todosUl.addEventListener('click', (event) => {
let elementClicked = event.target;
if (elementClicked.className === 'x') {
handlers.deleteTodo(parseInt(elementClicked.parentNode.id));
};
});
// Edit List Item
todosUl.addEventListener('click', (event) => {
let elementClicked = event.target;
let position = elementClicked.parentNode.id;
if (elementClicked.className === 'edit') {
let input = document.getElementById('textInput');
input.disabled = false;
input.className += " activeTextInput ";
input.focus();
input.select();
input.addEventListener('keyup', (event) => {
let elementClicked = event.target;
if (event.keyCode === 13) {
let textInput = input.value;
input.disabled = true;
input.classList.remove("activeTextInput");
todoList.changeTodo(position, textInput);
};
});
};
});
// Line through on check
todosUl.addEventListener('click', (event) => {
let elementClicked = event.target;
let position = elementClicked.parentNode.id;
let check = document.getElementById('checkbox');
if (elementClicked.className === 'checkbox') {
todoList.toggleCompleted(position);
check.checked = true;
};
});
//Delete All
let clearAll = document.getElementById('clearAll');
clearAll.addEventListener('click', (event) => {
todoList.todos.splice(0, todoList.todos.length);
view.displayTodos();
});
// TODO Delete Selected
}
};
view.setUpEventListeners();
html,
body {
margin: 0;
height: 100%;
}
body {
background-color: #eeeeee !important;
}
h1 {
color: #282845 !important;
}
p {
opacity: .3;
}
.container {
min-height: 70%;
display: flex;
justify-content: center;
flex-direction: column;
width: 50% !important;
}
#todoTextInput {
background-color: white;
opacity: .7;
box-shadow: 1px 1px 10px rgba(5, 5, 5, 0.2);
height: 50px;
padding: 10px;
border: none;
font-style: italic;
}
ul {
display: flex;
flex-direction: column;
padding: 0;
margin: 0;
}
li {
list-style-type: none !important;
border-bottom: 1px solid rgba(5, 5, 5, 0.1);
padding: 10px;
background-color: white;
box-shadow: 1px 1px 10px rgba(5, 5, 5, 0.2);
min-width: inherit;
}
li a {
float: right;
padding-right: 10px;
opacity: .3 !important;
transition: .2s;
}
li a:hover {
opacity: 1 !important;
}
input {
border: none;
border-bottom: 1px solid rgba(5, 5, 5, 0..01);
background-color: inherit;
}
li input[type="text"] {
padding: none;
border: none;
width: 80%;
margin-left: 10px;
transition: .5s;
}
.activeTextInput {
box-shadow: 1px 1px 10px rgba(5, 5, 5, 0.1);
padding: 10px !important;
}
input[type="text"] {
background-color: inherit;
border: none;
border-bottom: 1px solid rgba(5, 5, 5, 0.01);
padding: 0;
}
input:focus {
outline: none;
}
input:checked+input[type="text"] {
text-decoration: line-through;
opacity: .5;
}
.btn {
background-color: white !important;
border-radius: 0 !important;
padding: 0;
box-shadow: 1px 1px 10px rgba(5, 5, 5, 0.2);
margin-top: -2px !important;
font-style: italic;
opacity: 0.5;
}
#clearAll {
margin-top: -21px !important;
}
.hidden {
display: none !important;
background-color: red !important;
}
<link href="https://cdn.rawgit.com/twbs/bootstrap/v4-dev/dist/css/bootstrap.css" rel="stylesheet" />
<body>
<div class="container">
<h1 class="text-center">List+</h1>
<div class="row list-head">
<div class="col-md-12">
<input type="text" placeholder="Enter a List Item" style="width: 100%" id="todoTextInput" onkeyup="handlers.addTodo(event)">
</div>
</div>
<div class="row list-container">
<div class="col-md-12">
<ul id="todoList">
</ul>
</div>
</div>
<div class="row">
<div class="col-md-12">
<button class="btn" style="width: 100%" id="clearAll">Clear All</button>
<button class="btn" style="width: 100%">Clear Selected</button>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p class="text-center"><i>Created by Connor Beam</i></p>
</div>
</div>
</div>
<script src="js/main.js"></script>
</body>
I'm building a todo list in vanilla JavaScript. I'm trying to get the 'edit' option to function properly. When I click the 'edit' button, the corresponding text input should be enabled, and auto-selected, then the user should be able to press 'enter' to submit changes.
However, no matter which 'edit' button is clicked, he first text input is always selected, while still changing the correct item when submitted.
Here's a working link to the most recent version: http://vanillajstodo.surge.sh/
I believe the problem is that all 'todoLiText' inputs are being created with the same id; however, I'm not sure how to fix that.
Let me know if more info is needed.
Thanks
Take a look at this solution:
https://codepen.io/anon/pen/YQQovp?editors=1010
let input = document.getElementById(position).childNodes[1];
Your mistake is that you should never have multiple elements with the same id on the same page. Instead you should use the same class in that situation.

Categories

Resources