How to detect each "this" on click javascript - javascript

I want to basically detect click event on each card and save this card in a variable but secondGuess is shadowed by firstGuess. What's the solution?
for (let i = 0; i < cards.length; i++) {
//ADDING FLIP EFFECT TO EACH CARD
toggleFlipEffect(cards[i]);
cards[i].addEventListener("click", compareCards, false);
}
function compareCards() {
let points = 0;
let tries = 0;
const firstGuess = this.getElementsByTagName("img")[0].alt;
const secondGuess = this.getElementsByTagName("img")[0].alt;
console.log(firstGuess);
console.log(secondGuess);
if (firstGuess === secondGuess) {
points += 1;
console.log(points);
} else {
firstGuess.classList.remove("card--flip");
secondGuess.classList.remove("card--flip");
}
}
}

Without the HTML and code for toggleFlipEffect it is not possible to know how you have implemented the card flipping. In essence you need to act differently to a click on the first card than on the second card. On the first, you really only need to note which card it is. Only when it is the second time, you need to perform the comparison.
Note that when a guess is wrong, you would need to have a delay before concealing those cards again, or else the user would not have the time to see the second card.
Here is how you could do it:
function toggleFlipEffect() {} // mock-up.
var cards = document.querySelectorAll('div');
for (let i = 0; i < cards.length; i++) {
//ADDING FLIP EFFECT TO EACH CARD
toggleFlipEffect(cards[i]);
// *** add data attribute for quicker access
cards[i].dataset.value = cards[i].getElementsByTagName("img")[0].alt;
cards[i].addEventListener("click", compareCards, false);
}
// Your variables should not be local to the click event handler:
let points = 0;
let tries = 0;
let revealed = [];
function compareCards() {
// do not allow more than 2 cards to be revealed in one turn
if (revealed.length >= 2) return;
// reveal clicked card
this.classList.add("card--flip");
// add it to array with the previous one (if there was one)
revealed.push(this);
// Need another card?
if (revealed.length < 2) return; // allow for second choice
// We have two revealed cards. Check if they are the same
if (revealed[0].dataset.value === revealed[1].dataset.value) {
points += 1; // Yes, keep score
revealed = []; // initialise for next turn
score.textContent = points;
} else {
// Different: conceal these two cards again, but after a delay
setTimeout(_ => {
revealed.forEach(elem => elem.classList.remove("card--flip"))
revealed = []; // initialise for next turn
}, 1000);
}
}
.card--flip {
background-color: white;
}
div {
background-color: black;
margin: 5px;
width: 50px;
height: 50px;
display: inline-block;
}
<div><img src="https://image.flaticon.com/icons/svg/418/418278.svg" alt=1></div>
<div><img src="https://image.flaticon.com/icons/svg/418/418268.svg" alt=2></div>
<div><img src="https://image.flaticon.com/icons/svg/418/418275.svg" alt=3></div><br>
<div><img src="https://image.flaticon.com/icons/svg/418/418268.svg" alt=2></div>
<div><img src="https://image.flaticon.com/icons/svg/418/418278.svg" alt=1></div>
<div><img src="https://image.flaticon.com/icons/svg/418/418275.svg" alt=3></div><br>
score: <span id="score"></span>

The question isn't very clear, but I think you need to distinguish between the first and second click on cards. For that, I suggest you to use a global bool variable, initially set to false.
var first_click = false;
Then, modify your function like this:
function compareCards() {
first_click = !first_click;
if (first_click) {
//code you need after first click
} else {
//code you need after second click
}
//code you need to do anyway independently if first or second click.
}

Related

Card Game: My function won't end after X amount of clicks

