jQuery unbinding event after second click - javascript

I am creating a Memory Game for a class at school, and I am using Bootstrap and jQuery. See Github. For testing use this jsfiddle, as the github code will change, I've included it if you would like to fork it for your own purposes.
I've constructed the code on the following logic:
Pick with how many cards you want to play.
Cards get loaded and randomized. Each pair have the same class(card* and glyphicon*).
You click on one card, then on another, and if they match they get discarded, else you pick again.
The problem that I'm currently having is with the third step, namely when you click on the third card it shows the previous two, meaning I need to include something to escape the first click events. At least that was my first suggestion for the problem. If you have other suggestions to completely restructure the third step, please don't shy to elaborate why.
// check if picked cards' classes match
jQuery("[class^=card]").click(function() { //picking the first card
jQuery(this).css('color', '#000');
var firstCard = $(this);
var firstCardClass = $(this).find('[class*=glyphicon]').attr('class').split(' ')[1];
jQuery("[class^=card]").click(function() { //picking the second card
var secondCard = $(this);
var secondCardClass = $(this).find('[class*=glyphicon]').attr('class').split(' ')[1];
console.log(firstCardClass);
console.log(secondCardClass);
if (firstCardClass == secondCardClass) {
console.log("yes")
$(firstCard).css('color', '#005d00'); //make them green
$(secondCard).css('color', '#005d00');
setTimeout(function() {
$(firstCard).css('display', 'none'); //discard
$(secondCard).css('display', 'none');
}, 1000);
}
else {
console.log("no");
$(firstCard).css('color', '#cc0000'); //make them red
$(secondCard).css('color', '#cc0000');
setTimeout(function() {
$(firstCard).css('color', '#fff'); //hide again
$(secondCard).css('color', '#fff');
}, 1000);
}
});
});
Note that the icons should be white as the cards, made them grey to see witch ones match without the need of firebug. If you click on more then two cards you will see what the problem is (if I failed to explain it well). I tried with including click unbind events in the end of each statement, but couldn't make it work.
Try your best! Thanks!

EDITED:
Seems I misunderstood the question so here's how I would go about having such game.
First I'll have my cards to have a structure like this:
<span class="card" data-card-type="one">One</span>
I'll use data-card-type to compare whether two cards are of the same type
I'll have a global variable firstCard which is originally null, if null I assign the clicked card to it and if not I compare the clicked card with it and then whether it's a match or not, I assign null to it meaning another pairing has begun.
I'll do all the logic in one onclick, looks weird to have a click listener inside another makes it to somehow look over-complicated.
var firstCard = null;
$('.card').on('click', function() {
$(this).addClass('selected');
if(!firstCard)
firstCard = $(this);
else if(firstCard[0] != $(this)[0]) {
if(firstCard.data('card-type') == $(this).data('card-type')) {
firstCard.remove();
$(this).remove();
firstCard = null;
//$('.card.selected').removeClass('selected');
}
else {
firstCard = null;
$('.card.selected').removeClass('selected');
}
}
});
jsfiddle DEMO

when a card is clicked, you can add a class to that particular card (e.g. classname clickedcard). Whenever you click another card you can test if there are 2 cards having this clickedcard class. If so, you can take action, for example remove all the clickedcard classes and add one again to the newly clicked one.
In pseudo code I would do it something like this:
jQuery("[class^=card]").click(function() {
if (jQuery('.clickedcard').length == 2) {
// two cards where clicked already...
// take the actions you want to do for 2 clicked cards
// you can use jQuery('.clickedcard')[0] and jQuery('.clickedcard')[1]
// to address both clicked cards
jQuery('.clickedcard').removeClass('clickedcard');
} else {
// no card or only one card is clicked
// do actions on the clicked card and add classname
jQuery(this).addClass('clickedcard');
}
});

