Why my "for" loop only gets the last item in html - javascript

I already read JavaScript closure inside loops – simple practical example and Why am I only getting the last item from javascript for loop? but still can't figure out, so I post my code to ask for help
function Produit(nom, prix){
this.nom = nom;
this.prix = prix;
}
// ______________ Objet ____________________
var apple = new Produit("apple", 0.30);
var banana = new Produit("banana", 0.03);
var pear = new Produit("pear", 0.35);
var kiwi = new Produit("kiwi", 0.40);
var produit = [apple,banana,pear,kiwi];
for (var i = 0; i < produit.length; i++) {
$('.test').html(produit[i].nom);
};
html
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
The for loop only gets the last item name "kiwi" in html.
I tested the loop with console.log and alert and they both get the 4 item names: "apple","banana","pear","kiwi"
Why doesn't this show up in my HTML?

The problem is you're changing the content of every single element with a test class on every loop iteration. Example:
$('.test').html('Replaced');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test">1</div>
<div class="test">2</div>
<div class="test">3</div>
<div class="test">4</div>
If you want to change the nth .test element on every loop iteration, you can use .eq.
var list = ['a', 'b', 'c', 'd'];
var $test = $('.test');
for (var i = 0; i < list.length; i++) {
$test.eq(i).html(list[i]);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test">1</div>
<div class="test">2</div>
<div class="test">3</div>
<div class="test">4</div>

Simply change the line in your for loop to this:
$('.test').eq(i).html(produit[i].nom);

When you call $(".test").html() you're replacing the contents of all the elements with that class. You need to use .eq() to specify a particular one.
for (var i = 0; i < produit.length; i++) {
$('.test').eq(i).html(produit[i].nom);
};
Another way to do it would be to pass a function to .html(). It receives the index of the element, and can use that to index the produit array:
$('.test').html(function(i) {
return produit[i].nom;
});

From what I understand, you'd like to populate your html so that you get
<div class="test">apple</div>
<div class="test">banana</div>
<div class="test">pear</div>
<div class="test">kiwi</div>
but instead you are getting
<div class="test">kiwi</div>
<div class="test">kiwi</div>
<div class="test">kiwi</div>
<div class="test">kiwi</div>
Is that correct?
If so, it's because
$('.test').html()
selects every matching instance. you aren't looping over the divs.

The issue is that this statement:
$('.test').html(produit[i].nom);
sets the HTML for ALL .test elements on every iteration of your loop, thus they are all set to the value of produit[i].nom on every iteration of your for loop so they all end up with the value at the last iteration of your loop. You aren't iterating over the .test elements to set them one at a time.
There are many different ways to do this, but I'll suggest an alternative approach that uses jQuery's .each() to iterate over your collection and then assigns the right HTML to each element:
var produit = [apple,banana,pear,kiwi];
$(".test").each(function(index, element) {
$(element).html(produit[index].nom);
});
This is also a little more efficient that some other methods because it evaluates the selector $(".test") only once and then iterates that one list of elements rather than re-evaluate the selector every time through the loop.
jQuery has lots of tricks up its sleeve. It turns out that the .html() method can also take a callback function that will be called separately for each element in the collection. So, you can also do this:
var produit = [apple,banana,pear,kiwi];
$(".test").html(function(index) {
return produit[index].nom;
});

Related

Appending more than two child elements to an element gives a strange behaviour

I have a very strange behaviour and I don't know what causes it.
I loop through all of the id's which javascript can find within my html page. Every time when the 'id' startsWith a specific label, it goes into my if statement.
Within this if-statement I am creating several elements such as P,H1 and HR. Within my 'html'-page I've got 3 unique id's which are DIV's and should go into the if statement. This worked well(picture left [ 1]), until...
I tried to append more than two child elements to those DIV elements. The first two DIV elements are extended as expected. The last one has not been extended though. At the moment that there are more than two elements added to the parent, the 3th DIV (standardArticle3) does not pass the if statement. (picture right [2])
Hopefully the code and scenario picture can make this story clearer:
home.component.ts
var allElements = document.getElementsByTagName("*");
console.log(allElements);
for (var i = 0, n = allElements.length; i < n; ++i) {
var el = allElements[i];
var foundElementId = el.id;
console.log(foundElementId);
if (foundElementId.startsWith("standardArticle"))
{
var standardArticleElement = this.jsonReaderService.getStandardArticleContent(foundElementId.substring(15, foundElementId.length));
var titleElement = document.createElement("h1");
var titleText = document.createTextNode(standardArticleElement.title);
titleElement.appendChild(titleText);
var contentElement = document.createElement("p");
contentElement.innerHTML = standardArticleElement.content;
var thematicBreak = document.createElement("hr");
thematicBreak.style.borderTopColor = "black";
var thematicBreakTwo = document.createElement("hr");
thematicBreakTwo.style.borderTopColor = "black";
// Child elements are all being appended the right way, but the last DIV will never be extended
el.appendChild(titleElement);
el.appendChild(thematicBreak);
el.appendChild(contentElement);
el.appendChild(thematicBreakTwo);
}
}
home.component.html
// other content
<div id="standardArticle1" class="col-sm-6">
</div>
// other content
<div id="standardArticle2" class="col-sm-6 pull-right">
</div>
// other content
<div id="standardArticle3" class="col-sm-6">
</div>
Scenario's 1 and 2.
Does anybody have an idea why this is happening?
document.getElementsByTagName() returns a live HTMLCollection, and your collection includes every element on the page. This means that when you append new elements, these elements get included in the collection but since you iterate up to the original length of the collection, you'll miss the last elements of the collection (the same number of elements as the number of elements you added).
It seems like a better solution to your problem would be to add a class to all elements you want to find and just get those ones with document.getElementsByClassName():
var allElements = document.getElementsByClassName("standard-article");
... and then you add your class name to your HTML:
// other content
<div id="standardArticle1" class="standard-article col-sm-6">
</div>
// other content
<div id="standardArticle2" class="standard-article col-sm-6 pull-right">
</div>
// other content
<div id="standardArticle3" class="standard-article col-sm-6">
</div>
With this approach you don't need your id.startsWith("standardArticle") if statement either.
I think I know what your problem is. Let's see what happens when you loop over all the elements on the page: for (var i = 0, n = allElements.length; i < n; ++i)
If there are 10 elements on the page, then n is set to 10. But then as you reach your if statement and add elements, n is still equal to 10 but you now have additional elements on the page. It looks like Mikael Lennholm said those more concisely and provided a good solution.

Getting infinity loop when I append div to existing divs?

I had took some program test online,from there I got some infinity loop error in appending new div to existing div.
<div id="one">
<div id="two"></div>
</div>
And this JS code is to add new div:
appendChildren();
function appendChildren() {
var allDivs = document.getElementsByTagName("div");
for (var i = 0; i < allDivs.length; i++) {
var newDiv = document.createElement("div");
allDivs[i].appendChild(newDiv);
console.log(allDivs[i]);
}
}
I want the HTML to look like this:
<div id="one">
<div id="two">
<div></div>
</div>
<div></div>
</div>
But at run time the program doesn't stop looping. Why? I couldn't guess! So can I run that appendChildren() only one time or is there another solution?
document.getElementsByTagName("div") is a live collection - it always reflects the actual data.
It means that when you append a div item, it is automatically appended to your collection. So, it never ends.
You can copy your collection using [].slice.call so that it doesn't change.
Here is the working demo snippet:
function appendChildren() {
var allDivs = [].slice.call(document.getElementsByTagName("div"));
for (var i = 0; i < allDivs.length; i++) {
var newDiv = document.createElement("div");
allDivs[i].appendChild(newDiv);
console.log(allDivs[i]);
}
}
appendChildren();
// For demonstration purposes only:
document.getElementById('html').innerText = document.getElementById('one').outerHTML;
<div id="one">
<div id="two">
</div>
</div>
<pre id="html"></pre>
The html element displays the HTML result - however, it looks bad. You may use developer tools to see the actual structure in a familiar way.

Why wont my onclick not remove any of my classes?

I have a huge problem here.
I can't get my onclick to work as I want .. So I hope someone here can help me.
#NiceToKnow
<div id="cards" class="nice"></div>
<div id="cards" class="nice"></div>
<div id="cards" class="nice"></div>
<div id="cards" class="video"></div>
I want it to display: none; every of my class="nice", so you only can see class="video", but nothing happens at all.
You are selecting the elements of the class not the class itself. So you will have to loop through the elements as javascript can only edit what is in the DOM not the CSS classes that effect your elements. So getElementsByClassName returns an array of nodes in which we must loop through and hide each one. Click runsnippet below to see this work
function changeNice(){
//ASSIGN ELEMENTS TO ARRAY
elementsofClass=document.getElementsByClassName('nice');
for(i=0; i<elementsofClass.length; i++){
//HIDE SELECTED ELEMENT
elementsofClass[i].style.display='none';
}}
#NiceToKnow
<div id="cards1" class="nice">TEST 1</div>
<div id="cards2" class="nice">TEST 2</div>
<div id="cards3" class="nice">TEST 3</div>
<div id="cards4" class="video">I don't HIDE</div>
Also don't use duplicate ID. This will cause errors later when trying to select your elements.
The getElementsByClassName will return an array, so we need to iterate through the array and hide one by one.
So it is better to declare a function and define the logic inside that. Please see the example below.
window.hideAllniceClass = function () {
var elems = document.getElementsByClassName('nice');
for (var i = 0; i != elems.length; ++i) {
elems[i].style.display = "none"; // hidden has to be a string
}
}
#NiceToKnow
<div id="cards1" class="nice">Test Content</div>
<div id="cards2" class="nice">Test Content</div>
<div id="cards3" class="nice">Test Content</div>
<div id="cards4" class="video">Test Video Content</div>
DEMO
Change your code to something like that:
var elems = document.getElementsByClassName('nice');
for(var i = 0; i < elems.length; i++) {
elems[i].style.display = 'none'
}
You have to iterate on the results returned by getElementsByClassName.
jsfiddle
You can create a loop that will loop through all the nice elements and then display none like this: https://jsfiddle.net/7vf9pz8u/
<script>
function hide(){
for(ct=0; ct < 3; ct++){
document.getElementsByClassName('nice')[ct].style.display='none'
}
}
</script>
getElementsByClassName returns array of all the match elements so you will have to provide index or loop through all of them to change their property.
Change your code to this
document.getElementsByClassName('nice')[0].style.display='none'
//For every element
var e = document.getElementsByClassName('nice');
for (i = 0; i < e.length; i++) {
e[i].style.display = "none";
}
As your divs do not have unique names they are in an array cards[].
So to access a particular div you need to reference the the array to that particular div. The quoted solution should work.

Change DIV order with jquery

I'm working on a client's HTML based site and need to randomly order a set of Divs on refresh of a page. I would normally handle this through PHP and a database call, but it's a static site.
So, I'm wondering if anyone knows how to randomly display a set of div's using jquery?
Here's an example:
<div class="myItems">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
and on refresh, it might change to:
<div class="myItems">
<div class="item">2</div>
<div class="item">3</div>
<div class="item">1</div>
</div>
Anyone know how to do that?
This willl do it
function reorder() {
var grp = $(".myItems").children();
var cnt = grp.length;
var temp,x;
for (var i = 0; i < cnt; i++) {
temp = grp[i];
x = Math.floor(Math.random() * cnt);
grp[i] = grp[x];
grp[x] = temp;
}
$(grp).remove();
$(".myItems").append($(grp));
}
Actually it's pretty simple:
$(".myItems").html($(".myItems .item").sort(function(){
return Math.random()-0.5;
}));
That's it!
Enjoy.
Another simple way is ...
1. create an array
2. generate a random number and check if it is Odd or Even
3. If odd, add your div to the top (shift method). If even, add your div to the bottom (push method).
4. So this way you will have your divs arranged randomly in the array.
5. Now simple join the array and append it to your Page.
var divArray = [];
for(var i=0; i<divs.length; i++){
//generate random number
if(rand_num == odd)
divArray.push( div[i] );
else
divArray.shift( div[i] );
}
$(myElem).html( divArray.join("") );

Remove parent element after removing last child element

I have a list of elements on a page, for the sake of discussion we can say I have the following:
<div id="group_01">
<div id="entry_1-01">stuff x</div>
<div id="entry_1-02">stuff x</div>
</div>
<div id="group_02">
<div id="entry_2-01">stuff x</div>
<div id="entry_2-02">stuff x</div>
</div>
The delete link calls an Ajax request and deletes the entry, after a succesful Ajax call, the entry div is removed from the page. My question is:
How can I remove the containing group div once all of it's entries have been deleted?
I hope that's a detailed enough question. I feel like this isn't anything new, yet two days of search has resulted in nothing.
Before you delete the child element, get its parent, count the number of children, and then after deleting the child, delete the parent if the child count is zero. Here is a quicky piece of sample code:
function d (x)
{
var e = document.getElementById(x);
var p = e.parentNode;
p.removeChild (e);
if (p.childNodes.length == 0) {
var pp = p.parentNode;
pp.removeChild (p);
}
}
I added onclicks to your divs like this:
<div id="group_01">
<div id="entry_1_01">stuff 11<a onclick="d('entry_1_01');" href="#delete">x</a></div>
<div id="entry_1_02">stuff 12<a onclick="d('entry_1_02');" href="#delete">x</a></div>
</div>
I also changed the link to "#delete". You could tidy this up in various ways.
A function like this should would work:
function removeNodeIfEmpty(node) {
var container = document.getElementById(node);
var nodeCount = 0;
for (i = 0; i < container.childNodes.length, i++) {
if (container.childNodes[i].nodeType == 1) {
nodeCount += 1;
}
}
if (nodeCount < 1) {
container.parentNode.removeChild(node);
}
}
This should account for the whitespace issue.
Assuming you do something like this to remove an entry:
entryDiv.parentNode.removeChild(entryDiv);
then you should be able to use the following code to remove the group div when the last child is removed:
var groupDiv = entryDiv.parentNode;
groupDiv.removeChild(entryDiv);
if (!groupDiv.firstChild) {
groupDiv.parentNode.removeChild(groupDiv);
}
...although you need to watch out for whitespace-only text nodes, if these entries haven't been created directly by script.
Really depends what library you're using
http://docs.jquery.com/Traversing/parent#expr
should be a suitable expression

Categories

Resources