I've been researching how to fix my issue for the past week but can't. I am creating a "click a card and it adds to score" card game but I am having trouble getting the clicks to stop after x amount of cards have already been clicked. I have created a clickCounter function that tracks the clicks and executes with every click. I have also added an isGameOver boolean to stop the function once it hits a certain amount of clicks. However, the boolean doesn't execute as I'd hoped in the clickCounter function.
Long story short: I just want the game to end once x amount of cards have been clicked.
Here is the JavaScript:
let isGameOver = false;
let counter = 0;
const clickCounter = function () {
switch (counter) {
case 0:
case 1:
counter += 1
break;
case 2:
isGameOver; //this is where I want the game to end//
break;
}
//this keeps track of the counts in console//
console.log(`counter equals: ${counter}`);
}
//this is my function that runs everything//
const cardSelection = function () {
randomNumber() //just a function to generate random #//
//beginning of if statement//
if(!isGameOver) {
//CARD ONE//
document.querySelector('#cardOne').addEventListener('click', function (e) {
cardOneFront(); //this changes card appearance, ignore please//
clickCounter(); //clickCounter is being called here//
if (randomNums[0]) {
scoreDisplay.innerText = `SCORE: ${scoreCount += randomNums[0]}`;
console.log(`score just added: ${randomNums[0]}`);
}
this.style.pointerEvents = 'none'
});
//CARD TWO//
document.querySelector('#cardTwo').addEventListener('click', function () {
cardTwoFront(); //this changes card appearance, ignore please//
cardCounter(); //clickCounter is being called here//
if (randomNums[1]) {
scoreDisplay.innerText = `SCORE: ${scoreCount += randomNums[1]}`;
console.log(`score just added: ${randomNums[1]}`);
}
this.style.pointerEvents = 'none'
});
} //if statement ends here//
} //cardSelection function ends here//
cardSelection()
I have visualization of it too. See Here:
what appears in console
I have 10+ cards but I only included two here to keep the code from being super long but the cards all have the similar code and their own individual click events. So, according to the image above, I want card three to not be clickable after counter = 2. But, as you see in the image, even though counter has equaled 2, card 3 is clickable anyways.
I hope this explanation was thorough and I appreciate any help as I am newer to coding. Also, I am coding all of this using vanilla JavaScript.
Thank you!
i think you need to add 'true' to case 2
break;
case 2:
isGameOver = true; //this is where I want the game to end//
break;
}
I guess you already have tried, but i dont see what else would be wrong with the code,
you can also try to add an 'else' to the code. i can only see and If(!isGameOver)
Do the check inside click handlers.
Add card class on each card (this will help to find all cards in one go, so that we can disable each at one place/function).
break;
case 2:
isGameOver = true; //this is where I want the game to end//
disableCards();
break;
}
function disableCards() {
// before doing this add `card` class on each card
let cardsEl = document.querySelector('.card'); // document.getElementsByClassName('card')
for(let i = 0; i< cardsEl.length; i++) {
cardsEl[i].classList.add('disableCard');
}
}
Add CSS into stylesheet
.disableCard {
pointer-events: none;
opacity: 0.7;
}
//CARD ONE//
document.querySelector('#cardOne').addEventListener('click', function (e) {
if(!isGameOver) {
cardOneFront(); //this changes card appearance, ignore please//
clickCounter(); //clickCounter is being called here//
if (randomNums[0]) {
scoreDisplay.innerText = `SCORE: ${scoreCount += randomNums[0]}`;
console.log(`score just added: ${randomNums[0]}`);
}
this.style.pointerEvents = 'none'
}
});
And if click handlers have quite similar code you can register them through for loop. like(it is just suggestion otherwise if the logic is not same for each card you can attach for eachone separatoly)
//add handlers//
for (let I = 0; I< cards.length; i++) {
document.querySelector('#card-'+i).addEventListener('click', function (e) {
if(!isGameOver) {
cardFront(i); //this changes card appearance, ignore please//
clickCounter(); //clickCounter is being called here//
if (randomNums[i]) {
scoreDisplay.innerText = `SCORE: ${scoreCount += randomNums[i]}`;
console.log(`score just added: ${randomNums[i]}`);
}
this.style.pointerEvents = 'none'
}
});
}
You are not setting isGameOver = true
try isgameOver = true in
isGameOver; //this is where I want the game to end// line

Accessing child elements in Javascript array via onclick (for loop)

