Defining listener function inside function - javascript

When I define a function outside the function, I cannot access the glide parameter:
export const setFocusListenersForKeyboardNavigation = (glide) => {
const slides = glide._c.Html.slides;
for (let i = 0; i < slides.length; i++) {
const currentSlide = slides[i];
const slideButton = currentSlide.querySelector(".js-slide-button");
const slideLink = currentSlide.querySelector(".js-slide-link");
slideButton && slideButton.addEventListener('focus', focusListener);
slideLink && slideLink.addEventListener('focus', focusListener);
}
};
const focusListener = (event) => {
const activeIndex = glide._i;
const buttonIndex = event.target.dataset.slideIndex;
if (activeIndex !== parseInt(buttonIndex)) {
glide.go(`=${buttonIndex}`);
}
};
Hence, I have did something like that:
export const setFocusListenersForKeyboardNavigation = (glide) => {
const focusListener = (event) => {
const activeIndex = glide._i;
const buttonIndex = event.target.dataset.slideIndex;
if (activeIndex !== parseInt(buttonIndex)) {
glide.go(`=${buttonIndex}`);
}
};
const slides = glide._c.Html.slides;
for (let i = 0; i < slides.length; i++) {
const currentSlide = slides[i];
const slideButton = currentSlide.querySelector(".js-slide-button");
const slideLink = currentSlide.querySelector(".js-slide-link");
slideButton && slideButton.addEventListener('focus', focusListener);
slideLink && slideLink.addEventListener('focus', focusListener);
}
};
I want to know if it is hack or good practice? Is there more convenient way for doing this.

