I'm using the scrollspy of Bootstrap-5.2 to update the displayed text whithin the scrollBox, when reaching the top or bottom part (pre-reload-item/post-reload-item).
The funktion for the bottom shiftContentDown works fine, but for shiftContentUp it works the first time only. After that the scrollspy doesn't activate the reload-top element anymore unless I scroll down to the post-reload-item (->reload-bottom activates -> shiftContentDown fires) and then up again.
I suspect it has something to do with the line document.getElementById('scrollBox').scrollBy(0, count * 24). Without it the new content of the pre-reload-item would be in fokus and the reload-top element stays active. With it the previous content of the pre-reload-item now part of the mid-reload-item stays visible and the reload-top element is no longer active. I'd expect it to activate again if I scroll up now, but for some reason it doesn't.
let nextIndex = 0;
let allContent = [];
let displayed = [0, 0]
let eof = false;
let pre = document.getElementById('pre-reload-item');
let mid = document.getElementById('mid-reload-item');
let post = document.getElementById('post-reload-item');
document.getElementById('scrollBox').addEventListener('activate.bs.scrollspy', () => {
let current = document.querySelector('#indexBox > a.active');
if (current === document.getElementById('reload-bottom')) {
shiftContentDown()
} else if (current === document.getElementById('reload-top')) {
shiftContentUp()
}
});
function shiftContentDown(count = 100, buffer = 0.5) {
let shift = allContent.length - displayed[1];
if (!eof && shift < count) {
getMoreContent(nextIndex);
} else {
let addition = allContent.slice(displayed[1], displayed[1] + count);
addition.forEach(l => {
if (pre.childElementCount >= (count * buffer)) {
pre.removeChild(pre.childNodes[0]);
displayed[0]++;
}
if (mid.childElementCount >= (count * (1 + 2*buffer)))
pre.appendChild(mid.childNodes[0]);
if (post.childElementCount >= (count * buffer) || post.childNodes.length > mid.childNodes.length)
mid.appendChild(post.childNodes[0]);
post.appendChild(l);
displayed[1]++;
});
}
}
function shiftContentUp(count = 100) {
let addition = allContent.slice(Math.max(displayed[0] - count, 0), displayed[0]);
addition.reverse().forEach(l => {
pre.insertBefore(l, pre.childNodes[0]);
displayed[0]--;
mid.insertBefore(pre.childNodes[pre.childElementCount - 1], mid.childNodes[0]);
post.insertBefore(mid.childNodes[mid.childElementCount - 1], post.childNodes[0]);
post.removeChild(post.childNodes[post.childElementCount - 1]);
displayed[1]--;
});
document.getElementById('scrollBox').scrollBy(0, count * 24);
}
function getMoreContent(index, chunkSize=100, last=1000) {
for (let i=0; i < chunkSize; i++) {
if (nextIndex < last) {
let newLineNode = document.createElement('div');
newLineNode.innerHTML = '----- ' + (nextIndex++) + ' -----';
allContent.push(newLineNode);
} else {
eof = true;
}
}
shiftContentDown();
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.3/dist/css/bootstrap.min.css" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<div id="indexBox" class="list-group" style="display: none">
<a id="reload-bottom" class="list-group-item list-group-item-action" href="#post-reload-item">reload bottom</a>
<a id="reload-top" class="list-group-item list-group-item-action" href="#pre-reload-item">reload top</a>
</div>
<div id="scrollBox" data-bs-spy="scroll" data-bs-target="#indexBox" data-bs-smooth-scroll="true" data-bs-offset="0" class="scrollspy-example" tabindex="0" style="height: 300px; overflow-y: scroll">
<div id="pre-reload-item" class="text-bg-danger" style="white-space: pre-line"></div>
<div id="mid-reload-item" class="text-bg-info" style="white-space: pre-line"></div>
<div id="post-reload-item" class="text-bg-danger" style="white-space: pre-line"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
Related
I have such an issue : I need to make smooth sub menu items fading with delay on mouseenter/leave, and i made a code
HTML + tailwind
<nav class="hidden shrink-0 xl:flex justify-center gap-x-8 items-center hover:[&>div>a]:opacity-100 [&>div>a]:opacity-50 [&>div>a]:transition-all">
<div>
Nav Hover
<div class="sub-nav absolute pt-12 pb-3 flex flex-col gap-y-2 [&>a]:text-gray-500 hover:[&>a]:text-black [&>a]:transition [&>a]:ease-in-out [&>a]:duration-300">
Item1
Item2
Item3
Item4
Item5
</div>
</div>
<div>Nav 2</div>
<div>Nav 3</div>
<div>Nav 4</div>
</nav>
JS
document.querySelector('#header nav div').addEventListener('mouseenter', function () {
if (this.querySelector('div') != null) {
this.querySelector('div').classList.remove('hidden')
let subNav = Array.from(this.querySelectorAll('div.sub-nav > a'))
console.log(subNav.length)
for (let i = 0; i < subNav.length; i++) {
console.log(subNav[i]);
setTimeout(function () {
subNav[i].style.opacity = 1
}, 100 * i);
}
}
})
document.querySelector('#header nav div').addEventListener('mouseleave', function () {
if (this.querySelector('div') != null) {
if (!this.querySelector('div').classList.contains('hidden')) {
let subNav = Array.from(this.querySelectorAll('div.sub-nav > a'))
console.log(subNav.length)
for (let i = subNav.length-1, d = 0; i >= 0, d < subNav.length; i--, d++) {
console.log(subNav[i]);
setTimeout(function () {
subNav[i].style.opacity = 0
if(i == 0){
this.querySelector('div').classList.add('hidden')
}
}, 100 * d);
}
}
}
})
it has two main problems :
If I enter area and then quickly leave it - the last items keeps visible and fadeOut animation breaks. I tried to check if opacity > 0 and then set it to 0^ but it doesnt work
This gives me error "Uncaught TypeError: this.querySelector is not a function"
if(i == 0){
this.querySelector('div').classList.add('hidden')
}
What the solution and best practices to the code? Thanks!
I tried opacity checking
I'm working on an assignment, where the user can draw inside an n*n grid. The tasks are as following:
Create a 16x16 grid as default grid upon page-load
Implement a mouseover-EventListener for the grid, that changes the background-color of cells to black
Add a reset button, that asks the user how big the new grid should be
Add 2 buttons that set the mouseover-EventListener (default black) to a different color. First button changes the color to yellow, second button to gray.
My problem:
I'm stuck with the last task. The 2 buttons work just fine. But they can be called only once. If I select yellow - it draws yellow. If I then select gray - it draws gray. After that, the button-clicks don't do anything anymore, but I can still draw in gray.
That's what I have done so far:
First, I querySelected the button-type and added an EventListener "click".
Second, in the colourHover function, a mouseover EventListener gets invoked, depending on the button's id.
Third, the EventListener-mouseover gets defined in the respective functions (grayColour(event), yellowColour(event)).
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body id="start">
<div class="container" id="grid">
</div>
<div><button type="button" class="button" id="gray">Gray Colour</button></div>
<div><button type="button" class="button" id="yellow">Yellow Colour</button></div>
<div><button type="button" id="button">Resize your grid manually.</button></div>
<script>
"use strict";
let container = document.getElementById("grid");
let n;
// creating a grid with number-input from user.
let makeGrid = function makeGrid(input) {
let c;
for (c = 0; c < (input * input); c++) {
let cell = document.createElement("div");
container.appendChild(cell).className = "squares";
};
container.style.gridTemplateColumns = `repeat(${input}, auto)`;
container.style.gridTemplateRows = `repeat(${input}, auto)`;
};
// function gets exectuted, only if a number and below 101
let numberCheck = function numberCheck() {
if ((n % n) === 0 && n <= 100) {
makeGrid(n);
};
while ((n % n) !== 0) {
alert("Please enter a number.");
n = prompt("Choose again.");
if ((n % n) === 0 && n <= 100) {
makeGrid(n);
}
};
while ((n % n) === 0 && n > 100) {
alert("The number is too high.");
n = prompt("Choose again.");
if ((n % n) === 0 && n <= 100) {
makeGrid(n);
}
else if ((n % n) !== 0) {
alert("Please enter a number");
n = prompt("Choose again");
if ((n % n) === 0 && n <= 100) {
makeGrid(n);
}
}
};
};
// default grid on first pageload
makeGrid(16);
// this function sets the hovered items colour to black on default / first pageload
let blackColor = function blackColor(event) {
let colour = event.target;
if (colour.className === "container") {
return;
}
else if (colour.className === "squares") {
colour.style.backgroundColor = "black";
};
};
// addEventListener for blackColor function
let hoverItems = document.querySelectorAll(".container");
hoverItems.forEach(element => { element.addEventListener("mouseover", blackColor)
});
// function to manual selecting a grid-size. Beforehand, old grid gets deleted
let removeElements = function removeElements(event) {
let elements = document.getElementsByClassName("squares");
while (elements.length > 0) {
elements[0].parentNode.removeChild(elements[0]);
};
n = prompt("Choose the size of your grid");
numberCheck(n);
};
// button for selecting a grid-size manually
let button = document.querySelector("#button");
button.addEventListener("click", removeElements);
// this function sets the background-color of the cells on mouseover to gray
let grayColour = function grayColour(event) {
let colour = event.target;
if (colour.className === "container") {
return;
}
else if (colour.className === "squares") {
colour.style.backgroundColor = "gray"
}
}
// this function sets the background-color of the cells on mouseover to yellow
let yellowColour = function yellowColour(event) {
let colour = event.target;
if (colour.className === "container") {
return;
}
else if (colour.className === "squares") {
colour.style.backgroundColor = "yellow";
}
}
let colourHover = function colourHover(event) {
let hover = document.querySelectorAll(".container");
switch (event.target.id) {
case "gray":
//hover.forEach(element => { element.removeEventListener("mouseover", yellowColour)
//});
//hover.forEach(element => { element.removeEventListener("mouseover", grayColour)
//});
hover.forEach(element => { element.addEventListener("mouseover", grayColour)
});
break;
case "yellow":
//hover.forEach(element => { element.removeEventListener("mouseover", yellowColour)
//});
//hover.forEach(element => { element.removeEventListener("mouseover", grayColour)
//});
hover.forEach(element => { element.addEventListener("mouseover", yellowColour)
});
break;
};
};
let colourBtn = document.querySelectorAll(".button");
colourBtn.forEach(element => { element.addEventListener("click", colourHover)
});
</script>
</body>
</html>
I appreciate any hint in the right direction. Really stuck right now.
The simplest way is to use CSS classes for styling the cells, and change the class of the table body in a button click handler. The state of the "widget" should be stored in a JS variable, that way you can avoid unnecessary DOM traversing on each click of the buttons. Something like this:
const buttons = document.querySelectorAll('button'),
table = document.querySelector('.hovered-colors');
// The state of the widget
let currentColor = 'black';
buttons.forEach(button => {
button.addEventListener('click', e => {
const newColor = e.target.getAttribute('data-color');
table.classList.remove(currentColor);
table.classList.add(newColor);
currentColor = newColor;
});
});
.black td:hover {
color: white;
background-color: black;
}
.gray td:hover {
color: black;
background-color: gray;
}
.yellow td:hover {
color: black;
background-color: yellow;
}
<table>
<tbody class="hovered-colors black">
<tr><td>R1C1</td><td>R1C2</td></tr>
<tr><td>R2C1</td><td>R2C2</td></tr>
<tr><td>R3C1</td><td>R3C2</td></tr>
</tbody>
</table>
<button data-color="gray">Gray</button>
<button data-color="yellow">Yellow</button>
class Cell {
constructor(game, index) {
this.isEmpty = false;
this.game = game;
this.index = index;
this.height = (game.height / game.dimension);
this.width = (game.width / game.dimension);
this.id = "cell-" + index;
this.cell = this.createCell();
$("#content").append(this.cell);
if (this.index === this.game.dimension * this.game.dimension - 1) {
this.isEmpty = true;
return;
}
this.setImage();
this.setPosition(this.index)
}
createCell() {
const cell = document.createElement("div");
$(cell).css({
"width": this.height + "px",
"height": this.width + "px",
"position": "absolute",
"border": "1px solid #fff"
})
$(cell).attr("id", this.id);
//On click Move to the Empty Space
$(cell).on("click", () => {
if (this.game.gameStarted) {
let validCells = this.game.checkValidCells();
let compareCells = [];
if (validCells.right) {
compareCells.push(this.game.cells[validCells.right].cell);
}
if (validCells.left || validCells.left === 0) {
compareCells.push(this.game.cells[validCells.left].cell);
}
if (validCells.top || validCells.top === 0) {
compareCells.push(this.game.cells[validCells.top].cell);
}
if (validCells.bottom) {
compareCells.push(this.game.cells[validCells.bottom].cell);
}
let i = this.game.cells.findIndex(item => item.cell === cell);
let j = this.game.findEmptyCell();
if (compareCells.indexOf(cell) != -1) {
[this.game.cells[i], this.game.cells[j]] = [this.game.cells[j], this.game.cells[i]];
this.game.cells[i].setPosition(i);
this.game.cells[j].setPosition(j);
if (this.game.checkWin()) {
alert("you won the game!!");
this.game.numberOfMoves = 0;
this.game.gameStarted = false;
}
this.game.numberOfMoves++;
$("#moves").html("Moves: " + this.game.numberOfMoves);
}
this.game.dragTheTile();
}
})
return cell;
}
setImage() {
const left = this.width * (this.index % this.game.dimension);
const top = this.height * Math.floor(this.index / this.game.dimension);
const bgPosition = -left + "px" + " " + -top + "px";
const bgSize = this.game.width + "px " + this.game.height + "px"
$(this.cell).css({
"background": 'url(' + this.game.imageSrc + ')',
"background-position" : bgPosition,
"background-size": bgSize
})
}
setPosition(index) {
const {left, top} = this.getPosition(index);
$(this.cell).css({
"left": left + "px",
"top": top + "px"
})
}
makeDraggable(index) {
let emptyCellIndex = this.game.findEmptyCell();
$(this.cell).draggable({
containment: "parent",
snap: this.game.cells[emptyCellIndex],
cursor: "move",
snapMode: "inner",
snapTolerance: 20,
helper: "clone",
opacity: 0.5
})
}
makeDroppable(index) {
$(this.cell).droppable({
drop: (event, ui) => {
let draggedCell;
draggedCell = ui.draggable;
let i = this.game.cells.findIndex(item => item.cell === draggedCell[0]);
let j = this.game.findEmptyCell();
[this.game.cells[i], this.game.cells[j]] = [this.game.cells[j], this.game.cells[i]];
this.game.cells[i].setPosition(i);
this.game.cells[j].setPosition(j);
this.game.clearDrag();
this.game.numberOfMoves++;
$("#moves").html("Moves: " + this.game.numberOfMoves);
if (this.game.checkWin()) {
alert("you won the game!!");
this.game.numberOfMoves = 0;
this.game.gameStarted = false;
} else {
this.game.dragTheTile();
}
}
})
}
getPosition(index) {
return {
left: this.width * (index % this.game.dimension),
top: this.height * Math.floor(index / this.game.dimension)
}
}
}
class GameBoard {
constructor(dimension){
this.dimension = dimension;
this.imageSrc = 'https://i.ibb.co/1XfXq6S/image.jpg'
this.cells = [];
let length = Math.min(window.innerHeight, window.innerWidth);
this.width = length - 100;
this.height = length - 100;
this.setup();
this.gameStarted = false;
this.numberOfMoves = 0;
}
setup() {
for(let i = 0;i < this.dimension * this.dimension; i++) {
this.cells.push(new Cell(this, i));
}
}
shuffle() {
for (let i = this.cells.length -1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[this.cells[i], this.cells[j]] = [this.cells[j], this.cells[i]];
this.cells[i].setPosition(i);
this.cells[j].setPosition(j);
}
}
findEmptyCell() {
return this.cells.findIndex(cell => cell.isEmpty)
}
checkValidCells() {
const emptyCell = this.findEmptyCell(),
topCell = emptyCell - this.dimension,
leftCell = emptyCell - 1,
rightCell = emptyCell + 1,
bottomCell = emptyCell + this.dimension;
const mod = emptyCell % this.dimension;
let left, right, top, bottom;
if (mod != 0) {
left = leftCell;
}
if (mod != this.dimension -1) {
right = rightCell;
}
if (emptyCell >= this.dimension) {
top = topCell;
}
if (emptyCell < this.dimension * (this.dimension - 1)) {
bottom = bottomCell
}
return {right: right, left: left, top: top, bottom: bottom};
}
findPosition(index) {
return this.cells.findIndex(cell => cell.index === index);
}
checkWin() {
for (let i = 0; i < this.cells.length; i++) {
if (i != this.cells[i].index) {
return false;
}
}
return true;
}
clearDrag() {
this.cells.forEach(cell => {
if($(cell.cell).data('ui-draggable')) $(cell.cell).draggable("destroy");
})
}
dragTheTile() {
this.clearDrag();
const validCells = this.checkValidCells();
let availableCells = [];
if (validCells.right) {
availableCells.push(this.cells[this.findPosition(validCells.right)].index);
}
if (validCells.left || validCells.left === 0) {
availableCells.push(this.cells[this.findPosition(validCells.left)].index);
}
if (validCells.top || validCells.top === 0) {
availableCells.push(this.cells[this.findPosition(validCells.top)].index);
}
if (validCells.bottom) {
availableCells.push(this.cells[this.findPosition(validCells.bottom)].index);
}
let emptyCellIndex = this.findEmptyCell();
availableCells.forEach(cell => {
this.cells[cell].makeDraggable(cell);
this.cells[emptyCellIndex].makeDroppable(cell);
})
}
solve() {
let i;
for (i = 0; i < this.cells.length; i++) {
let j = this.cells[i].index;
if (i != j) {
[this.cells[i], this.cells[j]] = [this.cells[j], this.cells[i]];
this.cells[i].setPosition(i);
this.cells[j].setPosition(j);
i--;
}
if (i === this.cells.length - 1) {
[this.cells[i], this.cells[i - 1]] = [this.cells[i - 1], this.cells[i]];
this.cells[i].setPosition(i);
this.cells[i - 1].setPosition(i - 1);
}
}
}
}
$(document).ready(() => {
let gb;
$("#startGame").on("click", () => {
gb.gameStarted = true;
gb.shuffle();
gb.numberOfMoves = 0;
$("#moves").html("Moves: " + gb.numberOfMoves);
gb.clearDrag();
gb.dragTheTile();
})
$("#solve").on("click", () => {
if (gb.gameStarted) {
gb.solve();
gb.clearDrag();
gb.dragTheTile();
}
})
$("#generate-puzzle").on("click", () => {
let number = parseInt($("#dimension").val());
if(number >= 3 && Number.isInteger(number)) {
gb = new GameBoard(number);
$("#content").css("display", "block");
$("#solve").css("display", "block");
$("#startGame").css("display", "block");
$("#enter-dimension").css("display", "none");
$("#content").css({
width: gb.width + "px",
height : gb.height + "px"
})
} else {
$("#alert").css("display", "block");
}
})
})
body {
background-color: #3d3d3d;
}
#content {
width: 400px;
height : 400px;
background-color: #fff;
margin: auto;
position: relative;
display: none;
}
#startGame {
margin: auto;
display: none;
}
#solve {
margin: auto;
display: none;
}
#alert {
display: none;
}
#moves {
margin: auto;
padding: 5px;
text-align: center;
color: #FFFF00;
}
#enter-dimension {
margin: auto;
}
#label-dimension {
color: #ddd;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- JQuery -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js" integrity="sha512-uto9mlQzrs59VwILcLiRYeLKPPbS/bT71da/OEBYEwcdNUk8jYIy+D176RYoop1Da+f9mvkYrmj5MCLZWEtQuA==" crossorigin="anonymous"></script>
<title>Image Puzzle</title>
</head>
<body>
<div class="container col-md-6">
<div id="moves">
Moves : 0
</div>
<div class="d-flex justify-content-center row">
<div class="col-md-6">
<div class="mt-5" id="enter-dimension">
<form>
<div class="form-group">
<label id="label-dimension" for="dimension">Enter the Dimension of the puzzle</label>
<input type="number" class="form-control" id="dimension">
</div>
<div class="alert alert-danger" id="alert" role="alert">
Enter a Valid Positive Integer Greater than or Equal to 3
</div>
<button type="button" class="btn btn-primary" id="generate-puzzle">Generate the Puzzle</button>
</form>
</div>
</div>
</div>
<div class="row">
<!--The GRid Layout and the tiles -->
<div class="mt-3" id="content">
</div>
</div>
<!--Button to Start the Game -->
<div class="row buttons">
<button type="button" class="btn btn-info mt-2" id="startGame">Start Game</button>
<button type="button" class="btn btn-success mt-2" id="solve">Solve This!</button>
</div>
</div>
</body>
</html>
The puzzle works perfectly but with one issue. If I drag-drop a cell to an empty cell I am unable to drag and drop that tile again to its previous position which is the current empty cell. The way I see it there is a problem somewhere in makeDraggable() and dragTheTile() functions.
When I move a cell with a mouse click I can drag and drop that same cell to its previous position (which is the current empty cell) or If I drag and drop a cell to an empty space I can move it to its previous position (which is the current empty cell) with mouse click. But after dragging the same cell cannot be dragged again. looks like draggable() is disabled for that particular cell.
Once a cell is dragged, The next move it should be a draggable cell (since it is a neighbour cell to the empty cell) When I make it draggable the "ui-draggable" class is added to it but "ui-draggable-handle" class is missing.
The problem was with the function where I cleared the draggable property:
clearDrag() {
this.cells.forEach(cell => {
if($(cell.cell).data('ui-draggable')) $(cell.cell).draggable("destroy");
})
}
The "destroy" method gives some conflicts.
The alternative method I found was to first initialize all the cells with draggable property but disable it in the beginning and enabling it again when I need.
the property used here was "disabled: true" and "disabled: false".
Instead of the dragTheTile() function I wrote a new function called dragTile() as follows:
dragTile() {
this.cells.forEach(cell => {
cell.makeDraggable();
})
const validCells = this.checkValidCells();
let availableCells = [];
if (validCells.right) {
availableCells.push(this.cells[this.findPosition(validCells.right)].index);
}
if (validCells.left || validCells.left === 0) {
availableCells.push(this.cells[this.findPosition(validCells.left)].index);
}
if (validCells.top || validCells.top === 0) {
availableCells.push(this.cells[this.findPosition(validCells.top)].index);
}
if (validCells.bottom) {
availableCells.push(this.cells[this.findPosition(validCells.bottom)].index);
}
let emptyCellIndex = this.findEmptyCell();
availableCells.forEach(cell => {
$(this.cells[cell].cell).draggable({disabled: false});
})
this.cells[emptyCellIndex].makeDroppable(availableCells);
}
This solved the problem very easily.
My question is simple I guess. I'm making a full calendar with Jquery. I want to know how can I do something like I'll show on the pictures.
User selected day 3 of month (it will appear as blue) and if he mouse hover on 8 all the numbers inside that range get a class. So.. the 4,5,6,7,8 get something. If he leaves 8 and return back 7 the 8 has no class added, should remove.
Calendar showing my idea, I did it manually:
Calendar showing that I have at this moment:
HTML
<div class="new-calendar-inside">
<div class="month-and-year-calendar">
<div class="left-arrow prev-month"></div>
<div class="month-year actual"></div>
<div class="right-arrow next-month"><i class="icon chevron right"></i>
</div>
</div>
<div class="calendar-days-list">
<table class="table table-bordered">
<tr class="days-of-the-week">
<th>S</th>
<th>T</th>
<th>Q</th>
<th>Q</th>
<th>S</th>
<th>S</th>
<th>D</th>
</tr>
</table>
</div>
</div>
<div class="calendar-buttons">
<button class="new-button no-border-button">Cancelar</button>
<button id="confirm" class="new-button no-border-button">Ok</button>
</div>
</div>
JQUERY
this.getCalendarTable().on("click", "td", function () {
var row = _this.getCalendarTable().find(".selected");
var rowOrange = _this.getCalendarTable().find(".selected-orange");
var table = _this.getCalendarTable();
if (_this.getContainer().find(".new-calendar.simple").hasClass("mini")) {
if ($(this).text() != "") {
if (_this.getContainer().find(".new-calendar.simple").hasClass("simple")) {
// $('.new-calendar.simple').find(".border-left").removeClass("border-left");
if ($(this).text() != "") {
if (row.length < 2) {
$(this).addClass("selected");
// ADICIONAR HOVER COM RATO ENQUANTO ESCOLHE ULTIMO
var days = $('.table td'),
first = days.index($('td.selected:first')),
last = 60;
var rangeDays = days.slice(first, last);
$(rangeDays).on("mouseover, mouseout", function () {
last = $(days).index($(this));
$(this).addClass("active");
});
_this.getContainer().find(".border-left").removeClass("border-left");
_this.getContainer().find(".border-right").removeClass("border-right");
_this.getContainer().find(".selected").eq(0).addClass("border-left");
_this.getContainer().find(".selected").eq(1).addClass("border-right");
_this.firstNumberCalendar();
$('td:contains(31)').addClass("border-right");
} else if (row.length > 1) {
row.removeClass("selected");
table.find(".active").removeClass("active");
$(this).addClass("selected");
}
if (row.length == 1) {
var last = days.index($('td.selected:last'));
var newSlice = days.slice(first, last);
newSlice.addClass("active");
$(rangeDays).off('mouseover');
$(rangeDays).off('mouseout');
}
}
}
Probably I need to get the last position as mouse hover but I don't know how to do it.
Regards.
Don't use mouseover and mouseout for the exact reason you've encountered.
Instead keep track of the start and end indices during the mouse events. Then simply compare if each day is between that range.
Make sure to take into account that end may be < start if the user selects in reverse order.
Also make sure to consider that the mouse may be released while hovering on no particular day.
1 click / drag:
let div = document.querySelector('div');
for (let i = 0; i < 31; i++) {
let span = document.createElement('span');
span.textContent = i + 1;
div.appendChild(span);
span.addEventListener('mousedown', () => beginSelection(i));
span.addEventListener('mousemove', () => updateSelection(i));
span.addEventListener('mouseup', () => endSelection(i));
}
document.addEventListener('mouseup', () => endSelection());
let selecting, start, end;
let beginSelection = i => {
selecting = true;
start = i;
updateSelection(i);
};
let endSelection = (i = end) => {
updateSelection(i);
selecting = false;
};
let updateSelection = i => {
if (selecting)
end = i;
[...document.querySelectorAll('span')].forEach((span, i) =>
span.classList.toggle('selected', i >= start && i <= end || i >= end && i <= start));
};
div {
display: flex;
width: 200px;
flex-wrap: wrap;
}
span {
width: 30px;
border: 1px solid;
padding: 5px;
user-select: none;
}
span.selected {
background: #adf;
}
span:hover:not(.selected) {
background: #cfefff;
}
<div><div>
2 clicks:
let div = document.querySelector('div');
for (let i = 0; i < 31; i++) {
let span = document.createElement('span');
span.textContent = i + 1;
div.appendChild(span);
span.addEventListener('click', () => toggleSelection(i));
span.addEventListener('mousemove', () => updateSelection(i));
}
let selecting, start, end;
let toggleSelection = i => {
if (selecting)
endSelection(i);
else
beginSelection(i);
};
let beginSelection = i => {
selecting = true;
start = i;
updateSelection(i);
};
let endSelection = (i = end) => {
updateSelection(i);
selecting = false;
};
let updateSelection = i => {
if (selecting)
end = i;
[...document.querySelectorAll('span')].forEach((span, i) =>
span.classList.toggle('selected', i >= start && i <= end || i >= end && i <= start));
};
div {
display: flex;
width: 200px;
flex-wrap: wrap;
}
span {
width: 30px;
border: 1px solid;
padding: 5px;
user-select: none;
}
span.selected {
background: #adf;
}
span:hover:not(.selected) {
background: #cfefff;
}
<div>
<div>
Would appreciate some guidance with this matching activity. I illustrate with the basic html and css code below - this is the format that I want, meaning, I want the contents (words/numbers) of the boxes to be changeable in the HTML section, I just need help with the javascript.
Aim: to match A to B, so for example, when user clicks on "aaaaaaaa" and "11111111" then the match is correct and both boxes disappears. However if for example the user clicks on "aaaaaaaa" and "33333333" then it is recorded and counted as an error which is then displayed on the page.
Also, how to add a timer from the moment the page is loaded till all the matches are made and the activity is finished ?
<html>
<head>
<style>
#wordBx
{
width:168px;
height:168px;
border:1px grey solid;
text-align:center;
padding:1px;
float:left;
}
.word
{
width:165px;
height:40px;
border:1px grey solid;
}
#numBox
{
width:50%;
height:400px;
border:1px blue solid;
text-align:center;
margin:1px;
float:left;
overflow:hidden;
}
.numb
{
width:180px;
height:70px;
border:1px blue solid;
margin:2px;
float:left;
font-size:11pt;
}
</style>
</head>
<body>
<div class="wordBx">
<div class="word">aaaaaaaa</div >
<div class"word">bbbbbbbb</div >
<div class="word">cccccccc</div >
<div class="word">dddddddd</div >
</div>
<div id="numBox">
<div class="numb">11111111</div >
<div class="numb">22222222</div >
<div class="numb">33333333</div >
<div class="numb">44444444</div >
</div>
</body>
</html>
var words = document.querySelectorAll('.word');
var numbers = document.querySelectorAll('.number');
var selectedWordIndex;
var selectedNumberIndex;
var timerInstance;
// Validate game settings
if (words.length !== numbers.length) {
return console.error('Words list size should be same as numbers list');
}
// Register click event handlers on all words and numbers
for (var i = 0; i < words.length; i++) {
words[i].setAttribute('data-index', i);
words[i].addEventListener('click', function(event) {
selectedWordIndex = this.dataset.index;
checkMatching();
});
}
for (var i = 0; i < numbers.length; i++) {
numbers[i].setAttribute('data-index', i);
numbers[i].addEventListener('click', function(event) {
selectedNumberIndex = this.dataset.index;
checkMatching();
});
}
function checkMatching() {
// When user selection matches
if (selectedWordIndex === selectedNumberIndex) {
// Remove pair
var word = document.querySelector('.word[data-index="' + selectedWordIndex + '"]')
var number = document.querySelector('.number[data-index="' + selectedNumberIndex + '"]')
word.parentNode.removeChild(word);
number.parentNode.removeChild(number);
// Reset selection
selectedWordIndex = null;
selectedNumberIndex = null;
// Check if game is ended
if (document.querySelectorAll('.word').length === 0) {
// Remove blocks and stop the timer
var words = document.querySelector('.words');
var numbers = document.querySelector('.numbers');
words.parentNode.removeChild(words);
numbers.parentNode.removeChild(numbers);
clearInterval(timerInstance);
}
} else if (selectedWordIndex && selectedNumberIndex) {
// Register an error while selection does not match
var errorCounter = document.querySelector('.errors .counter');
errorCounter.innerHTML = parseInt(errorCounter.innerHTML, 10) + 1;
}
}
function startTimer() {
var display = document.querySelector('.time');
var timer = 0, minutes, seconds;
timerInstance = setInterval(function () {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.innerHTML = minutes + ":" + seconds;
timer++;
}, 1000);
}
startTimer();
I have created JSFiddle with all of stuff you needed
JSFiddle (UPDATED)
Try something like :
HTML
<div id="wordBx">
<div id="word1" onclick="save(word1)" class='class1'>aaaaaaaa</div >
<div id="word2" onclick="save(word2)" class='class2'>bbbbbbbb</div >
<div id="word3" onclick="save(word3)" class='class3'>cccccccc</div >
<div id="word4" onclick="save(word4)" class='class4'>dddddddd</div >
</div>
<div id="numBox">
<div id="numb1" onclick="save(numb1)" class='class1'>11111111</div >
<div id="numb2" onclick="save(numb2)" class='class2'>22222222</div >
<div id="numb3" onclick="save(numb3)" class='class3'>33333333</div >
<div id="numb4" onclick="save(numb4)" class='class4'>44444444</div >
</div>
elements shouldn't have the same id.
And
JAVASCRIPT :
var old = undefined;
var number = 0;
function save(t) {
document.getElementById(t);
if(old != undefined && t.className == old.className && old != t) {
t.style.display = "none";
old.style.display = "none";
}
else{
number = number +1;
if ( number / 2 == 1 && number != 0) {
document.getElementById("try").innerHTML = "Try number " + parseInt(number/2);
}
}
old = t;
}
For the event to trigger when the game should end use :
setTimeout(myFunction, timeInMSeconds)
And for the timer you can use it again.