Beginner JavaScript for loop - javascript

I was wondering if anyone knows the reason for [i-1] in the fourth line when you also have i++ in the second line? Thank you for any help! (It's from the book "JavaScript" by Vodnik and Gosselin.)
if (figureCount === 3) {
for (var i = 1; i < 4; i++) {
filename = "images/IMG_0" + photoOrder[i] + "sm.jpg";
currentFig = document.getElementsByTagName("img")[i - 1];
currentFig.src = filename;
}//end of for loop

It because document.getElementsByTagName returns an HTMLCollection(similar to array) hence. So accessing the 1st(and subsequent) img tag on the page are done through setting i-1

document.getElementsByTagName return a HTMLCollection which is not an array but an array like object. So to access any element from that collection you can pass the index.
document.getElementsByTagName("img")[i - 1] is creating a collection of all the img tags & it is accessing specific element in that collection by passing the index [i-1]
In the below example [1] is trying to access the second element from the collection
var getAllDiv = document.getElementsByTagName('div');
console.log(getAllDiv[1].innerHTML)
<div>1</div>
<div>2</div>

Some developers get confused with the for loop operators logic instead of doing it correctly:
for (var i = 0; i < 3; i++) {
they decided to add some extra processing to the mix ( which isn't that big of a deal ) but iv'e fired developers for less.
CurrentFig is using i - 1 because it appears there is a prepended img element so the developer also chose to select it as well instead of selecting the exact elements that he needs.

Related

replace all elements belonging to a specific class

I was trying to develop an embedded widget. User would include an anchor tag and a javascript in their page, and it would render the content. Similar to embedded tweets.
<a href="http://localhost:3000/user/13"
target="_blank"
class="my-widget"
data-widget-type="profile"
data-widget-identifier="id"
data-identifier-value="13"
>Sayantan Das</a>
</div>
<script src="//localhost/my-widget/js/widget.js" async ></script>
And from widget.js, i would get all the elements with class="my-widget" and replace with an iframe.
widgets.js
!function(global, document) {
global.MyWidgets = global.MyWidgets || {};
for(let widgets = document.getElementsByClassName('my-widget'), i = 0; i < widgets.length; i++) {
console.log(widgets)
let element = widgets[i];
let span = document.createElement('span');
span.innerHTML = "Changed from widget " + i;
element.parentNode.appendChild(span);
element.parentNode.removeChild(element);
}
}(window, document);
The problem is , when I remove the element the loop also runs for a shorter number. for example, if there are two elements with class my-widget, after first time the loop runs and one element is removed and the loop runs only once. How do I replace all the elements?
That's because getElementsByClassName returns a live HTMLCollection; when you remove the class="my-widget" element from the DOM, it's also removed from the collection.
Either work backward through the collection (so you're removing from the end, which doesn't affect the ones before it), or use querySelectorAll(".my-widget") instead, which returns a snapshot NodeList, not a live HTMLCollection.
Working backward:
for(let widgets = document.getElementsByClassName('my-widget'), i = widgets.length - 1; i >= 0; i--) {
Using qSA instead:
for(let widgets = document.querySelectorAll('.my-widget'), i = 0; i < widgets.length; i++) {
or if you don't need i (you seem only to be using it to get the element and for demo purposes), you can use for-of with NodeLists now (on most platforms; this answer has a polyfill for others):
for (const element of document.querySelectorAll('.my-widget')) {
// ...and remove the `let element = ...` line
use document.querySelectorAll to the length of the widgets

getElementById in a for-loop only displays the first item

Relevant HTML portion
<nav>
<div class="create_button">+ Create KPI</div>
<div id="items"></div>
</nav>
Relevant JS portion
VSS.getService(VSS.ServiceIds.ExtensionData).then(function(dataService) {
// Get all document under the collection
dataService.getDocuments("MyCollection").then(function(docs) {
items = docs
for(var i = 0; i < docs.length; i++) {
console.log('doclen', docs.length)
console.log(items[i].name)
document.getElementById("items").innerHTML = "KPI Name : " + items[i].name;
}
});
});
My JS code fetches all data that I have in my VSTS storage. The docs contains an object with all items. It returns correctly and items[i].name contains the correct value that I want to display.
But this one just displays the first item in my <div id="items"> and not the rest.
Is this the right usage?
How can I fix it?
Here are 2 versions that show different ways to do this. Pay attention to the changes in the code that use es6 style.
VSS.getService(VSS.ServiceIds.ExtensionData).then((dataService) => {
dataService.getDocuments('MyCollection').then((docs) => {
// keep a reference to the element instead of searching for it in each loop.
const itemsDiv = document.getElementById('items');
const contents = [];
for (let i = 0; i < docs.length; i++) {
// using template strings here to show you another way of working with strings in es6
contents.push(
`<div>KPI Name : ${docs[i].name}</div>`
)
}
// finally update the target element one time with your contents.
// The new line character isn't required, can just use '', but this might be easier to read for you
itemsDiv.innerHTML = contents.join('\n');
});
});
More compact version using the map functional array method. But note that this is actually slightly slower than doing a normal for loop because its executing a function on each iteration.
VSS.getService(VSS.ServiceIds.ExtensionData).then((dataService) => {
dataService.getDocuments('MyCollection').then((docs) => {
// much more compact version using map. Note that while this is more compact,
// its slower than the for loop we did in the previous example
document.getElementById('items').innerHTML = docs.map((item) => `<div>KPI Name : ${docs[i].name}</div>`).join('\n');
});
});
The issues occours because you are setting the innerHTML of the items div on each iteration in the loop; meaning that the values will be overwritten every time and only display the last value being set in the loop.
One easy solution is to append a new element instead when you set the values to the items div
for(var i = 0; i < docs.length; i++) {
console.log('doclen', docs.length)
console.log(items[i].name)
var newElement = document.createElement('div');
newElement.innerHTML = "KPI Name : " + items[i].name;
document.getElementById("items").appendChild(newElement);
}

How do I use the querySelector?

So I got this code:
Function9() {
x = document.getElementById('BakjeRood');
x.style.opacity=1;
y = document.querySelectorAll("#BakjeBlauw, #BakjeGeel, #BakjePaars, #BakjeRoze, #BakjeWit");
y.style. opacity = 0;
}
If you click the button with function9() the image 'BakjeRood' gets an opacity of 1, but the other images (BakjeBlauw etc.) should then get an opacity of 0 (this structure is the same for all the functions I have on my site. How do I get this second part to work?
In your example, you've illustrated a knowledge of getElementById, which by nature returns a single HTMLElement instance or derivative, whereas querySelectorAll returns an enumerable, list/array-like object, containing all HTMLElement instances that match the query.
var elems = document.querySelectorAll("#x, #z, #z");
for(var index = 0; index < elems.length; index++) {
elems[index].style.opacity = 0;
elems[index].canDoOtherStuffToo();
}
You need to iterate over the results of querySelectorAll - it returns an array-like object (a list of nodes), not a single node.

Shorthand for loop passing three times even if array lenght is 1?

I have this javascript function in external .js file:
function init() {
var v = document.getElementsByTagName('video'),i;
console.log(v.length);
for (i in v) {
console.log("class:" + v[i].className + "id:" + v[i].id);
}
}
init();
And one video element in dedicated html page. This is what script returns to Chrome console:
1 // v.length
class:video1id:bigBunny //first pass of for loop
class:undefinedid:undefined //??
class:undefinedid:undefined //??
Why is this happening?
A NodeList (returned by getElementsByTagName) has not only the elements but two additional properties:
length (the amount of elements)
item (to get an element, basically the same as using [i] notation)
You're iterating them as well and treating as if they are elements. They're not; they don't have a class nor an ID. You should use a numeric for loop (for(var i = 0; i < v.length; i++) instead. This (unlike for in) obviously can't include such properties.
You should really be using a traditional for loop anyway.
for(var i = 0; i < v.length; i++)
{
console.log("class:" + v[i].className + "id:" + v[i].id);
}

Java Script fetches Document objects in infinite loop

I am writing a code to add a div depending on a set of 'object' found in document on click of button.
When Document.getElementsByTagName('object');
there is only one object, but it goes in to an infinite loop! giving me incremented value of swf.length incremented by one infinitely in an alert.
can you help whats wrong am i doing!
<script>
function get2(){
var swf = document.getElementsByTagName('object');
for (j=0;j<swf.length ;j++ )
{
alert(swf.length);
var objs = swf[j].getElementsByTagName('param');
for(i=0;i<objs.length;i++){
id = objs[i].getAttribute('name');
if (id == 'src'){
source = objs[i].getAttribute('value')
dv = document.createElement('div');
dv.setAttribute('id','myContent');
dv.setAttribute('border','2');
document.getElementsByTagName('body')[0].appendChild(dv);
/* code to embed a new swf object in new Div*/
}/*if*/
}/*for*/
}/*outer for*/
}/*function*/
here is the HTML part:
<body>
<div id="old">
</br><h1> Hello </h1>
<object id="myId1" width="250" height="250" name="movie1" classid="clsid:d27cdb6e-ae6d- 11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0"><param name="src" value="whitebird.swf">
</object>
<h1> welcome to flash</h1>
</div>
</br>
<input type="button" onclick="get2()" value="Insert"/>
</body>
Put a breakpoint in your inner for loop and inspect the values of j, i, and getElementsByTagName('object').length as well as swf[j].getElementsByTagName('param').length after the first iteration and post the results. Try getting your length outside the loop (which is good practice anyway). Also use the var keyword in your loops:
var objectLength = swf.length;
for (var j = 0; j < objectLength; j++)
{
...
}
You aren't adding more object tag in your div and just omitted that code are you?
I think the issue is that you're looping on the length of a dynamic array returned from getElementsByTagName('object') and then inside that loop you're adding a new object tag which will increase the array which will cause the loop to extend again which will add another object which will extend the loop again - forever. You can fix that by not looping on .length, but by getting the length of the initial array and only looping on that. Here are other things I suggest you fix too.
Use local variables, not global variables.
When you use getElementsByTagName, they return dynamic arrays that can change in length as you manipulate things. It's safer to loop on the initial length and thus never have infinite loop risk.
You can use document.body instead of document.getElementsByTagName('body')[0].
Some semi-colons missing.
Here's fixed up code that has these fixed/protections in it.
function get2(){
var swf = document.getElementsByTagName('object');
for (var j = 0, len = swf.length; j < len ; j++ )
{
var objs = swf[j].getElementsByTagName('param');
for(var i=0, oLen = objs.length; i < oLen; i++){
var id = objs[i].getAttribute('name');
if (id == 'src'){
var source = objs[i].getAttribute('value');
var dv = document.createElement('div');
dv.setAttribute('id','myContent');
dv.setAttribute('border','2');
document.body.appendChild(dv);
/* code to embed a new swf object in new Div*/
}/*if*/
}/*for*/
}/*outer for*/
}/*function*/
When using any of these dynamic arrays, I always prefetch the length of the array into a local variable and loop on that to avoid every having this situation.
There are two things here that make me suspicious.
The only thing that looks really suspicious to me is the following line:
document.getElementsByTagName('body')[0].appendChild(dv);
You're modifying the DOM here. What's this element that you're modifying here?
The structure of your outer loop:
for (j=0; j<swf.length; j++) { ... }
Its termination condition presumes that swf.length doesn't change.
There is a particular situation I can imagine where these two things can clash. What if you end up appending each new div right into the SWF element?

Categories