I'm making a javascript quiz using a single HTML page. For some reason, my code will only display one question and upon clicking the element, it does not go to the next question. And it doesn't start the quiz with the first question.
I used a for loop inside a for loop. The first loop renders the question and then the other renders its corresponding choices. The questions and choices are held in an array containing the objects.
I've only been coding with Javascript and jQuery for a few weeks, so you'll have to tell me in beginner terms. I will have to refactor it. I apologize for it being somewhat messy.
I tried taking out the return commands. Same with preventDefault, no changes.
function renderQuiz(i) {
$heading.text("");
$mainDiv.text("");
$heading.text(quizQuestions[i].question);
for (var j = 0; j < quizQuestions[i].choices.length; j++) {
//console.log(quizQuestions[0].choices.length);
var li = document.createElement("li");
li.innerText = JSON.stringify(quizQuestions[i].choices[j]);
$mainDiv.append(li);
};
$('li').on("click", function() {
if (event.target.matches('li')) {
event.preventDefault();
var guess = event.target.innerText;
var answer = (JSON.stringify(quizQuestions[i].answer[0]));
if (guess === answer) {
timeLeft += 10;
console.log(timeLeft + "it works");
} else {
timeLeft -= 10;
console.log(timeLeft)
};
}
});
return;
};
mainPage();
$button.on("click", function(click) {
event.preventDefault();
for (var i = 0; i < quizQuestions.length; i++) {
renderQuiz(i);;
}
});
The $button click event is iterating through all of the questions, that is why the last question is the one displayed.
You need to define the variable i outside of the $button click function, then increment i on each click of the button.
var i = -1;
function renderQuiz(i) {
...
};
mainPage();
$button.on("click", function(click) {
event.preventDefault();
renderQuiz(++i);
});
There is an error in the $button onclick handler (in your console you should have "Uncaught ReferenceError: event is not defined").
If the function takes click as parameter then you should apply the method preventDefault() to that same parameter. In other words instead of
$button.on("click", function(click) {
event.preventDefault();
you should have
$button.on("click", function(event) {
event.preventDefault();
By the way there is the same error here
$('li').on("click", function() {
you should have
$('li').on("click", function(event) {
I hope this help.
Related
I am trying to convert a small script from javascript to jquery, but I don't know where I should be putting the [i] in jquery?. I am nearly there, I just need someone to point out where I have gone wrong.
This script expands a search input when focused, if the input contains any values, it retains it's expanded state, or else if the entry is removed and clicks elsewhere, it will snap back.
Here is the javascript:
const searchInput = document.querySelectorAll('.search');
for (i = 0; i < searchInput.length; ++i) {
searchInput[i].addEventListener("change", function() {
if(this.value == '') {
this.classList.remove('not-empty')
} else {
this.classList.add('not-empty')
}
});
}
and converting to jquery:
var $searchInput = $(".search");
for (i = 0; i < $searchInput.length; ++i) {
$searchInput.on("change", function () {
if ($(this).value == "") {
$(this).removeClass("not-empty");
} else {
$(this).addClass("not-empty");
}
});
}
Note the key benefit of jQuery that it works on collections of elements: methods such as .on automatically loop over the collection, so you don't need any more than this:
$('.search').on("change", function() {
this.classList.toggle('not-empty', this.value != "");
});
This adds a change event listener for each of the .search elements. I've used classList.toggle as it accepts a second argument telling it whether to add or remove the class, so the if statement isn't needed either.
I am trying to code a simple quiz app. I am trying to put a hidden screen at the end when one clicks on a button 3 times at the end. This is what I have tried:
for (var i = 0; i > 2; i++) {
onEvent("button26", "click", function() {
setScreen("TrollScreen");
playSound("sound://default.mp3", false);
});
}
I am fairly new to code, and I'm not sure how to do this. Help is appreciated.
You need to keep the count of the clicks outside of the event handler. Then inside it you can check that value and show the screen or increase the counter accordingly.
var count = 0;
onEvent("button26", "click", function(){
if(count > 2){
setScreen("TrollScreen");
playSound("sound://default.mp3", false);
}else{
count++;
}
});
Since all DOM elements are actually objects, you can attach a property to them that will serve as a counter, thus when a button gets clicked, you increment that property by 1 and then check if it reached 3 already.
A more subtle approach is to use a helper function that attaches the event and set up the counter as a closured variable, here is how:
function attachEventWithCounter(elem, func, maxClickCount) {
let count = 0;
elem.addEventListener("click", function(e) {
count++;
if(count >= maxClickCount) {
func.call(this, e);
// and probably reset 'count' to 0
}
});
}
You can then use it like so:
attachEventWithCounter(myButton, myEventListener, 3);
attachEventWithCounter just takes a DOM element, a function that will serve as the event listener and a number that will be the maximum amount of tries. It then attaches a click event listener (you could pass in the type of the event as well if you want) and then whenever that event happens, it increments a locally declared variable count (initially set to 0) and checks if it reached the maximum amount of tries, if so it just calls the function passed as parameter (using Function#call to pass a custom this and the event argument to mimic the actual event listener).
Example:
function attachEventWithCounter(elem, func, maxClickCount) {
let count = 0;
elem.addEventListener("click", function(e) {
count++;
if(count >= maxClickCount) {
func.call(this, e);
count = 0;
}
});
}
let btn = document.getElementById("myButton");
function listener() {
alert("Clicked at last!!!");
}
attachEventWithCounter(btn, listener, 3);
<button id="myButton">Click me 3 times</button>
this will click the button three times every time you press it (at least I think). instead, make a counter variable that starts at 0 and increment it up by 1 each time the button is pressed. the put the action you want to perform inside in an if statement ie
if(counter >= 3){
//do some action
}
hope that helps!
you want to keep a counter variable outside the scope of the event to keep track of how many times it was clicked. Ex.
let counter = 0;
onEvent("button26", "click", function() {
if(counter >= 3) {
setScreen("TrollScreen");
playSound("sound://default.mp3", false);
}
counter ++;
});
//create a variable to check how many times the button has been clicked
var buttonClick = 0;
function CheckCount() {
//Check if the buttonClick variable is three
if (buttonClick == 3) {
//if it is equal to three, display the screen and play the sound
//below commented out for sake of demo
//setScreen("TrollScreen");
//playSound("sound://default.mp3", false);
document.getElementById('buttonclickcount').innerHTML = "You clicked it three times";
} else {
//if it is not so, then increment the buttonClick variable by 1
buttonClick++
//so you can see how many times the button has been clicked
document.getElementById('buttonclickcount').innerHTML = buttonClick;
}
};
<!--I would create an onclick event on the button itself that runs a function when -->
<button onclick="CheckCount()">You Win!</button>
<div id="buttonclickcount"></div>
You should look at using closures. This is where you define a variable before returning a function; your returned function closes around this variable. You could do something like in this fiddle.
const button = document.getElementById('button');
const output = document.getElementById('output');
const click = (function() {
let count = 0;
return function() {
count++;
if(count > 3) {
output.innerHTML = 'Count is greater than 3: ' + count
}
}
})();
button.addEventListener('click', click);
I am having some trouble understanding what is happening in a piece of vanilla JS for the Isotope filter. The original code is here: https://codepen.io/desandro/pen/VWLJEb
var buttonGroups = document.querySelectorAll('.button-group');
for (var i = 0; i < buttonGroups.length; i++) {
var buttonGroup = buttonGroups[i];
var onButtonGroupClick = getOnButtonGroupClick(buttonGroup);
buttonGroup.addEventListener('click', onButtonGroupClick);
}
function getOnButtonGroupClick(buttonGroup) {
return function(event) {
// check for only button clicks
var isButton = event.target.classList.contains('button');
if (!isButton) {
return;
}
var checkedButton = buttonGroup.querySelector('.is-checked');
checkedButton.classList.remove('is-checked')
event.target.classList.add('is-checked');
}
}
What is happening between the getOnButtonGroupClick function and it being assigned to a variable in the for loop preceding it?
getButtonGroupClick returns a closure that saves the value of buttonGroup. When you click on a button in the button group, it uses that closure variable to search for the checked button in the group, uncheck it, and then check the button you clicked on.
This complexity isn't really needed. When an event listener is called, event.currentTarget is set to the element that the listener was attached to, so you could just use that.
var buttonGroups = document.querySelectorAll('.button-group');
for (var i = 0; i < buttonGroups.length; i++) {
var buttonGroup = buttonGroups[i];
buttonGroup.addEventListener('click', onButtonGroupClick);
}
function OnButtonGroupClick(event) {
// check for only button clicks
var isButton = event.target.classList.contains('button');
if (!isButton) {
return;
}
var checkedButton = event.currentTarget.querySelector('.is-checked');
checkedButton.classList.remove('is-checked')
event.target.classList.add('is-checked');
}
The for loop is used to iterate over all of the elements with the class of button-group and and a click event listener to them. getOnButtonGroupClick returns a function to be used as the function to be used as the event listener for the element i.e. the function that is run when the element is clicked on.
var buttonGroups = document.querySelectorAll('.button-group');
//get all elements within the document with a class of button-group
//buttonGroups is a NodeList
for (var i = 0; i < buttonGroups.length; i++) {
//loop through all of the elements with a class of button-group matched by the above query selector
var buttonGroup = buttonGroups[i];
//get the element in the NodeList with the index i
var onButtonGroupClick = getOnButtonGroupClick(buttonGroup);
//get the function to be run when the element is clicked on
buttonGroup.addEventListener('click', onButtonGroupClick);
//add a click event listener to the element
}
function getOnButtonGroupClick(buttonGroup) {
return function(event) {
// check for only button clicks
var isButton = event.target.classList.contains('button');
//check if the element has a class of button
if (!isButton) {
//if the element does not have a class of button, do nothing
return;
}
var checkedButton = buttonGroup.querySelector('.is-checked');
checkedButton.classList.remove('is-checked')
event.target.classList.add('is-checked');
}
}
If I understood your question correctly, It means that a click event is being added to every button in the buttonGroups there is. Although, if you ask me, it would be way better and cleaner to just use a forEach, like so:
const buttonGroups = document.querySelectorAll('.button-group');
buttonGroups.forEach(button => button.addEventListener("click", OnButtonGroupClick)
function OnButtonGroupClick(event) {
// check for only button clicks
let isButton = event.target.classList.contains('button');
if (!isButton) {
return;
}
let checkedButton = event.currentTarget.querySelector('.is-checked');
checkedButton.classList.remove('is-checked')
event.target.classList.add('is-checked');
}
So, you add a click event to ALL the buttons in the buttonGroups that will run the function onButtonGroupClick.
EDIT: And there's no really need to assign the function like that... at all. Just call it on the click event and that's it.
I'm using the HTML5 tag details for a FAQ section of a company. An issue was that if the user opened another question the other question would not close automatically. Therefore I searched on the web and found the following solution:
function thisindex(elm){
var nodes = elm.parentNode.childNodes, node;
var i = 0, count = i;
while( (node=nodes.item(i++)) && node!=elm )
if( node.nodeType==1 ) count++;
return count;
}
function closeAll(index){
var len = document.getElementsByTagName("details").length;
for(var i=0; i<len; i++){
if(i != index){
document.getElementsByTagName("details")[i].removeAttribute("open");
}
}
}
This code does work properly in some sense but it has some small issues. Sometimes it opens two questions at the same time and works funny. Is there a method so this can work properly? This should work on desktop, tablet and mobile.
NOT DESIRED EFFECT:
I created a fiddle http://jsfiddle.net/877tm/ with all the code. The javascript is doing it's work there, ig you want to see it live click here.
Since you tagged jQuery, you can just do this:
$('.info').on('click', 'details', function () {
$('details').removeAttr('open');
$(this).attr('open', '');
});
All this does is remove the open attribute of all detail tags when you click on any detail, and then reopen the one you just clicked on.
http://jsfiddle.net/877tm/3/
the hole thisindex function is stupid and can be removed. You can simply pass the details element to closeAll.
The closeAll is quite stupid, too it searches for details in the for loop, wow.
// closeAll
function closeAll (openDetails){
var details = document.getElementsByTagName("details");
var len = details.length;
for(var i=0; i<len; i++){
if(details[i] != openDetails){
details[i].removeAttribute("open");
}
}
}
In case you want write clean code.
You should use $.on or addEventlistener.
Try to be in a specific context and only manipulate details in this context. (What happens, if you want to have two accordion areas. Or some normal details on the same site, but not inside of the group.)
Only search for details in the group, if details was opened not closed.
Give the boolen open property some love, instead of using the content attribute
I made small fiddle, which trys to do this.
To make details as accordion tag you can use below jquery.
$("#edit-container details summary").click(function(e) {
var clicked = $(this).attr('aria-controls');
closeAll(clicked);
});
function closeAll (openDetailid){
$("#edit-container details" ).each(function( index ) {
var detailid = $(this).attr('id');
var detailobj = document.getElementById(detailid);
if (openDetailid != detailid ) {
detailobj.open = false;
}
});
$('html, body').stop().animate({ scrollTop: $('#'+openDetailid).offset().top -100 }, 1000);
}
I have a solution with jQuery
$('details').on('click', function(ev){ //on a '<details>' block click
ev.preventDefault(); //prevent the default behavior
var attr = $(this).attr('open');
if (typeof attr !== typeof undefined && attr !== false){ //if '<details>' block is open then close it
$(this).removeAttr('open');
}else{ // if '<details>' block is closed then open the one that you clicked and close all others
var $that = $(this); //save the clicked '<details>' block
$(this).attr('open','open'); //open the '<details>' block
$('details').each(function(){ //loop through all '<details>' blocks
if ($that.is($(this))){ //check if this is the one that you clicked on, if it is than open it or else close it
$(this).attr('open','open');
}else{
$(this).removeAttr("open");
}
});
}
});
I am developing an app that stays in the website itself, and I want every link to call a function. I have tried this:
HTML
link<br>
link 2
Javascript
var a = document.getElementsByTagName("a");
for (var i = 0; i < a.length; i++) {
a[i].onclick = function () {
return false
}
}
What is wrong? It doesn't work.
Since it's not jQuery, you should use the preventDefault function.
var a = document.getElementsByTagName("a");
for (var i = 0; i < a.length; i++) {
a[i].onclick = function (e) {
e.preventDefault();
doSomething();
}
}
edit for pure javascript solution
document.addEventListener("click", function(e){
if (e.nodeName==="A"){
e.preventDefault();
return false;
}
}, false);
This will only add one single event to the document and prevent all clicks on anchor elements only.
I removed the old solution because of the comment, that this wasn't a jquery question
Don't use return false, it does more than you really need. Instead try event.preventDefault()
var a = document.getElementsByTagName("a").forEach(function (e) {
e.onclick = function (a) {
doSomething(a);
return false;
}
}
}