You could use `one' (to bind an event once):
$("[class^=card]").one(`click', firstCard);
function firstCard() { //picking the first card
$(this).css('color', '#000');
var firstCard = $(this);
var firstCardClass = $(this).find('[class*=glyphicon]').attr('class').split(' ')[1];
$("[class^=card]").one('click', secondCard);
function secondCard() { //picking the second card
var secondCard = $(this);
var secondCardClass = $(this).find('[class*=glyphicon]').attr('class').split(' ')[1];
console.log(firstCardClass);
console.log(secondCardClass);
if (firstCardClass == secondCardClass) {
console.log("yes")
$(firstCard).css('color', '#005d00'); //make them green
$(secondCard).css('color', '#005d00');
setTimeout(function() {
$(firstCard).css('display', 'none'); //discard
$(secondCard).css('display', 'none');
}, 1000);
}
else {
console.log("no");
$(firstCard).css('color', '#cc0000'); //make them red
$(secondCard).css('color', '#cc0000');
setTimeout(function() {
$(firstCard).css('color', '#fff'); //hide again
$(secondCard).css('color', '#fff');
}, 1000);
}
$("[class^=card]").one(`click', firstCard);
}
}

Related

I have a problem with if statement in javaScript

I want to check if the state of show to change the innerText of the button according to it but when I run it the else statment doesnt work
let showBtn = document.querySelector('.show-more');
showBtn.onclick = function () {
let show = false;
if (show === false) {
showBtn.innerText = 'Show Less';
show = true;
} else {
showBtn.innerText = 'Show more';
}
}
showBtn is false on each click in your code. It should not be assigned in the onclick handler. Try this:
let showBtn = document.querySelector('.show-more');
let show = false;
showBtn.onclick = function() {
if (show === false) {
showBtn.innerText = 'Show Less';
} else {
showBtn.innerText = 'Show more';
}
show = !show;
}
<button class="show-more" />Show</button>
Reset the value of show, every click by this way.
let showBtn = document.querySelector('.show-more');
show = false;
showBtn.onclick = function () {
if (show === false) {
showBtn.textContent= 'Show Less';
show = true;
} else {
show = false
showBtn.textContent= 'Show more';
}
}
You should not even be using a variable here in the first place. The accepted answer just creates a new bug as it will break as soon as you have more then one element as the global state would apply to all the elements.
If you want to attach state to elements you can either do it by adding/removing classes or through data attributes (or whatever more specific attribute is applicable).
let showBtn = document.querySelector('.show-more');
showBtn.onclick = function (event) {
// event.target is the clicked element
if (event.target.matches('.expanded')) {
showBtn.textContent= 'Show Less';
else {
showBtn.textContent= 'Show more';
}
event.target.classList.toggle('expanded')
}
In this example the class 'expanded' is used to keep track of the state of the button. This also lets you attach different visual "states" through CSS.
In this case do not assign your state variable let show inside of the calling function. When you do that it will initialize the variable every time you call it. This causes a performance issue (see variable initialization and garbage collection) and depending on what you want to do it will break.
Someone here as already given an answer, but in their answer the variable declaration is in global scope. Most people agree that it's not a good idea to declare global variables, especially with such a general name as show . Later on, as your code base grows, you're likely to run into conflicts and your code will start acting in ways you can't predict. This is probably the most universally agreed upon coding convention. It's a bad habit. Learn how to do it the right way now.
These two StackOverflow answers contain examples that are a good starting point to producing working modular code to control the state of your objects:
wrapper function: https://stackoverflow.com/a/50137216/1977609
This is another way to implement your button: https://stackoverflow.com/a/10452789/1977609

Click to enlarge image, then click again to make it go back to previous size

I want to be able to click on an image, have it become big, and then when I click it again, make it go back to being small. I'm trying to use an if/else statement to solve this problem, but I still can't figure it out. This is the JS I have so far:
document.addEventListener("DOMContentLoaded", function(event) {
var thumbnailElement = document.getElementById("smart_thumbnail");
if (thumbnailElement.className === "small") {
thumbnailElement.addEventListener("click", function() {
thumbnailElement.className = "";
});
} else {
thumbnailElement.addEventListener("click", function() {
thumbnailElement.className = "small";
});
}
});
And the HTML for the image:
<img class="small" id="smart_thumbnail" src="http://4.bp.blogspot.com/-QFiV4z\
3gloQ/ULd1wyJb1oI/AAAAAAAAEIg/LE1Kakhve9Y/s1600/Hieroglyphs_Ani-papyrus.jpg">
I'm simply wanting to get rid of the "small" class on the id "smart_thumbnail" to make it big and put the "small" class back to make it small again, but I can only make it big. When I click on it the 2nd time, it doesn't do anything. I've tried an if/else if statement and that didn't work. I looked on here for the same question, but could only find stuff about jQuery. Trying to solve this with JavaScript only.
Any help greatly appreciated. Thanks!
The problem is that the code above will only be run once: After loading your site. So only one condition is fullfilled (thumbnailElement.className === "small")
What you want is something along the lines of:
var thumbnailElement = document.getElementById("smart_thumbnail");
thumbnailElement.addEventListener("click", function() {
if (this.className === "small")
this.className = "";
else
this.className = "small";
});
This will check the class when clicking the image.
Alternatively, you can also use classList.toggle
The DOMContentLoaded event only fires once, that is, when the page is loaded. Instead run your if-statement on a per-click basis.
For example
thumbnailElement.addEventListener("click", function() {
if (thumbnailElement.className === "small") {
thumbnailElement.className = "";
} else {
thumbnailElement.className = "small";
}
});
Now you will register the click event once, but every time it is clicked it will check the classname logic and apply the class name appropriately.

How to do car turn lights (blinkers) with jQuery?

I have some strange task to do. I have to implement car turning lights functionality into my web page. I have two buttons (left and right) and two green arrows (left and right). It should be that: I click left button (left arrow is now hidden) and left arrow should blink. When I click once more time, it should stop animation and be hidden. I just don't know to handle state of button and use it properly. Thanks.
$("#arrow-left").click(function blinker() {
if(something) {
$('#arrow-left-blink').css("visibility", "visible");
$('#arrow-left-blink').fadeOut(500);
$('#arrow-left-blink').fadeIn(500);
setInterval(blinker, 1000);
} else {
//hide
}
}
You should create a closure to save the state across different clicks. Simply create a closure by putting the click handler inside a self-invoking function and declare+initialize your shared variables inside it. Increase your count at the end of the click handler. You can toggle between true and false with the modulus operator '%'. 0%2==0, 1%2==1, 2%2==0, 3%2==1, 4%2==0, ...
(function(){
var counter = 0;
$("#arrow-left").click(function blinker() {
if(counter%2) {// modulus operator will toggle between 0 and 1 which corresponds to truthy or falsy
$('#arrow-left-blink').css("visibility", "visible");
$('#arrow-left-blink').fadeOut(500);
$('#arrow-left-blink').fadeIn(500);
setInterval(blinker, 1000);
} else {
//hide
}
counter++;
});
})();
You could define an outside variable counter.
For example:
$(document).ready(function() {
var counter = 0;
var blinking;
function blinker() {
$('#arrow-left-blink').fadeOut(500);
$('#arrow-left-blink').fadeIn(500);
blinker();
}
$("#arrow-left").click(function() {
counter++;
if (counter % 2 == 1) {
$('#arrow-left-blink').css("visibility", "visible");
blinking = setTimeout(function() {
blinker();
}, 1000);
} else {
clearInterval(blinking);
$('#arrow-left-blink').css("visibility", "hidden");
}
});
});
Here is a JSFiddle link: https://jsfiddle.net/o2xb8Lod/.
I would create a css class to handle the fading and blinking with css animations and just toggleClass on click in Jquery.

jQuery nested functions looping twice

In my limited understanding of jQuery, I don't understand what is happening here.
I have two divs, each with a class of "required". The 1st div contains a set of checkboxes, while the second contains radio buttons. The object is to highlight the appropriate div background if no objects in either set are selected.
The 2nd set, however (the radio buttons), does have a button pre-selected by default, so should never be highlighted.
Here is what is happening:
Both sets are being looped through twice. At the end of the 1st div (checkbox) loop, the div is correctly highlighted. The radio button items are them checked and the default checked button is recognized and that div is not highlighted. Then, the first div items are again gone through. At the end of this - and BEFORE the 2nd div is looped through for the second time, the 2nd div is highlighted. I am assuming that the cause here, even though it is correctly looping through the checkboxes, it is associating them with the id of the radiobuttons. What I need to do is prevent this second loop-through.
$(document).ready(function () {
$('#Submit').click(function () {
$('div.Required', '#JobForm').each(function () {
var FieldName = this.id;
var FieldLength = FieldName.length
var CheckError = true;
$("input:checkbox").each(function () {
if (this.checked) {
CheckError = false;
}
});
if (CheckError) {
ErrorList += FieldName + ' requires a selection\n';
this.style.backgroundColor = "#FFC0C0";
}
CheckError = true;
$("input:radio").each(function () {
if (this.checked) {
CheckError = false;
}
});
if (CheckError) {
ErrorList += FieldName + ' requires a selection\n';
this.style.backgroundColor = "#FFC0C0";
}
});
if (Error) {
$('#Submit').val('Submit');
alert(ErrorList)
return false;
} else {
document.JobForm.submit();
}
});
});
Thanks to adeneo's suggestions, I was able to limit the double div looping, which also allowed me to eliminate the additional error message. As adeneo stated, since there are two div's, as originally written, the code was looping through both input types each time.
As written below, the first loop loops through the checkboxes, while the second loops through the radio buttons.
$(document).ready(function()
{
$('#Submit').click(function ()
{
$('div.Required','#JobForm').each(function()
{
var FieldName = this.id;
var FieldLength = FieldName.length
var CheckError = true;
$("input:checkbox", this).each(function ()
{
if(this.checked){CheckError=false;}
});
$("input:radio", this).each(function ()
{
if(this.checked){CheckError=false;}
});
if(CheckError){ErrorList += FieldName+' requires a selection\n'; this.style.backgroundColor="#FFC0C0";}
});
if(Error)
{
$('#Submit').val('Submit');
alert(ErrorList)
return false;
}
else
{
document.JobForm.submit();
}
});
});
You're not iterating over each DIV twice, but you're iterating over all the inputs the same number of times as you have matching DIV's
$('#Submit').click(function () {
// loop over the DIV's, lets say two times if there are two elements
$('div.Required', '#JobForm').each(function () {
// ..... other code
// the code below selects all the checkboxes, both times
$("input:checkbox").each(function () {
if (this.checked) {
CheckError = false;
}
});
// the code below selects all the radiobuttons, both times
$("input:radio").each(function () {
if (this.checked) {
CheckError = false;
}
});
// .......more code
});
});
Limit the selection of inputs with context or find()
$("input:checkbox", this).each(function () { ...
or
$(this).find("input:checkbox").each(function () { ...
also, your conditions don't make sense, you keep setting CheckError to a boolean, and on the next line you're checking for that boolean, and you'll never get a different result
CheckError = true;
if (CheckError) { ...
is like doing
if (true) { ...

jQuery - hiding an element when on a certain page

I have this wizard step form that I simulated with <ul> list items by overlapping inactive <li> items with absolute positioning.
The wizard form is working as desired except that I want to hide next or previous button on a certain step.
This is my logic in jQuery but it doesn't do any good.
if (index === 0) {
$('#prev').addClass(invisible);
$('#prev').removeClass(visible);
} else if (index === 1) {
$('#prev').addClass(visible);
$('#prev').removeClass(invisible);
} else {
$('#next').addClass(invisible);
}
To get the index value I used eq() chained on a current step element like the following
var current;
var index = 0;
$(function () {
current = $('.pg-wrapper').find('.current');
$('#next').on('click', function() {
if (current.next().length===0) return;
current.next().addClass('current').show();
current.removeClass('current').hide();
navstep.next().addClass('active');
navstep.removeClass('active');
current = current.next();
navstep = navstep.next();
index = current.eq();
});
I tried to isolate it as much as possible but my full code will give you a better idea.
If you would care to assist please check my JS BIN
There were several issues
you used .eq instead of index
you were missing quotes around the class names
your navigation logic was flawed
no need to have two classes to change visibility
I believe the following is an improvement, but let me know if you have questions.
I added class="navBut" to the prev/next and rewrote the setting of the visibility
Live Demo
var current;
var navstep;
$(function () {
current = $('.pg-wrapper').find('.current');
navstep=$('.nav-step').find('.active');
$('.pg-wrapper div').not(current).hide();
setBut(current);
$('.navBut').on('click', function() {
var next = this.id=="next";
if (next) {
if (current.next().length===0) return;
current.next().addClass('current').show();
navstep.next().addClass('active');
}
else {
if (current.prev().length===0) return;
current.prev().addClass('current').show();
navstep.prev().addClass('active');
}
current.removeClass('current').hide();
navstep.removeClass('active');
current = (next)?current.next():current.prev();
navstep = (next)?navstep.next():navstep.prev();
setBut(current);
});
});
function setBut(current) {
var index=current.index();
var max = current.parent().children().length-1;
$('#prev').toggleClass("invisible",index<1);
$('#next').toggleClass("invisible",index>=max);
}
The eq function will not give you the index, for that you need to use the index() function.
I have not looked at the whole code but shouldn't your class assignemnts look like:
$('#prev').addClass('invisible');
$('#prev').removeClass('visible');
i.e. with quotes around the class names? And is it really necessary to have a class visible? Assigning and removing the class invisible should easily do the job (provided the right styles have been set for this class).
You should make 4 modifications.
1) Use .index() instead of .eq();
2) Add a function changeIndex which changes the class depends on the index and call it on click of prev and next.
3) add quotes to invisible and visible
4) There is a bug in your logic, try going to 3rd step and come back to 1st step. Both buttons will disappear. So you have to make next button visible if index = 0
Here is the demo :
http://jsfiddle.net/ChaitanyaMunipalle/9SzWB/
Use index() function instead of eq() because eq() will return object and index() will return the integer value.
DEMO HERE
var current;
var navstep;
var index = 0;
$(function () {
current = $('.pg-wrapper').find('.current');
navstep=$('.nav-step').find('.active');
$('.pg-wrapper div').not(current).hide();
}(jQuery));
$('#next').on('click', function() {
if (current.next().length===0) return;
current.next().addClass('current').show();
current.removeClass('current').hide();
navstep.next().addClass('active');
navstep.removeClass('active');
current = current.next();
navstep = navstep.next();
index = current.index();
change_step(index)
});
$('#prev').on('click', function() {
if (current.prev().length===0) return;
current.prev().addClass('current').show();
current.removeClass('current').hide();
navstep.prev().addClass('active');
navstep.removeClass('active');
current = current.prev();
navstep = navstep.prev();
index = current.index();
change_step(index)
});
function change_step(value)
{
if (value === 0) {
$('#prev').hide();
$('#next').show();
} else if (value === 1) {
$('#prev').show();
$('#next').show();
} else {
$('#next').hide();
$('#prev').show();
}
}

Categories

Resources