Increase value of counter by one upon class creation/append - javascript

Creating a system that increases the 'fault' counter by a value of one upon every wrong answer submitted. I'm doing this by having my system listen out for the creation of a class called "incorrectResponse". However, it appears to just add to the counter as soon as the page finishes loading and doesn't add any more values after that.
Here is the code I've tried that isn't working.
//*-- Fault counter --*//
if (document.querySelectorAll('incorrectResponse')) {
$('#fault-counter').html(function(i, val) { return val*1+1 });
};
Any reason as to why this is the case?

Assumming you're using jQuery (you mention it in one of your comments), one thing you could do is attach an event listener to the container of your 'wrong answers'.
A custom event would be triggered manually any time a wrong answer is received/appended to the page and the event listener would react to it recalculating the number of wrong answers and updating the counter.
function updateNumberOfIncorrectMsgs() {
$('.counter').text($('.incorrectAnswer').length);
}
updateNumberOfIncorrectMsgs();
var $container = $('.wrongAnswersContainer')
.on('newWrongAnswerAdded', function() {
updateNumberOfIncorrectMsgs();
});
// The for and setTimeout is only to simulate msgs appended to the page, the important part is the custom event that gets triggered when an element is added.
for (var i = 1; i < 6; i++) {
setTimeout(function() {
$container
.append('<p class="incorrectAnswer">Incorrect answer</p>')
.trigger('newWrongAnswerAdded');
}, 1000 * i);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Incorrect answer counter: <span class="counter"></span>
</p>
<div class="wrongAnswersContainer"></div>

Related

Make For Loop pause until click on div - JavaScript

I'm trying to write a code that displays several boxes one after the other, but only after the previous one has been closed. This can be done in two ways. Either the box closes automatically after 10 seconds, or it stays up indefinitely until it is closed by clicking "X".
I am trying to use a for loop to iterate over an array (mandatory) of these boxes, but I cannot work out how to 'pause' the loop to wait for user action. The loop must stop when all boxes have been displayed.
Does anyone know how this could be done (without jQuery)?
I've tried using setTimeout, but then realized it cannot be done this way. I'm new to programming, so it's all a bit confusing, if anyone could help I'd really appreciate it!
It may be worth mentioning that I'd prefer not to use id's.
I've tried to simplify my code to be easier to read:
HTML:
// Simplified - every element has this structure, only the class changes for the parent div
<div class=" box 'type' "> // type -> can be '"success" or "warning"
// BOX BODY
<div class="box-close" onClick="removeBox()"> X </div>
</div>
CSS
.box{display="none";}
JavaScript
// Simplified - each box div present in page is stored in array allBoxes
allBoxes = array of boxes
//Show boxes 1 by 1
for (j = 0; j < allBoxes.length; j++) {
showBox(allBoxes[j]);
}
function showBox() {
box=allBoxes[j];
box.style.display= "block";
if (box.classList.contains("success")==true){
setTimeout(removeBox, 10000); //PROBLEM: only executes after for loop is done, meaning it 'removes' the last div in the array being looped, regardless of type class
//alternative
setTimeout(removeBox(box), 10000); //Problem: executes remove immediately, without waiting the 10s
}
else{
//something to make the For Loop pause until the user clicks on X
box.querySelector(".box-close").addEventListener("click",removeBox); //doesn't do anything, loop continues
//alternative
box.querySelector(".box-close").addEventListener("click",removeBox(box)); //simply removes box immediately (not in response to click or anything), loop continues
}
}
function removeBox() {
box.style.display = "none";
}
My take on this is to actually use setTimeout(). We can assign an onClick next to the timeout that both will show the next box. If needed, the timeout can be canceled using clearTimeout()
So the next box will be shown after 3 seconds, or when the previous box is closed (clicked in my demo below)
To give an example, please see the demo below, were we have 3 main functions:
openBox; opens a box, starts the timeout, set click event to toggle box
closeBox; closes a box
openNext; Call closeBox for current box, clear any timeout's that are set and ofc call openBox to open the next one
Please see additional explanation in the code itself.
const nBoxes = 5; // Number of boxes
let index = 0; // Current box index
let count = null; // setTimeout pid
// Function to open boxes, assign onClick and start the count-down
const openBox = (n) => {
var e = document.getElementById('box_' + n);
e.style.display = 'block';
e.onclick = openNext;
count = setTimeout(openNext, 3000);
}
// Function to close a box
const closeBox = (n) => document.getElementById('box_' + n).style.display = 'none';
// Function to cycle to the next box
const openNext = () => {
// Close current open box
if (index > 0) {
closeBox(index);
}
// Stop any count-downs
if (count) {
clearTimeout(count);
count = null;
}
// Stop the loop if we've reached the last box
if (index >= nBoxes) {
console.log('Done!')
return;
}
// Bump index and open new box
index++;
openBox(index);
};
// Start; open first box
openNext()
.box {
display: none;
}
<div id='box_1' class='box'>1</div>
<div id='box_2' class='box'>2</div>
<div id='box_3' class='box'>3</div>
<div id='box_4' class='box'>4</div>
<div id='box_5' class='box'>5</div>
The only thing you need the loop for is to assign the click event listener to the close buttons. Inside the click event listener we hide the current box and then find the next box and show it, if it exists.
Note: In the following snippet, the .box-wrapper element is necessary to isolate all the boxes from any other siblings so that box.nextElementSibling will properly return null when there are no more boxes left to open.
const autoBoxAdvanceTime = 10000
const allBoxes = document.querySelectorAll('.box')
allBoxes.forEach(box => box.querySelector('.box-close').addEventListener('click', () => nextBox(box)))
//Show first box
showBox(allBoxes[0]);
function showBox(box) {
box.style.display = "block";
if (box.classList.contains("success")) {
console.log(`Going to next box in ${autoBoxAdvanceTime/1000} seconds`)
setTimeout(() => {
// only advance automaticaly if the box is still showing
if (box.style.display === "block")
nextBox(box)
}, autoBoxAdvanceTime);
}
}
function nextBox(box) {
box.style.display = "none"
const next = box.nextElementSibling
if (next) {
console.log('going to box:', next.textContent)
showBox(next)
} else {
console.log('last box closed')
}
}
.box,
.not-a-box {
display: none;
}
.box-close {
cursor: pointer;
}
<div class="box-wrapper">
<div class=" box type success">
one
<div class="box-close"> X </div>
</div>
<div class=" box type ">
two
<div class="box-close"> X </div>
</div>
<div class=" box type ">
three
<div class="box-close"> X </div>
</div>
</div>
<div class="not-a-box">I'm not thier brother, don't involve me in this!</div>
Instead of a real loop, use a function that re-invokes itself after a delay.
showBox should not take the box index as an argument
Instead, it should look at the allBoxes array directly. You might also need to keep track of which boxes have already been dealt with, which could be done either by maintaining a second list of boxes, or with a simple counter; either way, you'd define that variable outside the function so that it would retain its value across invocations of showBox.
showBox should call itself as its final step
Instead of relying on the loop to call showBox, have showBox do that itself. (This is known as "tail recursion" -- a function that calls itself at its own end.)
To add a 6-second delay, you'd wrap that invocation in a setTimeout.
nextBoxTimer = setTimeout(showBox, 6000)
integrate the tail-recursing loop with manual box closing
When a user manually closes a box, you want to stop the current timer and start a new one. Otherwise, a person could wait 5 seconds to close the first box, and then the second box would automatically close 1 second later.
So, make showBox begin by canceling the current timer. This will be pointless when showBox is running because it called itself, but it's crucial in cases when showBox is running because the user pre-empted the timer by closing the previous box herself.
For this to work, you'll need to define the variable outside of showBox (which is why the snippet in step 2 doesn't use let when assigning into nextBoxTimer).

