The variable is not incrementing properly - javascript

I have a webpage that examines users with some questions.
When a person answers one question, it hides and another one comes up.
I came up with a code that uses incrementation but it doesnt work.
I also wrote exactly the same thing just using numbers instead of incrementing variable and it works perfect. What is the difference?
This code works:
$(".questionBox" + 0 + " .answerButton").click(function() {
$(".questionBox" + 0).hide();
$(".questionBox" + 1).show();
$(".answerButton").hide();
});
$(".questionBox" + 1 + " .answerButton").click(function() {
$(".questionBox" + 1).hide();
$(".questionBox" + 2).show();
$(".answerButton").hide();
});
$(".questionBox" + 2 + " .answerButton").click(function() {
$(".questionBox" + 2).hide();
$(".questionBox" + 3).show();
$(".answerButton").hide();
});
But this one NOT:
let i = 0;
$(".questionBox" + i + " .answerButton").click(function() {
$(".questionBox" + i).hide();
i++;
$(".questionBox" + i).show();
$(".answerButton").hide();
});

You do not need to keep track of an index variable. Just declare some indices at data attributes. You can advance to the next question by getting the parent .question-box of the .answer-button and call .next() to go to the next sibling and show it.
$('.question-box').not(':first-child').hide();
$('.answer-button').on('click', function() {
const $parent = $(this.closest('.question-box'));
const index = parseInt($parent.data('index'), 10);
const $next = $parent.hide().next();
if ($next.length !== 0) {
$next.show();
} else {
console.log('Done!');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="questions">
<div class="question-box" data-index="0">
<h2>Question 1</h2>
<button class="answer-button">Answer</button>
</div>
<div class="question-box" data-index="1">
<h2>Question 2</h2>
<button class="answer-button">Answer</button>
</div>
<div class="question-box" data-index="2">
<h2>Question 3</h2>
<button class="answer-button">Answer</button>
</div>
</div>

There are two ways to solve your issue using the incrementing variable, but it's definitely worth thinking in #MrPolywhirls answer above. I'm just adding these to show the issue you are seeing with your compacted code. The first example is to call a method to instantiate the first on, and then as questions get answered you want to instantiate the next ones. That would look something like this:
let i = 0;
function applyToQuestionBox(){
$(".questionBox" + i + " .answerButton").click(function() {
$(".questionBox" + i).hide();
i++;
$(".questionBox" + i).show();
$(".answerButton").hide();
// The answer is given, so initialise the next box (i+1)
applyToQuestionBox();
});
}
// On load, this will be applied to question 0 (or initial 'i')
applyToQuestionBox();
It's a little bit annoying that you call the method again inside there, it means you have to read the flow of your code in chunks and makes it harder to reason about. The second option, though, is having a for loop to simply apply it all on page load, and never worry about it again:
const amountOfQuestions = 10;
for( const i = 0; i < amountOfQuestions; i++ ){
$(".questionBox" + i + " .answerButton").click(function() {
$(".questionBox" + i).hide();
$(".questionBox" + (i + 1)).show();
$(".answerButton").hide();
});
}
This reads rather clearly what's going on there: ten questions, every one of them has a click, and every click has an effect. You can reason about this code.
Hope this clears up the issue you might have been having.
In purer JS (sorry I am more accustomed to it but would advise you to look into that instead of picking up jQuery - pure JS is much simpler now, and jQuery's usefulness is waning every year now). The setup here is to avoid having to predetermine the amount of questions. What if you add one to the HTML, do you want to have to go back to your javascript just to add a question? No, you want to automate this:
// Let's select all question blocks
// It doesn't care if there's two or eight hundred
// It is important to store this in a variable though,
// as we will need to reference this list later to get the
// element after the current one thats displayed.
const questions = document.querySelectorAll( '.question' );
questions.forEach((question, index) => {
// Attach an event listener to the internal button
question.querySelector( 'button' ).addEventListener( 'click', event => {
// Use CSS to hide and display certain elements
question.classList.remove( 'active' );
// Check if the next index exists, if not
// we ran out of elements and have reached the end
if( questions[ index + 1 ] ){
questions[ index + 1 ].classList.add( 'active' );
} else {
alert( 'You completed all questions!' );
}
});
});
.question { display: none; }
.question.active { display: block; }
<section class="question active">
<p>Question 1</p>
<button>Answer</button>
</section>
<section class="question">
<p>Question 2</p>
<button>Answer</button>
</section>
<section class="question">
<p>Question 3</p>
<button>Answer</button>
</section>

I got it solved! The problem was that the selector in the function call is kind of static, it did not increment after the first time in the below code.
let i = 0;
$(".questionBox" + i + " .answerButton").click(function() {
$(".questionBox" + i).hide();
i++;
$(".questionBox" + i).show();
$(".answerButton").hide();
});
It works when i only increment inside of the function:
$(".answerButton").click(function() {
$(".questionBox"+i).hide();
i++;
$(".questionBox"+i).show();
$(".answerButton").hide();
});

Related

Adding and using html with jquery works only sometimes

I wrote some code that generates a bunch of html-elements based on a Json-Object. I add it to the page by JQuerys .append() and .after().
It does work perfectly often, but sometimes the outer loop is only executed once and stops at $( '#'+inputname ).entityselector().
function addlinks(qid, prop) {
html="<fieldset id=\"quickpresets\">" +
"<legend>Quick Presets (" + prop.name + ")</legend></fieldset>";
$('.wikibase-statementgrouplistview').first().after( html );
for( var p = 0; p < prop.defaults.length; p++ ) {
pid=prop.defaults[p].pid;
pname=prop.defaults[p].name;
pvalues=prop.defaults[p].values;
inputname="input"+pname;
pclass="addstatement";
if($('#P'+pid).find(".wikibase-snakview-value a").length !== 0) {
pclass += " disabled";
}
str="<p class='"+pclass+"'>Add "+pname+":";
for( var i = 0; i < pvalues.length; i++) {
toqid=pvalues[i].qid;
toname=pvalues[i].name;
str += " <a href='javascript:void(0);' onclick=\""+
"additemstatement("+qid+","+pid+",'"+pname+"',"+ toqid +",'" + toname+ "')\">" + toname+ "</a>"+
" ∙";
}
str += "<span class=\"quickpresetsinput\"><input id='"+inputname+"'/> ";
str += "<a href=\'javascript:void(0);\' onclick=\""+
"onselectitem("+qid+","+pid+",'"+pname+"','"+ inputname +"')\">✔</a>";
str += "</span></p>";
$('#quickpresets').append( str );
input = $( '#'+inputname ).entityselector( {
url: 'https://www.wikidata.org/w/api.php',
language: mw.config.get('wgUserLanguage')
} );
}
}
How do I fix this issue? And what other things are there that I should do to improve this ugly code?
Updates:
I get the following error in the console:
TypeError: $(...).entityselector is not a function [Weitere
Informationen]
The full code can be found here.
I have to use ES5.
The data is always the same ("hard coded") JSON.
See below for the better readable version of Roamer-1888 – which still causes the same bug.
It's not apparent in the code why .entityselector() should throw on some occasions. The most likely reason is that you are trying to invoke the plugin before it is loaded.
In an attempt to fix the issue, you might try initialising all the inputs in one hit instead of individually in the loop.
Also, the code would be made more readable by attaching a fairly simple fragment to the DOM, then immediately adding links and attaching event handlers with jQuery.
Here it is the way I would write it (with a bunch of tidying up) :
function addlinks(qid, prop) {
// compose and intert fieldset.
var $fieldset = $("<fieldset><legend>Quick Presets (" + prop.name + ")</legend></fieldset>")
.insertAfter($('.wikibase-statementgrouplistview').first());
// loop through prop.defaults
prop.defaults.forEach(function(dflt) {
// compose and append a basic fragment ...
var $fragment = $("<p class='addstatement'>Add " + dflt.pname + ":<span class=\"links\"></span>"
+ "<span class=\"quickpresetsinput\"><input /> ✔</span></p>")
.appendTo($fieldset);
// ... then allow jQuery to augment the appended fragment :
// i) conditionally addClass('disabled')
if($('#P' + dflt.pid).find(".wikibase-snakview-value a").length !== 0) {
$fragment.addClass('disabled');
}
// ii) loop through dflt.values and add links.
dflt.values.forEach(function(val) {
$("<span> ∙</span>")
.appendTo($fragment.find('span.links'))
.find('a')
.text(val.name)
.on('click', function(e) {
e.preventDefault();
additemstatement(qid, dflt.pid, dflt.pname, val.qid, val.name);
});
});
// iii) attach click handlers to the quickpresets inputs
$fragment.find('.quickpresetsinput').find('a').on('click', function(e) {
e.preventDefault();
var selection = $(this).prev('input').data('entityselector').selectedEntity();
additemstatement(qid, dflt.pid, dflt.pname, selection.id.substring(1), selection.label);
});
});
// invoke .entityselector() on all the quickpresets inputs in one hit
$('.quickpresetsinput input').entityselector({
'url': 'https://www.wikidata.org/w/api.php',
'language': mw.config.get('wgUserLanguage')
});
}
untested except for syntax
That's certainly tidier though without a proper understanding of the original issue, .entityselector() may still throw.

javascript add unnecessary white space to start of toggled class / end of original class

I'm trying to develop a very simple, add / toggle class method, which is similar to jQuery's addClass(), and toggleClass() methods.
However, I have come across a minor issue where, the method will work great initially, but after the first add / toggle, there will be added white space in front of the new class (or behind the original class).
For example:
<!-- initial toggle / add (this is what I wanted everytime) !-->
<div class="firstclass toggledclass"></div>
<!-- after initial toggle, THEN the new class is removed (this is what I'm left with) !-->
<div class="firstclass "></div>
<!-- after initial toggle / add (when I add a new class back in again) !-->
<div class="firstclass toggledclass"></div>
<!-- and so on... !-->
This obviously isn't how it is supposed to function, but I cannot figure out a way to fix it without breaking my script.
Here is my (relevant) JavaScript:
// add class
for(var i = 0; i < this.element.length; i++){
if((" " + this.element[i].className + " ").indexOf(" " + classes + " ") < 0){
this.element[i].className += " " + classes; // i feel the problem retains here
}
}
// toggle class
for(var i = 0; i < this.element.length; i++){
if(this.element[i].classList.contains(classes)){
var regex = new RegExp("(^| )" + classes + "($| )", "g");
for(var i = 0; i < this.element.length; i++){
this.element[i].className = this.element[i].className.replace(regex, " ");
}
} else {
if((" " + this.element[i].className + " ").indexOf(" " + classes + " ") < 0){
this.element[i].className += " " + classes; // and here
}
}
}
I'm still pretty new to JavaScript, so most of this has been made from altered scripts I have found (I wanted to stray away from classList new methods .add(), .toggle() e.t.c...) from forums.
If anyone could help me figure out how to solve this problem / improve this block of code, it would be greatly appreciated,
Maybe instead working with whitespaces directly try to get all classes into array and work on array. It would work
var classStr = el.getAttribute('class');
var classes = classStr.split(" ");
And after pushing class to array do the join method.
so if you for example want to add new class to the array I would firstly look if class isn't there already with
if(classes.indexOf("class") == -1) {
classes.push("class");
}

Multiple buttons with the same ID [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 8 years ago.
Improve this question
SOLVED: Not enough coffee - the event listener was being added before the buttons were created.
Im creating a table from HTML LocalStorage like so:
for (i = 1; i <= localStorage['count']; i++) {
tableHtml = tableHtml + "<tr><td><button class='delete' id=" + i + ">Delete</button></td><td>" + localStorage['fullName' + i] + "</td><td>" + localStorage['email' + i] + "</td><td>" + localStorage['phoneNum' + i] + "</td></tr>";
}
This works great, however the delete button is proving troublesome.
I saw this:
jQuery find ID of clicked button by class
So I assumed i could do this:
$(".delete").click(function () {
alert('delete clicked for id: ' + $(this).id);
});
However, it doesn't work. Suggestions?
you can get the id of element by using javascript without using jquery wrapper like this:
$(".delete").click(function () {
alert('delete clicked for id: ' + this.id); // get id via javascript
});
or:
$(".delete").click(function () {
alert('delete clicked for id: ' + $(this)[0].id); // via javascript object
});
If you see the link reffered in question it is also getting id using javascript (this.id)
if you want to get via jquery then:
$(".delete").click(function () {
alert('delete clicked for id: ' + $(this).attr("id")); // via jquery object
});
When are you binding the click event? If it's before the element exists, you'll want to use .on.
$('body').on('click','.delete', function(){
alert('delete clicked for id: ' + $(this).attr('id'));
});
Check out a working jsFiddle.
use pure javascript:
this.id
or jquery
$(this).attr('id');
as $(this) is jquery object. and you are trying to get javascript property id. You need to first convert the object to javascript.
$(this)[0].id;
DOM elements shouldn't have numbers as the first character of id.
Change your code to something like this:
for (i = 1; i <= localStorage['count']; i++) {
tableHtml = tableHtml + "<tr><td><button class='delete' id=del-id" + i + ">Delete</button> </td><td>" + localStorage['fullName' + i] + "</td><td>" + localStorage['email' + i] + "</td><td>" + localStorage['phoneNum' + i] + "</td></tr>";
}
And
$(".delete").click(function () {
alert('delete clicked for id: ' + $(this).prop('id');
});
This should take you on the right track.
REMEMBER that .prop() function is jQuery 1.10+ specific.
If you are using an older version use .attr()
As some people here mentioned - id's can be numeric as of HTML5
still using them in a numeric form might generate rather unpleasant problems.
Link in the comments below from one of the users.

How to set the dynamic ids for link? [duplicate]

This question already has answers here:
How to increment div id value?
(3 answers)
Closed 9 years ago.
I have a horizontal menu. I want to create an id for each using jQuery. My code is:
<div class="menuBar">
<div class="menuHeader ui-corner-top">
<span>Home</span>
</div>
<div class="menuHeader ui-corner-top">
<span>New Transaction</span>
</div>
</div>
Here menu has no id. I want to create id dynamically using jQuery. How can I do this? I did an example like this, but it is not working:
var i = 0;
$('.menuHeader').each(function () {
i++;
newID = menu + i;
$(this).attr('id', newID);
$(this).val(i);
});
The .each() method is designed to make DOM looping constructs concise and less error-prone. When called it iterates over the DOM elements that are part of the jQuery object. Each time the callback runs, it is passed the current loop iteration, beginning from 0.
You have also omitted the quotes when you concatenated menu string with i variable.
$('.menuHeader').each(function (i) {
$(this).find('a').attr('id', 'menu' + (i + 1));
$(this).html((i + 1) + '. ' + $(this).html());
});
jsFiddle Demo
Try this, no need of extra variable i:
$('.menuHeader').each(function () {
$(this).attr('id', 'menu' + ($(this).index() + 1));
$(this).val($(this).index() + 1);
});
Try this
$('.menuHeader').each(function (i) {
newID = "menu_" + i;
$(this).attr('id', newID);
$(this).val(i);
});

jQuery not removing absolute positioned divs?

I have the following code which will hide the parent div of the link you click on. When the div's are relatively positioned, there is no problem, but when they are absolutely positioned, they won't remove.
The script also saves the state of each element to localStorage, and the error only happens the first time you try to close one of the divs. What I mean by this is that, if you show three divs, say div one, two, and three, and then try to close the top most div, it won't close. If you reload the page, and try to close div three, which is on top from before you reloaded the page, it will close.
There's a little bit too much code to post, so here's the jsFiddle for it. But here's the code for posterity:
function loadWidgets() {
$(".base").each(function () {
var id = $(this).attr("id");
var counter = localStorage.getItem("counter-" + id) || 0;
var active = localStorage.getItem(id + "-active") || "";
$.each(active.split(" "), function (k, v) {
var s = v.split(",");
if (s.length != 2) {
return;
}
var newElement = $("#" + s[0]).clone(true, true);
newElement.attr("id", s[1]).attr("class", "drag " + id).data("id", id).appendTo("body");
});
});
}
function closeWidget() {
var id = $(this).parent().attr("id").match(/[a-zA-Z]+/g);
$(this).parent().remove();
var active = [];
$($("." + id).not(".base")).each(function () {
active.push(id + "," + $(this).attr("id"));
});
active = active.join(" ");
localStorage.setItem(id + "-active", active);
}
function cloneWidget() {
var id = $(this).attr("href").match(/[a-zA-Z]+/g);
var key = "counter-" + id;
var counter = localStorage.getItem(key) || 0;
counter++;
var newElement = $("#" + id).clone(true, true);
newElement.attr("id", id + counter).attr("class", "drag " + id).appendTo("body");
var active = [];
$($("." + id).not(".base")).each(function () {
active.push(id + ',' + $(this).attr("id"));
});
active = active.join(" ");
localStorage.setItem(id + "-active", active);
localStorage.setItem(key, counter);
}
loadWidgets();
$(".nav a").click(cloneWidget);
$(".close").click(closeWidget);​
And the HTML:
<div class="base" id="one" style="background-color:blue">
<a class="close" href="#">Close</a>
<input class="input" id="test"/>
<textarea class="textarea" id="test2"></textarea>
</div>
<div class="base" id="two" style="background-color:red">
<a class="close" href="#">Close</a>
</div>
<div class="base" id="three" style="background-color:green">
<a class="close" href="#">Close</a>
</div>
<div class="nav">
One
Two
three
</div>​
jQuery's clone doesn't copy events by default:
.clone( [withDataAndEvents] [, deepWithDataAndEvents] )
[...]
Normally, any event handlers bound to the original element are not copied to the clone. The optional withDataAndEvents parameter allows us to change this behavior, and to instead make copies of all of the event handlers as well, bound to the new copy of the element.
And since you have events bound to things inside what you're cloning, you want deepWithDataAndEvents to be true as well:
var newElement = $("#" + s[0]).clone(true, true);
Corrected fiddle: http://jsfiddle.net/ambiguous/Jdutt/
You create a new widget but you do not assign an event handler to the Close link.
Take the next line and put it at the end of the cloneWidget function:
$("#" + id + counter + " a.close").click(closeWidget);

Categories