Having the function outside is better.
Mainly for readability and testing, but if your function is called a lot of times (several hundreds for example) it can be even performance hit to be redefined every time.
you can add arrow function to the listener, that will call the focusListener with the correct parameters.
you can do something like this:
export const setFocusListenersForKeyboardNavigation = (glide) => {
const slides = glide._c.Html.slides;
for (let i = 0; i < slides.length; i++) {
const currentSlide = slides[i];
const slideButton = currentSlide.querySelector(".js-slide-button");
const slideLink = currentSlide.querySelector(".js-slide-link");
slideButton && slideButton.addEventListener('focus', (event) => {focusListener(event, glide)});
slideLink && slideLink.addEventListener('focus', (event) => {focusListener(event, glide));
}
};
const focusListener = (event, glide) => {
const activeIndex = glide._i;
const buttonIndex = event.target.dataset.slideIndex;
if (activeIndex !== parseInt(buttonIndex)) {
glide.go(`=${buttonIndex}`);
}
};

Related

Select a different image depending on a variable

I'm trying to select a different image depending on the click.id so I have to give id to indicate the image. I'm doing it like this:
const imgChange = document.querySelector('.eyeImage img');
const eyeBtn = document.querySelectorAll('.icons .fa-eye');
eyeBtn.forEach((click) => {
click.addEventListener('click', () => {
eyeImage.classList.remove('active');
if (click.id === 'product-1') {
imgChange.src = 'images/product-1.png';
} else if (click.id === 'product-2') {
imgChange.src = 'images/product-2.png';
} else if (click.id === 'product-3') {
imgChange.src = 'images/product-3.png';
}
});
});
Is there any proper way to achieve it?
You can use expressions on a string using ` instead of ':
const imgChange = document.querySelector('.eyeImage img');
const eyeBtn = document.querySelectorAll('.icons .fa-eye');
eyeBtn.forEach((click) => {
click.addEventListener('click', () => {
eyeImage.classList.remove('active');
imgChange.src = `images/product-${click.id}.png`;
});
});
Example with a for:
for(let i = 0; i < 5; i++) console.log(`images/product-${i}`);

How can I encapsulate my code, so I can pass arguments from one function to another?

I wrote this simple carousel but without encapsulation. So previously I placed items from buttonControl() in global scope and added eventListeners on global scope, which are now emraced in prev() and next() functions. However, my encapsulation breaks the code. Because arguments from buttonControl() aren't global but prev() and next() needs them to work. I thought that maybe I can pass all arguments from buttonsControl() inside addEventListener('click', prev) but I cannot, because when I write thisaddEventListener('click', prev(slides,totalItems,allItems....)) it is launching this event without a click.And I even don't know if its correct way.
I thought of puting arguments from buttonsControl() inside prev() and next() but it won't work.
function buttonsControl(){
const slides = document.querySelector('.slides');
const totalItems = document.querySelectorAll('.slides>*').length - 1;
const allItems = document.querySelectorAll('.slides>*').length;
console.log(totalItems)
let activeItem = 0;
let controlCarouselFooter = document.querySelector('.carousel_footer');
controlCarouselFooter.innerHTML = `1 / ${allItems}`
console.log(controlCarouselFooter)
const prevButton = document.querySelector('.prev_button').addEventListener('click', prev)
const nextButton = document.querySelector('.next_button').addEventListener('click', next)
// no idea how to pass those arguments
}
// Buttons controls
function prev(){
// const prevButton = document.querySelector('.prev_button').addEventListener('click', () => {
if (activeItem === 0) {
activeItem = totalItems;
slides.style.transform = `translateX(-${totalItems * 100}%)`;
console.log(`if ${activeItem}`)
controlCarouselFooter.innerHTML = `${activeItem+1} / ${allItems}`
}else {
activeItem--;
slides.style.transform = `translateX(-${activeItem * 100}%)`;
console.log(`else ${activeItem}`)
controlCarouselFooter.innerHTML = `${activeItem+1} / ${allItems} `
}
}
// );
// }
function next(){
// const nextButton = document.querySelector('.next_button').addEventListener('click', () => {
if(activeItem < totalItems) {
activeItem++;
slides.style.transform = `translateX(-${activeItem * 100}%)`;
console.log(`if ${activeItem}`)
controlCarouselFooter.innerHTML = `${activeItem+1} / ${allItems}`
} else {
activeItem = 0;
slides.style.transform = 'none';
console.log(`else ${activeItem+1}`)
console.log(`totalItems ${totalItems}`)
controlCarouselFooter.innerHTML = `${activeItem+1} / ${allItems}`
}
}
// );
// };
// });
buttonsControl();
The easiest solution would be to define the functions prev and next inside the buttonsControl function, so that all its local variables are in scope through closure:
function buttonsControl() {
const slides = document.querySelector('.slides');
const totalItems = document.querySelectorAll('.slides>*').length - 1;
const allItems = document.querySelectorAll('.slides>*').length;
let activeItem = 0;
let controlCarouselFooter = document.querySelector('.carousel_footer');
controlCarouselFooter.innerHTML = `1 / ${allItems}`;
const prevButton = document.querySelector('.prev_button').addEventListener('click', prev);
const nextButton = document.querySelector('.next_button').addEventListener('click', next);
// Buttons controls
function prev() {
if (activeItem === 0) {
activeItem = totalItems;
} else {
activeItem--;
}
slides.style.transform = `translateX(-${totalItems * 100}%)`;
controlCarouselFooter.innerHTML = `${activeItem+1} / ${allItems}`
}
function next() {
if (activeItem < totalItems) {
activeItem++;
slides.style.transform = `translateX(-${activeItem * 100}%)`;
} else {
activeItem = 0;
slides.style.transform = 'none';
}
controlCarouselFooter.innerHTML = `${activeItem+1} / ${allItems}`
}
}
buttonsControl();
If I'm understanding your question correctly, You could bind the variables to the listeners.
EDIT: Someone pointed out that you're mutating activeItems. Which is true. So You will want to define all your variables on an object first so that mutation is persistent between function calls.
function buttonsControl(){
let obj = {};
obj.slides = document.querySelector('.slides');
obj.totalItems = document.querySelectorAll('.slides>*').length - 1;
obj.allItems = document.querySelectorAll('.slides>*').length;
console.log(obj.totalItems)
obj.activeItem = 0;
obj.controlCarouselFooter = document.querySelector('.carousel_footer');
controlCarouselFooter.innerHTML = `1 / ${allItems}`
console.log(controlCarouselFooter)
//bind the variables you want to use to your input
const prevButton = document.querySelector('.prev_button').addEventListener('click', prev.bind(null, obj))
const nextButton = document.querySelector('.next_button').addEventListener('click', next.bind(null, obj))
// no idea how to pass those arguments
}
// Buttons controls
function prev(obj){
//define the arguments in your fn params.
// const prevButton = document.querySelector('.prev_button').addEventListener('click', () => {
if (obj.activeItem === 0) {
obj.activeItem = totalItems;
obj.slides.style.transform = `translateX(-${totalItems * 100}%)`;
console.log(`if ${activeItem}`)
obj.controlCarouselFooter.innerHTML = `${obj.activeItem+1} / ${obj.allItems}`
}else {
obj.activeItem--;
obj.slides.style.transform = `translateX(-${activeItem * 100}%)`;
console.log(`else ${obj.activeItem}`)
obj.controlCarouselFooter.innerHTML = `${obj.activeItem+1} / ${obj.allItems} `
}
}
// );
// }
function next(obj){
...similar implimentation to prev()
}
// );
// };
// });
buttonsControl();

How to get external function work in addEventListener for loop

I have two click methods, which do the same thing, so I created an external function. But external function doesn't know what 'i' is. How can I make it work?
Passing 'i' as argument doesn't work.
const modalOverlay = document.querySelectorAll(".overlay");
const modalWindow = document.querySelectorAll(".modal_window");
const modalCloseBtn = document.querySelectorAll(".close_modal");
const modalOpenBtn = document.querySelectorAll(".open_modal");
const closeModal = ()=>{
modalOverlay[i].classList.add("hidden");
modalWindow[i].classList.add("hidden");
}
for (let i = 0; i < modalOpenBtn.length; i++) {
modalOpenBtn[i].addEventListener("click", ()=>{
modalOverlay[i].classList.remove("hidden");
modalWindow[i].classList.remove("hidden");
})
}
for (let i = 0; i < modalCloseBtn.length; i++) {
modalCloseBtn[i].addEventListener("click", closeModal)
}
for (let i = 0; i < modalOverlay.length; i++) {
modalOverlay[i].addEventListener("click", closeModal)
}

How to make an html element always stay inside boundaries?

I'm coding a search algorithms visualizer in react and I'm having a small glitch whenever I move my start or end nodes. When I press down on the mouse and move the cursor over the board, the nodes update accordingly. That is until the cursor goes beyond the boundaries of the grid, leaving a node with the start-node or end-node classname, visually creating two (or more) nodes, as such:
This doesn't affect functionality but it is pretty annoying. Ideally, I'm looking to make it work like it does on this demo: https://pathfindout.com/, where it doesn't matter where the user moves the cursor while dragging the nodes, they always stay inside the limits of the board. This is the code for my Node component so far:
const Node = ({ row, col, isWall, isStart, isEnd, handleState, handleMouseState }) => {
const { nodesMatrix, updateNodes } = handleState;
const { isMouseDown, setIsMouseDown } = handleMouseState;
//This variable helps to prevent the user
//from stacking the 'start' and 'end' nodes
//on top of each other. Instead they jump to
//the next available sibling.
let prevCollision = null;
const handleMouseDown = (e) => {
e.preventDefault();
isStart ? setIsMouseDown(1) : isEnd ? setIsMouseDown(2) : setIsMouseDown(3);
};
const handleMouseUp = (e) => {
setIsMouseDown(0);
const newParent = e.target.id ? e.target : null;
const [newParentRow, newParentCol] = newParent.id.match(/\d+/g);
if (isMouseDown === 1) updateNodes(false, true, parseInt(newParentRow), parseInt(newParentCol));
if (isMouseDown === 2) updateNodes(false, false, parseInt(newParentRow), parseInt(newParentCol));
if (isMouseDown === 3) updateNodes(true, false, parseInt(newParentRow), parseInt(newParentCol));
};
const handleMouseEnter = (e) => {
const newParent = e.target.id ? e.target : null;
const prevParent = e.relatedTarget;
const [newParentRow, newParentCol] = e.target.id.match(/\d+/g);
const nodePointer = nodesMatrix[newParentRow][newParentCol];
//moving start node
if (isMouseDown === 1) {
const collision = isEnd;
if (!collision) {
prevParent.classList.remove('start-node');
newParent.classList.add('start-node');
nodePointer.isStart = true;
} else {
prevCollision = prevParent;
}
};
//moving end node
if (isMouseDown === 2) {
const collision = isStart;
if (!collision) {
prevParent.classList.remove('end-node');
newParent.classList.add('end-node');
nodePointer.isEnd = true;
} else {
prevCollision = prevParent;
}
}
//creating walls
if (isMouseDown === 3) {
if(!isStart && !isEnd) {
if (isWall) {
newParent.classList.remove('wall-node');
nodePointer.isWall = false;
} else {
newParent.classList.add('wall-node');
nodePointer.isWall = true;
};
}
};
};
const handleMouseLeave = (e) => {
const prevParent = e.target.id ? e.target : null;
const [prevRow, prevCol] = prevParent.id.match(/\d+/g);
const nodePointer = nodesMatrix[prevRow][prevCol];
if (isMouseDown === 1) {
if (prevCollision) prevCollision.classList.remove('start-node');
nodePointer.isStart = false;
};
if (isMouseDown === 2) {
if (prevCollision) prevCollision.classList.remove('end-node');
nodePointer.isEnd = false;
};
};
const handleClick = (e) => {
const target = e.target.id ? e.target : null;
const [row, col] = target.id.match(/\d+/g);
const nodePointer = nodesMatrix[row][col];
if (isWall) {
target.classList.remove('wall-node');
nodePointer.isWall = false;
} else {
target.classList.add('wall-node');
nodePointer.isWall = true;
}
};
return (
<div id={`node-${ row }-${ col }`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseUp={handleMouseUp}
onMouseDown={handleMouseDown}
onClick={(isStart || isEnd) ? null : handleClick}
className={
`${ isStart ? 'start-node' : isEnd ? 'end-node' : isWall ? 'wall-node' : '' }
node w-6 h-6 border border-blue-400`} />
)
};
export default Node
Any help is greatly appreciated!
Edit:
This is the parent Grid component if it helps with anything:
const Grid = () => {
const [nodesMatrix, setNodesMatrix] = useState([]);
const [speed, setSpeed] = useState(5);
const [startNodeRow, setStartNodeRow] = useState(6);
const [startNodeCol, setStartNodeCol] = useState(10);
const [endNodeRow, setEndNodeRow] = useState(6);
const [endNodeCol, setEndNodeCol] = useState(17);
// 1 to move 'start' node;
// 2 to move 'end' node;
// 3 to create a wall;
const [isMouseDown, setIsMouseDown] = useState(0);
//Initializes Grid
useEffect(() => {
const cells = [];
const startNodeRow = 6;
const startNodeCol = 10;
const endNodeRow = 6;
const endNodeCol = 17;
for (let row = 0; row < ROWS; row++) {
const currentRow = [];
for (let col = 0; col < COLUMNS; col++) {
const node = {
row,
col,
isWall: false,
isStart: row === startNodeRow && col === startNodeCol,
isEnd: row === endNodeRow && col === endNodeCol,
distance: Infinity,
visited: false,
previousNode: null
};
currentRow.push(node);
};
cells.push(currentRow);
};
setNodesMatrix(cells);
}, []);
useEffect(() => {
clearAllNodesStyles();
}, [isMouseDown]);
const resetMatrix = (matrix) => {
const matrixRef = [...matrix]
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLUMNS; col++) {
const node = matrixRef[row][col];
node.distance = Infinity;
node.previousNode = null;
node.visited = false;
node.isWall = node.isStart || node.isEnd ? false : node.isWall
};
};
return matrixRef
};
const updateNodes = (isWall, isStart, newRow, newCol) => {
const newGrid = resetMatrix(nodesMatrix);
if(isWall) return setNodesMatrix(newGrid);
if (isStart) {
setStartNodeRow(newRow);
setStartNodeCol(newCol);
} else {
setEndNodeRow(newRow);
setEndNodeCol(newCol);
};
setNodesMatrix(newGrid);
};
return (
<div className="flex flex-col self-center">
{nodesMatrix.map((row, rowIndex) => {
return (
<div key={rowIndex} className='flex'>
{row.map((node, index) => {
const { row, col, isWall, isStart, isEnd } = node;
return <Node
key={index}
row={row}
col={col}
isWall={isWall}
isStart={isStart}
isEnd={isEnd}
handleState={{nodesMatrix, updateNodes}}
handleMouseState={{isMouseDown, setIsMouseDown}} />
})}
</div>
)
})
}
<button
onClick={() => visualizeDijkstra(nodesMatrix, { startNodeRow, startNodeCol, endNodeRow, endNodeCol, speed })}
className="...">
Search Path
</button>
</div>
)
};
export default Grid
As I mentioned in comment, The problem is, you are not tracking whether the mouse going out of the Grid or not. You are also not storing the last Node before going out of grid. As a result, when the mouse again enters the Grid, the program doesn't know which one is the last node which it visited. So, it can't remove the class from the last node.
import React, { useState, useEffect } from "react";
import { clearAllNodesStyles, visualizeDijkstra } from "../algorithms/dijkstra";
import Node from "./Node";
const COLUMNS = 60;
const ROWS = 25;
const Grid = () => {
const [nodesMatrix, setNodesMatrix] = useState([]);
const [speed, setSpeed] = useState(5);
const [startNodeRow, setStartNodeRow] = useState(6);
const [startNodeCol, setStartNodeCol] = useState(10);
const [endNodeRow, setEndNodeRow] = useState(6);
const [endNodeCol, setEndNodeCol] = useState(17);
// 1 to move 'start' node;
// 2 to move 'end' node;
// 3 to create a wall;
const [isMouseDown, setIsMouseDown] = useState(0);
//Initializes Grid
useEffect(() => {
const cells = [];
const startNodeRow = 6;
const startNodeCol = 10;
const endNodeRow = 6;
const endNodeCol = 17;
for (let row = 0; row < ROWS; row++) {
const currentRow = [];
for (let col = 0; col < COLUMNS; col++) {
const node = {
row,
col,
isWall: false,
isStart: row === startNodeRow && col === startNodeCol,
isEnd: row === endNodeRow && col === endNodeCol,
distance: Infinity,
visited: false,
previousNode: null,
};
currentRow.push(node);
}
cells.push(currentRow);
}
setNodesMatrix(cells);
}, []);
useEffect(() => {
clearAllNodesStyles();
}, [isMouseDown]);
const resetMatrix = (matrix) => {
const copyCat = [...matrix];
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLUMNS; col++) {
const node = copyCat[row][col];
const newNode = {
...node,
distance: Infinity,
previousNode: null,
visited: false,
isWall: node.isStart || node.isEnd ? false : node.isWall,
};
copyCat[row][col] = newNode;
}
}
return copyCat;
};
const updateNodes = (isWall, isStart, newRow, newCol) => {
const newGrid = resetMatrix(nodesMatrix);
if (isWall) return setNodesMatrix(newGrid);
if (isStart) {
setStartNodeRow(newRow);
setStartNodeCol(newCol);
} else {
setEndNodeRow(newRow);
setEndNodeCol(newCol);
}
setNodesMatrix(newGrid);
};
const handleTouchBoundaryLine = (e) => {
const prevParent = e.target.id ? e.target : null;
if (isMouseDown === 1 || isMouseDown === 2) {
if (prevParent) {
const [prevRow, prevCol] = prevParent.id.match(/\d+/g);
const nodePointer = nodesMatrix[prevRow][prevCol];
if (isMouseDown === 1) nodePointer.isStart = true;
if (isMouseDown === 2) nodePointer.isEnd = true;
let clickEvent = document.createEvent("MouseEvents");
clickEvent.initEvent("mouseup", true, true);
prevParent.dispatchEvent(clickEvent);
}
}
};
return (
<div className="flex flex-col self-center">
<div id="grid-boundary" onMouseLeave={handleTouchBoundaryLine}>
{nodesMatrix.map((row, rowIndex) => {
return (
<div key={rowIndex} className="flex">
{row.map((node, index) => {
const { row, col, isWall, isStart, isEnd } = node;
return (
<Node
key={index}
row={row}
col={col}
isWall={isWall}
isStart={isStart}
isEnd={isEnd}
handleState={{ nodesMatrix, updateNodes }}
handleMouseState={{ isMouseDown, setIsMouseDown }}
/>
);
})}
</div>
);
})}
</div>
<button
onClick={() =>
visualizeDijkstra(nodesMatrix, {
startNodeRow,
startNodeCol,
endNodeRow,
endNodeCol,
speed,
})
}
className="h-20 w-full text-white hover:bg-blue-800 bg-blue-700 place-self-end"
>
Search Path
</button>
</div>
);
};
export default Grid;
I have added a boundary line/div around the grid. So, whenever the mouse leaves that div, it will dispatch mouseup event on the previous visited Node and mark it as the start/end Node.

Module - IIFE isn't automatically called

I created a module but it is not automatically rendered in my page, I have to manually call Board.renderBoard() in the console for the Board to appear. What am I doing wrong ?
here's the entire code:
const Board = (() => {
const board = document.querySelector('.board');
const createTiles = () => {
tile = document.createElement('div');
tile.classList.add('tile');
return tile;
};
const renderBoard = () => {
for (let i = 0; i < 9; i++) {
createTiles();
board.appendChild(createTiles());
}
};
return {
renderBoard
};
})();
I created a module but it is not automatically rendered in my page, I have to manually call Board.renderBoard() in the console for the Board to appear.
JS doesn't just call function unless you tell it to call these functions.
Your module simply defines an object Board with a method renderboard. There's nothing that tells JS to call this method.
You could add the call right after declaring the module.
const Board = (() => {
...
})();
Board.renderBoard();
But if all you want is that the code initially builds the board you can do:
(() => {
const board = document.querySelector('.board');
const createTiles = () => {
const tile = document.createElement('div'); // please declare your variables
tile.classList.add('tile');
return tile;
};
for (let i = 0; i < 9; i++) {
createTiles(); // what is this line for?
board.appendChild(createTiles());
}
})();
or
(() => {
const board = document.querySelector('.board');
for (let i = 0; i < 9; i++) {
const tile = document.createElement('div');
tile.classList.add('tile');
board.appendChild(tile);
}
})();
or maybe even
document.querySelector('.board').innerHTML = '<div class="tile"></div>'.repeat(9);

Categories