jQuery remove class item keeps coming back

Here is the situation.
I'm trying to remove an div class item based when the user clicks on a rating.
The problem I have is that every time I click on the item it goes away, however when I move the mouse the item that I removed comes back.
Here is my current code:
<div class="star_'.($iPos+1).' ratings_stars ratings_vote" onmouseover="overRating(this);" onmouseout="outRating(this);" onClick="selectEmailRating(this);" ></div>
The above item is the div that is calling the JavaScript. When I click on the rating I run the code that is in the following function below:
function selectEmailRating(elem) {
var star = elem;
var rating = widget.data('fsr').rating;
if($(star).attr('class') === 'star_'+ rating + ' ratings_stars ratings_over ratings_vote'){
$(elem).andSelf().removeClass();
$(star).attr('class', 'star_'+ rating + ' ratings_stars');
$(star).attr('class').unbind('onmouseover').unbind('onmouseout');
}
function outRating(elem) {
$(elem).prevAll().andSelf().removeClass('ratings_over');
setRating($(elem).parent());
}
function overRating(elem) {
$(elem).prevAll().andSelf().addClass('ratings_over');
$(elem).nextAll().removeClass('ratings_vote');
}
function setRating(widget) {
var votes = $(widget).data('fsr').rating;
$(widget).find('.star_' + votes).prevAll().andSelf().addClass('ratings_vote');
$(widget).find('.star_' + votes).nextAll().removeClass('ratings_vote');
}
As you see in the code, it is removing the item, however, it is coming back when I move the mouse. Is there a way to make sure when I click on the item to remove it stays removed?
I may be wrong here, but you didn't "bind" the onmouse* events to the element, you added attributes.
You may overwrite the onmouse* attributes with $(elemment).attr('onmouseout', '') or something alike.
and you might want to have a look at https://api.jquery.com/hasclass/
Well I found a solution..
After trying to figure out why the rating keeps coming back, it was the due to the fact that the outRating function was causing the problem. Here is what I did...
The old outRating function:
function outRating(elem) {
$(elem).prevAll().andSelf().removeClass('ratings_over');
setRating($(elem).parent());
}
The new outRating function:
function outRating(elem) {
var star = elem;
var widget = $(elem).parent();
var rating = widget.data('fsr').rating;
$(elem).prevAll().andSelf().removeClass('ratings_over');
if($(star).attr('class') !== 'star_'+ rating + ' ratings_stars') {
setRating($(elem).parent());
}
}

JQuery - Button triggers multiple times

I have a checklist, where I get a list when I click on a Checkbox. I can check more than one Checkbox - so I get more lists. I add at then end of each list a button, through JQuery. My Code for the button looks like this:
//Add a button to the previous list:
function add() {
$(".list:last").append("<div id='button'><input type='button' class='click' id='feld' name='feld' value='+'/>Add new listfield</div>");
}
When I click on the button, I get a new Checkbox with empty Textfield:
$(document).ready(function() {
// Variables for counting
var utxtname = 0;
var ucheckname = 0;
var utxtid = 1;
var ucheckid = 1;
var uctxtname = 1;
var uccheckname= 1;
//Function for the buttons with the same class ".click"
$(document).on('click', '.click', function() {
var utxtname ='utxtfield'+uctxtname;
var ucheckname ='uchecklist'+uccheckname;
$(this).closest(".list").append("<br><input type='checkbox' name=' "+ucheckname+"' id='"+ucheckid+"' checked><input type='textfield' name ='"+utxtname+"' id='"+utxtid+"'/>");
uctxtname += 1;
uccheckname += 1;
utxtid += 1;
ucheckid += 1;
});
});
My Problem:
When I generate more than one Button, the function triggers multiple times. If I have 3 lists with 3 generated buttons, my function generates 3 buttons if I click on a button. I knew the reason for my mistake. It's because I have 3 buttons with the same class, so it triggers the function multiple times. I just can't figure out how I can solve this problem. I tried so many methods to prevent this. For example unbind the function and bind it again. I also tried to dynamically add button with unique functions, but I couldnt get this.
Any Ideas how can I solve this problem easier?
Use inner function is the simplest solution, but may lead to a memory leak. This is just a more choice.
function add() {
var $button = $("<div id='button'><input type='button' class='click' id='feld' name='feld' value='+'/>Add new listfield</div>");
$(".list:last").append($button);
$button.click(function(){
/*process here*/
});
}
Just for remind, the button may not release , since click function is bound. If you remove in your own code, please add $("button").unbind("click");
Just use Off() in jquery before the on() statement
You can use
e.PreventDefault();
statement to prevent multiple trigger.

onclick event won't fire when there is more than one dynamically added button

So I have EDIT and REMOVE buttons that are dynamically added for each data node (a "poll") in a Firebase database. I have a function which assigns onclick listeners to these with jQuery, but oddly, the event only fires when there just happens to be a single node, and hence a single pair of EDIT/REMOVE buttons. When there are multiple nodes and multiple pairs of buttons, none will fire. Here's the javascript where the events are added to the buttons...
function displayCurrentPollsForEditing(pollsRef)
{
var tbl = createTable();
var th = ('<th>Polls</th>');
$(th).attr('colspan', '3');
$(th).appendTo($(tbl).children('thead'));
pollsRef.once('value', function(pollsSnapshot) {
pollsSnapshot.forEach(function(pollsChild) {
var type = pollsChild.name();
// If this is true if means we have a poll node
if ($.trim(type) !== "NumPolls")
{
// Create variables
var pollRef = pollsRef.child(type);
var pollName = pollsChild.val().Name;
var btnEditPoll = $('<button>EDIT</button>');
var btnRemovePoll = $('<button>REMOVE</button>');
var tr = $('<tr></tr>');
var voterColumn = $('<td></td>');
var editColumn = $('<td></td>');
var rmvColumn = $('<td></td>');
// Append text and set attributes and listeners
$(voterColumn).text(pollName);
$(voterColumn).attr('width', '300px');
$(btnEditPoll).attr({
'class': 'formee-table-button',
'font-size': '1.0em'
});
$(btnRemovePoll).attr({
'class': 'formee-table-remove-button',
'font-size': '1.0em'
});
$(btnEditPoll).appendTo($(editColumn));
$(btnRemovePoll).appendTo($(rmvColumn));
// Append to row and row to table body
$(tr).append(voterColumn).append(editColumn).append(rmvColumn);
$(tr).appendTo($(tbl).children('tbody'));
// Append table to div to be displayed
$('div#divEditPoll fieldset#selectPoll div#appendPolls').empty();
$(tbl).appendTo('div#divEditPoll fieldset#selectPoll div#appendPolls');
$(btnEditPoll).click(function() {
displayPollEditOptions(pollRef);
return false;
});
$(btnRemovePoll).click(function() {
deletePoll($(this), pollsRef);
return false;
});
}
});
});
}
The markup would be something like the following...
<div id="divEditPoll">
<form class="formee" action="">
<fieldset id="selectPoll">
<legend>SELECT A POLL</legend>
<div class="formee-msg-success">
</div>
<div class="grid-12-12" id="appendPolls">
</div>
</fieldset>
</div>
EDIT - So I've switched some lines around and now I don't set the click() events until the buttons are appended to the document, so the button elements are definitely in the DOM when the click events are attached. So could this issue result from not setting id's for these buttons? That seems strange to me, since I'm using variable references rather than ids to attach the events.
There are two things I would check for.
First, make sure you don't have two elements with the same id. If you do, jquery may only bind to the first, or not bind at all.
Second, make sure the element is added to the dom before jquery attempts to bind the click event. If the code is running asynchronously, which can easily happen if you're using ajax, then you may be trying to bind the event before creating the element. Jquery would fail to find the element then give up silently.
you should use .on() for dynamically added button

How do I show or hide div, based on position of another div

I have the following jquery that slides a div horizontally:
$('.nextcol').click(function() {
$('.innerslide').animate({'left': '-=711px'}, 1000);
});
$('.prevcol').click(function() {
$('.innerslide').animate({'left': '+=711px'}, 1000);
});
What I want to happen is this... if the div.innerslide has a position that is left: 0px then I want to hide div.backarrow. If the position is not left: 0px, then it shows it.
Any help would be greatly appreciated.
EDIT (added HTML Markup)
<div class="backarrow prevcol">
<div id="mainleft" class="overflowhidden">
<div class="innerslide">
<div class="col">my content including next</div>
<div class="col">my content including next</div>
<div class="col">my content including next</div>
</div>
</div>
Try this:
if ($('.innerslide').css("left") == 0) {
$('div.backarrow').hide();
} else {
$('div.backarrow').show();
}
Fix for Double-Click Issue:
From what you described in your comment about the issue when the visitor double-clicks, it sounds like the double-click is causing two of the animation events to fire. To keep this from happening, you can either disable the click handler while the animation is running and re-enable it once it is finished, or you can try to write a new thread to continually check the element's position. One of these solutions is not a good idea - I'll let you figure out which one :) - but the other actually has a very simple solution that requires little change to your existing code (and may actually reduce your overhead by a teeny weeny amount):
$('.nextcol').on("click.next", function() {
$('.innerslide').animate({'left': '-=711px'}, 1000, showHideBack());
$(this).off("click.next");
});
$('.prevcol').on("click.prev", function() {
$('.innerslide').animate({'left': '+=711px'}, 1000, showHideForward());
$(this).off("click.prev");
});
Then add this this line to showHideBack() (and a complementary one to showHideForward() if you are using that):
$('.nextcol').on("click.next".....
I suggest that you write a function to set each click handler and another to remove each one. This will make your live very easy and the whole solution should reduce overhead by removing unnecessary click handlers while the animation is running.
Note: the animation method often calls its callback before the animation finishes. As such, you may wish to use a delay before calling the showHide... method(s).
Let me know if you have any questions. Good luck! :)
UPDATE:
Here is the updated version of the fiddle you gave me with all bugs ironed out. It looks like I misunderstood part of your goal in my original solution, but I straightened it out here. I have also included the updated jQuery, here:
var speed = 1000;
var back = $("div.backarrow");
var next = $(".nextcol");
var prev = $(".prevcol");
var inner = $(".innerslide");
function clickNext(index) {
next.off("click.next");
inner.animate({
'left': '-=711px'
}, speed, function() {
back.show(); //this line will only be hit if there is a previous column to show
next.delay(speed).on("click.next", function() {
clickNext();
});
});
}
function clickPrev() {
prev.off("click.prev");
inner.animate({
'left': '+=711px'
}, speed, function() {
if (inner.css("left") == "0px") {
back.delay(speed).hide();
prev.delay(speed).on("click.prev", function() {
clickPrev();
});
} else {
back.delay(speed).show();
prev.delay(speed).on("click.prev", function() {
clickPrev();
});
}
});
}
next.on("click.next", function() {
clickNext();
});
prev.on("click.prev", function() {
clickPrev();
});​
I was going to also include a condition to check if you were viewing the last column, but, as I don't know what your final implementation will be, I didn't know if it would be applicable. As always, let me know if you need help or clarification on any of this. :)
You could try the step option — a callback function that is fired at each step of the animation:
$('.prevcol').click(function() {
$('.innerslide').animate({ left: '+=711px' },
{
duration: 1000,
step: function(now, fx) {
if (now === 0 ) {
$('div.backarrow').hide();
} else {
$('div.backarrow').show();
}
}
});
});
More examples of usage in this article The jQuery animate() step callback function

Categories