I have a problem with a loop inside a loop.
By selecting the number and clicking the "Generate boxes" button it generates boxes with numbers from 1 to 49.
If you click the first time everything works fine.
But if you add more boxes it once again adds those 49 numbers to the already existing boxes. That's the problem. I only want to generate new boxes with numbers from 1 to 49.
This is the code:
function kasterl() {
$(".plunder").click(function() {
var wert = $( "#wieviel option:selected").text();
MyInt = parseInt(wert);
createKaesten(MyInt);
});
}
function createKaesten(zahl) {
var gesamt = $(".kasten").length+1;
var numberOf = $(".kasten").length;
for(var i=1; i<=zahl; i++) {
$(".rahmen").append("<div class='kasten nr"+ gesamt +"'><ul></ul></div>");
}
for(var n=1; n<=49; n++) {
$(".kasten ul").append("<li class='nummer'>" + n + "</li>");
}
}
And here you can test it: link for testing
As you have found, $(".kasten ul").append(...) will append to all elements that matched the ".kasten ul" selector.
You said you had a problem with a "loop within a loop", but your current code doesn't in fact nest the loops. Following is a solution that actually does nest the loops:
function createKaesten(zahl) {
var gesamt = $(".kasten").length + 1;
var numberOf = $(".kasten").length;
var newUL;
for (var i = 1; i <= zahl; i++) {
newUL = $("<ul></ul>");
for (var n = 1; n <= 5; n++) {
newUL.append("<li class='nummer'>" + n + "</li>");
}
$("<div class='kasten nr" + gesamt + "'></div>").append(newUL).appendTo(".rahmen");
}
}
$("button").click(function() {
createKaesten(3);
});
li { display: inline-block; padding: 0 10px; }
.kasten { border: thin black solid; margin: 2px; padding: 0px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button>Test</button>
<div class="rahmen"></div>
The outer loop creates a new, empty UL, then the inner loop appends the new LI items to that UL, then we create a DIV, append the new UL to it, then append the DIV to the .rahmen" container.
(Note that for demo purposes each click of the button only adds 3 x 5 items, rather than something x 49 items, and I've styled the LIs to go across the page horizontally so that it's easier to see what's happening. Click "Run code snippet" to try it out.)
Note in the code that you use the function $().append()
Appending does just that - it appends new content to the end of existing content. Append will be executed every time you click generate.
Edit: I added a codepen to illustrate this. Hit each button multiple times to see the difference.
Related
I am a beginner in Javascript. What I am trying to do is when a user clicks on "Click to start loop", the first <li> will be 1. The second time the user clicks it, it will be 2, and the third time, it will be 3. After the third click, the loop will break.
The issue with my code is that it always displays the number 3 instead of starting from 1 and going all the way to 3.
function myFunction() {
demo = document.getElementById("demo")
ul = document.createElement("ul")
demo.appendChild(ul)
li = document.createElement("li")
ul.appendChild(li)
for (let i = 1; i <= 3; i++){
li.innerText = i
}
}
<p id="demo" onclick="myFunction()">Click to start loop</p>
It is because, there is only one 'li' element created before loop starts and at the end of loop, it is just updating the final innterText.
You can fix it by moving li creation code to loop
function myFunction() {
demo = document.getElementById("demo")
ul = document.createElement("ul")
demo.appendChild(ul)
<--- from here
for (let i = 1; i <= 3; i++){
li = document.createElement("li") <--- to here
ul.appendChild(li)
li.innerText = i
}
}
You just have to save the current loop value in some place:
let i = 1;
function myFunction() {
// Check for i value
if (i === 4) return;
demo = document.getElementById("demo")
ul = document.createElement("ul")
demo.appendChild(ul)
li = document.createElement("li")
ul.appendChild(li)
// Update the i value
li.innerText = i++;
}
<p id="demo" onclick="myFunction()">Click to start loop</p>
One solution could be to create a global variable with initial value set to 1 and increase it every time there is a click on your <p> tag.
I have implemented the same using the global variable counter.
<p id="demo" onclick="myFunction()">Click to start loop</p>
<script>
var counter = 1;
function myFunction() {
if(counter === 4){
return;
}
demo = document.getElementById("demo");
ul = document.createElement("ul");
demo.appendChild(ul);
li = document.createElement("li");
ul.appendChild(li);
li.innerText = counter;
counter++;
}
</script>
I think you should put li = document.createElement("li") inside of the loops
index.js
function myFunction(status) {
demo = document.getElementById("demo")
ul = document.createElement("ul")
demo.appendChild(ul)
for (let i=1; i<=3; i++) {
li = document.createElement("li")
li.innerText = i
ul.appendChild(li)
}
}
This is unclear, are you looking for javascript function generator ?
const
ul_List = document.body.appendChild( document.createElement('ul') )
, generator = (function* ()
{
for (let i = 0; (++i < 4);)
{
ul_List.appendChild( document.createElement('li') ).textContent = i
yield
}
})()
<p id="demo" onclick="generator.next()" >Click to 3 times loop</p>
Counters
A function that deals with an incrementing variable (aka counter) usually declares or defines it as a number outside of a loop then iterates the variable with a ++ or += operator within the loop. But would a loop within an event handler that increments a number by +1 per click make much sense? So forget about iterations based on a single run of a function/event handler.
The next problem is that once the function/event handler is done, the counter is garbage collected (deleted from memory), so on the next click it is back to what it was initially (usually 0) -- so you need the user to click a HTML element, increase a number by one, and increase it by one per click thereafter. There a few ways to keep the counter's last value:
HTML/DOM
Store the last value in a HTML form control by [value]
let counter = 0;
counter++;
document.forms[0].count.value = counter;
....
<input id='count' type='hidden' value='0'><!--value will be '1'-->
Store the last value in any other type of HTML element by [data-*]
or text content
document.querySelector('.count').dataset.idx = counter;
....
<nav class='count' data-idx='1'></nav>
document.querySelector('aside').textContent = counter;
....
<aside>2</aside>
Keep in mind any value stored in HTML is converted into a string so when getting the variable counter value you must convert it back into a real number:
parseInt(document.forms[0].count.value);
parseFloat(document.querySelector('.count').dataset.idx);
Number(document.querySelector('aside').textContent);
Closures
A better way IMO is to deal with variable scope. If you noticed in the previous code, let and const are used instead of var. The reason for this is scope.
Function Scope: If var was used, then all variables would be influenced by anything inside or the immediate outside of the function it is in. If completely outside of all functions then it is global (much more susceptible to side effects and buggy behavior).
Block Scope: let and const scope is block which means they can only be accessed within the brackets they are located in:
var G = `any function, method, expression, etc can affect it or be affected
by it`;
function clickHandler(e) {
var L = `vulnerable to anything within the function and immediately
outside the function`;
if (e.target.matches('button')) {
let A,
const B = `only affects or get affected only by things within the
brackets`;
var C = `even buried deep within a function it will be hoisted to be
accessible to everything within this function`;
}
function addItem(node, count) {
/* Although a function defined within another function it cannot access
A or B*/
}
}
Wrapping a function/event handler in another function in order to provide an isolated scope and an environment wherein variables can exist past runtime and avoid garbage collection is a closure. The following examples are closures #1 is simple and #2 is more refined (user can edit each list item directly). Go here for details on scope and closures.
Example 1
function init() {
const ul = document.querySelector('ul');
let i = 0;
function clickHandler(e) {
i++;
addItem(this, i);
}
ul.onclick = clickHandler;
};
function addItem(list, counter) {
const li = document.createElement('li');
list.append(li);
li.textContent = counter;
};
init();
<ul>Click to Add Item</ul>
Example 2
function init() {
const ui = document.forms[0];
let counter = 1;
function addItem(event) {
const clicked = event.target;
if (clicked.matches('legend')) {
const list = clicked.nextElementSibling;
let marker = `${counter++}`.padStart(2, '0');
list.insertAdjacentHTML('beforeEnd', `<li contenteditable data-idx="${marker}"></li>`);
}
}
ui.onclick = addItem;
};
init();
form {
font: 1.5ch/1 Consolas;
}
legend {
font-size: large;
font-weight: bold;
user-select: none;
cursor: pointer;
}
legend::after {
content: 'Click to Add Item';
font-size: small;
}
ul {
list-style: none;
}
li {
margin: 5px 11px 5px 9px;
padding: 4px 8px;
border-bottom: 1px solid #980;
}
li::marker {
display: list-item;
content: attr(data-idx)'.';
margin-bottom: -2px;
}
<form>
<fieldset>
<legend>List<br></legend>
<ul></ul>
</fieldset>
</form>
When the page is loaded, there are 3 lists. First containing an active list and second and third are blank. When clicked on the active list li the second list (ul) is filled with one li element, the filling process continues to the third list automatically (on mouse click). Then when the button delete is pressed, both lists get removed and the process is supposed to work the same as described, but the second list for some reason doesn't get filled and everything goes to the third list.
I have tried methods: remove(), empty(), detach() but none of them seem to work.
Also in if statement I tried to check if (typeof ul.lenght === "undefined") but even this is not working.
Also to mention in console.log() when cheking ul.lenght property first time it returns 0 and undefined and fill the second list but after delete button is pressed it returns same value but won't fill the list.
Here is the code:
function myf() {
var orig = $("#ori li").length;
if ($("#ori li").length === 0 || typeof orig === "undefined") {
$(document).on('click', '#orglis li', function() {
$(this).remove();
$("#ori").append(this);
});
} else {
$(document).on('click', '#orglis li', function() {
$(this).remove();
$("#zam").append(this);
var lio = document.getElementById("ori").getElementsByTagName("li");
var larr = lio[0].innerText;
var arr = Array.from(document.querySelectorAll('#c>ul>li'), li => (li.textContent));
var tekst = "";
var i;
var j;
for (j = 0; j < arr.length; j++) {
arr[j] = arr[j].trim();
}
for (i = arr.length - 1; i >= 0; i--) {
tekst += larr + ", " + arr[i] + '\n';
}
document.getElementById("tare").innerHTML = tekst;
document.getElementById("imep").value = larr;
});
}
}
Here you can see demo.
EDIT: Also tried with outerHTML to set ul as before but still won't work
#freedomn-m was right about mixing onclick= and $(document).on("click" and I noticed that I had, without reason, two same functions
I am able to dynamically create fieldsets on button click. Let say first time I click button it creates Definition 1 then Definition 2 then Definition 3 etc.
Each fieldset has X mark to remove the dynamically created fieldset if one was created by accident.
What I am trying to do is for example Definition 2 fieldset was deleted then Definition 3 one should say Definition 2.
What I need to know is when I click X mark in one fieldset, grab the value of the legend from the next fieldset and change it to the value of the one deleted.
Here is what my dynamic call looks like:
if($(".addDef").length > 0){
i++;
}else{
i = 2;
}
$(".definitionBlock").append("<fieldset><legend class='addDef'>Definition #"+ i +"</legend><div class='removeDef'><span>✖</span></div>
</fieldset>");
Thanks!
You can use .text(function(index, text){}) to replace digit portion of .textContent with index of element within collection
var n = 2;
$(".addDefBtn").on("click", function() {
if (!$(".addDef").length) {
n = 2;
}
$(".mvnDefinitionBlock").append("<fieldset class='addDef'><legend class='addDefTitle'>Definition #" + n++ + "</legend><div class='removeDef'><span>✖click</span></div></fieldset>");
});
$('body').on('click', '.removeDef', function() {
$(this).closest('fieldset').remove();
n = 2;
$(".addDef legend").text(function(index, text) {
return text.replace(/\d+/, n++);
});
});
.removeDef {
float: right;
margin-top: -20px;
font-size: 13px;
color: red;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
Add New Definition
<div class="mvnDefinitionBlock"></div>
I wrote a JavaScript function that takes the current number of spans of the class mini in the paragraph element with an id mega, which is at least 1, and if there are less than 4, adds enough to make 4. If there was no second mini, then the second mini, which the function should create, should say 2nd, if a third one is created, it should say 3rd, and if a fourth is created, it should say 4th. For example, if there are already 2 mini spans, the program, should add 2 more, the first one added saying 3rd and the second one saying 4rd. Here's the code:
function addSpans(currentNumOfSpans)
{
var mega = document.getElementById("mega");
var mini = document.createElement("span");
mini.className = "mini";
if (currentNumOfSpans < 4)
{
if (currentNumOfSpans < 3)
{
if (currentNumOfSpans < 2)
{
mini.innerHTML = "2<sup>nd</sup>;
mega.appendChild(mini);
}
mini.innerHTML = "3<sup>rd</sup>;
mega.appendChild(mini);
}
mini.innerHTML = "4<sup>th</sup>;
mega.appendChild(mini);
}
}
Soooo.... If currentNumOfSpans is 3, it works fine, and adds 4th to mega. However, if currentNumOfSpans is 1 or 2, while it should add 2nd3rd4th or 3rd4th, respectively, it just adds 4th. Can someone help me figure out what's wrong with this. Any help's appreciated, thanks!
Note: If you notice any typos, please comment or edit, but they aren't the problem, I've checked over my code in a syntax checker, but I often make errors in my code on SO because I use a tiny phone keyboard. So basically, typo's, whichI probably made, aren't the real problem. Thanks!
Your example included a few typos, most of which could be found by running your code through a debugger, like http://jshint.com.
However, I would use a more functional approach. The following method is not hard coded like yours, so you could use it for multiple elements, or use a different number of spans with very minimal changes to the usage, I've shown this in the demo below.
function getSuffix(i) {
var j = i % 10, k = i % 100;
if (j == 1 && k != 11) return i + "<sup>st</sup>";
if (j == 2 && k != 12) return i + "<sup>nd</sup>";
if (j == 3 && k != 13) return i + "<sup>rd</sup>";
return i + "<sup>th</sup>";
}
function addSpans(scope, length) {
var spans = scope.querySelectorAll('.mini');
var current = length - (length - spans.length);
while(current < length) {
var span = document.createElement('span');
span.className = 'mini';
span.innerHTML = getSuffix(++current);
scope.appendChild(span);
}
}
var wrap = document.querySelector('.wrap'), divs;
var clone = wrap.cloneNode(true);
wrap.parentNode.appendChild(clone);
divs = wrap.querySelectorAll('.mega');
for(var i in Object.keys(divs)) addSpans(divs[i], 4);
divs = clone.querySelectorAll('.mega');
for(var i in Object.keys(divs)) addSpans(divs[i], 6 + (i * 2));
.mega { font-size: 0; } .mini { display: inline-block; width: 40px; font-size: 16px; }
<div class="wrap">
<div class="mega"></div>
<div class="mega"><span class="mini">1<sup>st</sup></span></div>
<div class="mega"><span class="mini">1<sup>st</sup></span><span class="mini">2<sup>nd</sup></span></div>
<div class="mega"><span class="mini">1<sup>st</sup></span><span class="mini">2<sup>nd</sup></span><span class="mini">3<sup>rd</sup></span></div>
<div class="mega"><span class="mini">1<sup>st</sup></span><span class="mini">2<sup>nd</sup></span><span class="mini">3<sup>rd</sup></span><span class="mini">4<sup>th</sup></span></div>
</div>
I have several links on a page, they are turned into an array with jQuery. Basically, when the user clicks "load more" on the page I would like to generate a <ul> with 4 images inside (or less if there aren't 4 remaining. I then need to shut the </ul>. I cannot figure out how I'd write this, does anyone know? Also, is this code ok or should it be tidied up somehow?
What I'd really like is a way to hide the 4 images while they load and then slide down the new <ul> but I don't know how as I don't know where I could write a callback function in there. Feel free to point me in the right direction if you know how!
My code so far;
var hrefs = jQuery.makeArray(jQuery('ul.js a'))
jQuery('#load_more').bind('click',function(){
jQuery('.img_gallery').append('<ul>')
for(var c = 0; c < 4; c++){
if((hrefs.length) > 0){
jQuery('<li>').append(jQuery('<img>', {src:hrefs[0]})).appendTo(jQuery('.img_gallery'))
hrefs.splice(0,1)
}
}
jQuery('.img_gallery').append('<ul>')
})
Thanks : )
Try this approach .. this is lot cleaner
var hrefs = jQuery.makeArray(jQuery('ul.js a'))
jQuery('#load_more').bind('click', function() {
var html = '<ul>'
for (var c = 0; c < 4; c++) {
if ((hrefs.length) > 0) {
html += '<li><img src="' + hrefs[0] + '" /></li>' ;
hrefs.splice(0,1);
}
}
html += '</ul>'
jQuery('.img_gallery').append(html )
})
Your hrefs array contains DOM elements, not URLs. Try this instead:
var hrefs = jQuery.map(jQuery('ul.js a'), function(el,i) {
return $(el).attr('href');
});
Incidentally, you can optimize your for loop as follows:
for (var c = 0; c < 4; c++) {
if (hrefs.length) {
jQuery('<li>').append(jQuery('<img>', {
src: hrefs.shift() // removes and returns the first array element
})).appendTo(jQuery('.img_gallery'));
};
};
When you use jQuery you are not appending html tags, you are appending html elements.
When you do $('.img_gallery').append('<ul>'), and then $('.img_gallery').append('<li>'), you end up with a structure like this:
<div class="img_gallery">
<ul></ul>
<li></li>
</div>
You do not have to worry about closing tags, just creating elements. jQuery "closes" all the tags for you in a sense. You need to append your li elements to the ul, not the .img_gallery. Just append to the last ul:
var hrefs = $.makeArray(jQuery('ul.js a'))
$('#load_more').bind('click',function(){
$('.img_gallery').append('<ul>');
for(var c = 0; c < 4; c++){
if((hrefs.length) > 0){
$('<li>').append($('<img>', {
src: $(hrefs[0]).attr('href'); // get the link reference from the element
})).appendTo($('.img_gallery ul').last()); // append to the last ul, not .img_gallery
hrefs.splice(0,1);
}
}
});
Or, you can keep your ul as a variable, add your li's and append the list after the loop:
var hrefs = $.makeArray(jQuery('ul.js a'))
$('#load_more').bind('click',function(){
var newul = $('<ul>'); // Makes an element, not a tag
for(var c = 0; c < 4; c++){
if((hrefs.length) > 0){
$('<li>').append($('<img>', {
src: $(hrefs[0]).attr('href'); // get the link reference from the element
})).appendTo(newul); // append to the ul you created
hrefs.splice(0,1);
}
}
$('.img_gallery').append(newul);
});