I am looping through an array like this:
<% thoughts.docs.forEach(function(thought, i) { %>
<div class="square-box-container">
<div class="pyp-image-container">
<div class="image-overlay">
By default, element class name 'image-overlay' is hidden with display: 'none'. I am trying to create a function with an onclick event, so when user clicks on div 'square-box-container', the image overlay for that element only changes to display: 'block'.
Currently I have the below code but I think my inner loop in wrong, as when I click on a square box container, the image overlays for ALL the square box containers change to display: 'block', as opposed to that container overlay only. Can anyone advise what I'm doing wrong please?
var containerItems = document.getElementsByClassName("square-box-container");
var overlayItems = document.getElementsByClassName("image-overlay");
for (var i = 0; i < containerItems.length; i ++) {
containerItems[i].onclick = function() {
for (var i = 0; i < overlayItems.length; i ++) {
overlayItems[i].style.display = 'block';
}
}
}
I'm not very familiar with use of child nodes, is that what is required here? Thanks
If you want only the associated element to have its display changed, don't loop inside the click handler - and use let instead of var.
for (let i = 0; i < containerItems.length; i++) {
containerItems[i].onclick = function () {
overlayItems[i].style.display = 'block';
}
}
Another option is to omit the overlayItems collection entirely, and navigate from the clicked element instead.
for (const container of document.getElementsByClassName("square-box-container")) {
container.addEventListener('click', (e) => {
e.currentTarget.querySelector('.image-overlay').style.display = 'block';
});
}

How do I make an Array of Buttons out of this in JS? And How do I set Random Images with this?

So I`m trying to make a Memory Game. At the moment I´m trying to set the Image of the card randomly but my code just changes to top Image.
var images = ["url(IMG1.jpg)","url(IMG2.jpg)"...];
var randomIMG =0;
var card = "<div class='flip-card'><div class='flip-card-inner'><div class='flip-card-front'><button id='button'onclick='Flipfront(this)'style='width:300px;height:300px; marign:50px; background-image:url(Frontpage.jpg);'></button></div><div class='flip-card-back'><button id='button2'onclick='Flipback(this)'style='width:300px;height:300px; marign:50px; background-image:images[randomIMG];background-repeat:no-repeat;background-size:contain;'></button></div></div></div>"
for (let i = 0; i < level; i++) {
document.querySelector("#container").innerHTML +=card;
}
function RandomBG(){
for (let i = 0; i<level;i++){
randomIMG = Math.floor(Math.random()*9)+0;
document.getElementById("button2").style.backgroundImage = images[randomIMG];
}
}
function Flipfront(el){
RandomBG();
el.closest('.flip-card').classList.add('flipped');
flipped++;
}
And Also for later on, how do I put all my Buttons into an array?
In this line
document.getElementById("button2").style.backgroundImage = images[randomIMG];
you are setting the background of button2. You need to ensure that you get the correct element:
function RandomBG(el){
for (let i = 0; i<level;i++){
randomIMG = Math.floor(Math.random()*9)+0;
el.style.backgroundImage = images[randomIMG];
}
}
and that you pass it:
function Flipfront(el){
RandomBG();
el.closest('.flip-card').classList.add('flipped');
flipped++;
}
If all you are trying to do is get all the buttons in the page:
let btns = document.querySelectorAll("button");
And if you want a few specific ones, change the css selector.

how to show array element one by one onclick event in javascript?

I have textarea and storing in array onclick i need to show one by one from the last element and redo onclick one by one from where user clicks. i am doing a custom undo and redo functionality.
var stack =[];
jQuery('#enter-text').keypress(function() {
console.log(jQuery('#enter-text').val());
stack.push(jQuery('#enter-text').val());
})
jQuery('#undo_text').click(function() {
console.log(stack.pop());
})
jQuery('#redo_text').click(function() {
// how to redo onclik where user undo text
})
I have created jsfiddle
https://jsfiddle.net/k0nr53e0/4/
Make an array of old values, so something like this:
var deleted_stack = [];
// your other code
jQuery('#undo_text').click(function() {
deleted_stack.push(stack.pop());
})
jQuery('#redo_text').click(function () {
stack.push(deleted_stack.pop());
}
If you need to be able to do this also in the middle of the text, you should keep track of cursor position. Then your deleted stack should look more like this:
deleted_stack = [
{
char_idx: 2, // index of position in string where it was deleted, but
// then you shouldn't be deleting characters with .pop()
char_val: 'a'
},
... // other objects like first one
]
But then deleting also becomes more complex...
instead if keeping different stacks for the actions you've done, and undone, you can keep them in one Array, and memorize the current position:
var stack = [ jQuery('#enter-text').val() ], index = 0;
updateButtonsDisabled();
jQuery('#enter-text').keypress(function() {
//adding the current action
stack[++index] = jQuery('#enter-text').val();
//removing the entries after the last one you added: they belong to a different redo-stack
stack.length = index+1;
updateButtonsDisabled();
})
jQuery('#undo_text').click(function() {
if(!index) return;
jQuery('#enter-text').val(stack[--index]);
updateButtonsDisabled();
})
jQuery('#redo_text').click(function() {
if(index === stack.length-1) return;
jQuery('#enter-text').val(stack[++index]);
updateButtonsDisabled();
})
//just some sugar
function updateButtonsDisabled(){
jQuery('#undo_text').toggleClass("disabled", index === 0);
jQuery('#redo_text').toggleClass("disabled", index === stack.length-1);
}
index holds the position in stack of the currently shown value. You can undo and redo as much as you want, but as soon as you start typing, the redo-stack will be cleared.
You should consider limiting the items you want to keep in stack, or you'll allocate quite some memory. And you could change the logic for keypress to wait for a pause of like 300ms before you update the stack. That would decrease the items in your stack tremendously.
Edit: made a snippet implementing the possible changes I mentioned, like detached update, and limited stacksize. Take a look at that
//this value is kept small for testing purposes, you'd probably want to use sth. between 50 and 200
const stackSize = 10;
//left and right define the first and last "index" you can actually navigate to, a frame with maximum stackSize-1 items between them.
//These values are continually growing as you push new states to the stack, so that the index has to be clamped to the actual index in stack by %stackSize.
var stack = Array(stackSize),
left = 0,
right = 0,
index = 0,
timeout;
//push the first state to the stack, usually an empty string, but not necessarily
stack[0] = $("#enter-text").val();
updateButtons();
$("#enter-text").on("keydown keyup change", detachedUpdateText);
$("#undo").on("click", undo);
$("#redo").on("click", redo);
//detach update
function detachedUpdateText() {
clearTimeout(timeout);
timeout = setTimeout(updateText, 500);
}
function updateButtons() {
//disable buttons if the index reaches the respective border of the frame
//write the amount of steps availabe in each direction into the data-count attribute, to be processed by css
$("#undo")
.prop("disabled", index === left)
.attr("data-count", index - left);
$("#redo")
.prop("disabled", index === right)
.attr("data-count", right - index);
//show status
$("#stat").text(JSON.stringify({
left,
right,
index,
"index in stack": index % stackSize,
stack
}, null, 2))
}
function updateText() {
var val = $("#enter-text").val().trimRight();
//skip if nothing really changed
if (val === stack[index % stackSize]) return;
//add value
stack[++index % stackSize] = val;
//clean the undo-part of the stack
while (right > index)
stack[right-- % stackSize] = null;
//update boundaries
right = index;
left = Math.max(left, right + 1 - stackSize);
updateButtons();
}
function undo() {
if (index > left) {
$("#enter-text").val(stack[--index % stackSize]);
updateButtons();
}
}
function redo() {
if (index < right) {
$("#enter-text").val(stack[++index % stackSize]);
updateButtons();
}
}
#enter-text {
width: 100%;
height: 100px;
}
#undo,
#redo {
position: relative;
padding-right: 1em;
}
#undo:after,
#redo:after {
content: attr(data-count);
position: absolute;
bottom: 0;
right: 0;
font-size: 0.75em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<textarea id="enter-text"></textarea>
<button id="undo">undo</button>
<button id="redo">redo</button>
<pre id="stat">
</pre>
This is a design decision, and it depends fully on your user needs and how complex you are willing to make it.
The way it is usually done is to keep 2 lists, one for undo, and one for redo.
Every time an action is undone, it is added to the redo list. Every time a new action is taken after undoing, the redo list is destroyed.
var stack = [];
var redo = [];
jQuery('#enter-text').keypress(function() {
console.log(jQuery('#enter-text').val());
stack.push(jQuery('#enter-text').val());
redo = []; // <-----
})
jQuery('#undo_text').click(function() {
redo.push(stack.pop()); // <-----
})
jQuery('#redo_text').click(function() {
stack.push(redo.pop()) // <-----
})
You can use the keyup event and keep always the entered data on the stack array. These way you can update the value of field $('#enter-text') with the appropriate array index that will be updated in the variable index:
var $textEnter = $('#enter-text'),
stack = [],
index = 0;
$textEnter.on('keyup', function () {
stack.push($textEnter.val());
index = stack.length - 1;
});
$('#undo_text').on('click', function () {
if (index >= 0) {
$textEnter.val(stack[--index]);
}
});
$('#redo_text').on('click', function () {
if (index < stack.length - 1) {
$textEnter.val(stack[++index]);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="enter-text"></textarea>
<div id="drag-area-selected">
</div>
<button id="undo_text">
Undo
</button>
<button id="redo_text">
Redo
</button>

Heatmap for Link clicks with only one function for all

I got a problem while writung a code for a Heatmap based on link clicks.
I have a Page with different articel teasers. When i click the link the artical itself opensup. Now i want to track in real time how often which articel is clicked so i can decide which articel is relevant to users and place it like on the first articel space and so on.
Therefor i wrote a function which tracks the clicks with a counter and show them in the div.
Now my problem is. That i cant write like 20 functions for counters and 20 for resets. I want to have one function which can decide which articel was clicked and add +1 to the counter of the link.
My Code for the counter and the reset + the link text gets red after more than 5 clicks.
var count1 = 0;
function heatmap(id) {
if(id==1){
count1++;
document.getElementById("count1").innerHTML = count1;
}
if(count1>=5){
document.getElementById("link1").style.color = "red";
}
}
function reset(id){
if(id==1){
count1=0;
document.getElementById("count1").innerHTML = 0;
}
}
My html code so far
<div style="text-align:center; font-size:20px; font-family: arial;">
<div id="link1" onclick="heatmap(1)">This is the first Link</div><br>
<div id="count1">0</div><br>
<button onclick="reset(1)">RESET</button>
<button onclick="save(1)">SAVE</button>
</div>
Now my main problem is that i only want one function for all the link tracking.
Is there a possibiblity to write the code in a way the variables are dynamicly so that the function can decide which link was clicked.
for example something like:
var count + ID = 0;
function heatmap(ID) {
count + ID ++;
document.getElementById("count" + ID).innerHTML = count+ID;
}
if(count + ID >=5){
document.getElementById("link + ID").style.color = "red";
}
}
function reset(id){
count + ID =0;
document.getElementById("count + ID").innerHTML = 0;
}
}
I already searched this homepage and did some google searches but all i found was working with arrays or lists. But i think this wouldnt realy work for me since i want to give my function an ID and later add this id to the varibale name like count + ID = count | count + ID = count2 same with the document.getElementById("count" + ID).innerHTML = count+ID; = document.getElementById("count1").innerHTML = count1;
As you can see the link got a onclick="heatmap(ID)" this ID will be added to every articel Link and will go from the first articel with 1 to the last articel with like 20.
If i change an Articel the counter will be resetet and the ID will be changed to its position. So there will always be a identifier with the ID which can be used for the counter.
You could loop through all articles and store a counter on each element which you update or reset once the specific button inside the article was clicked:
var articles = document.querySelectorAll('article');
for(var i=0; i < articles.length; i++) {
articles[i].querySelector('.save').addEventListener('click', updateCounter);
articles[i].querySelector('.reset').addEventListener('click', resetCounter);
articles[i]['counter'] = 0;
}
function updateCounter(ev) {
var article = ev.target.parentNode;
article.counter++;
article.querySelector('.counter').innerHTML = article.counter;
/* Some server synchronisation should go here */
}
function resetCounter(ev) {
var article = ev.target.parentNode;
article.counter = 0;
article.querySelector('.counter').innerHTML = article.counter;
/* Some server synchronisation should go here */
}
DEMO
But to permanently store the counters you have to synchronize your results with a server, respectively a database.
If you just want to use one function you could realize it like this:
var articles = document.querySelectorAll('article');
for(var i=0; i < articles.length; i++) {
articles[i].querySelector('.save').addEventListener('click', checkCounter);
articles[i].querySelector('.reset').addEventListener('click', checkCounter);
articles[i]['counter'] = 0;
}
function checkCounter(ev) {
var article = ev.target.parentNode,
button = ev.target;
if(button.classList[0] === 'save') {
article.counter++;
} else if(button.classList[0] === 'reset') {
article.counter = 0;
}
article.querySelector('.counter').innerHTML = article.counter;
}
DEMO

Categories