Null style property on DOM element: JavaScript - javascript

I am trying to add some custom JavaScript to an Enjin web platform. In the platform's HTML, there is a common element in the forums that I want to change the background color of.
<div class="block-container">...content...</div>
I am attempting to access it with the following JavaScript code:
function onStart() {
for (block in document.getElementsByClassName("block-container")) {
console.log("block");
if (block.style != null) {
console.log("styleNotNull");
block.style.backgroundColor = "#252525";
}
}
}window.onload = onStart;
However, when the page loads, it logs the word "block" in the console for each element but does not change the color or log "styleNotNull". If I remove the null checker, it errors out and says that the style property is null. Most of the answers on this topic have been because the element was missing, but that is obviously not the case because the for loop is executing.

Pure javascript
Use ordinary for loop:
var blocks = document.getElementsByClassName("block-container");
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
block.style.backgroundColor = "#252525";
}
Working jsfiddle.
Basically for..in loop iterates through the properties of an object passed. More info here.
JQuery
This could be easily done by jQuery. So just in case here is an example:
$(function() {
$(".block-container").css("background-color", "#252525");
});
Working jsfiddle.

In JavaScript, a for..in loop will loop through the object passed, but for each iteration, the iterator variable will be assigned the key of the current element, not the value. So, try this instead:
function onStart() {
var elements = document.getElementsByClassName("block-container");
for (key in elements) {
elements[key].style.backgroundColor = "#252525";
}
}
window.onload = onStart;
Edit:
For a discussion about whether to use for..in or a typical for loop for arrays in JavaScript, see Why is using "for...in" with array iteration a bad idea?

The for in loop is for enumerating properties but you want to iterate over an array. Use a for loop instead. Or Array.prototype.forEach if it's available to all the browsers you're targetting.
See this post for more info - Loop through an array in JavaScript

Related

Assign HTML elements ID to document.queryselector shorthand variables in a loop

I am new to Javascript development.
I am trying to assign HTML elements IDs stored in an array to shorthands to be used in my function later.
So that instead of writing :
let addprop = document.querySelector(`#addprop`);
let readprop = document.querySelector(`#readprop`);
let editprop = document.querySelector(`#editprop`);
let footer = document.querySelector(`#footer`);
let association = document.querySelector(`#association`);
I can attribute elements ids that i store in an array like this :
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"] ;
arrayElements.forEach(el => { return(new Function (`${el} = document.querySelector("#${el}");`)()); });
Now, this bit of code works but from what I read here :
Execute JavaScript code stored as a string
This is probably not a good way to do it and also declares global variables.
One problem I encountered is that if I try to directly execute the assignment like this :
el = document.querySelector(`#${el}`);
Then the el value takes the value of the named access ID element (https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object) and breaks the code.
So I resorted to generate a string first then execute it.
I could simply assign each shorthand manually but I spent way too much time trying to make this work and am now left curious as to what would be a good solution or approach for this.
And would the scope limitations for loops simply forbid me to do this without using global variables ?
edit : switched the working code in one line
Possible answer :
1 - does it matter to declare global variables like that ? As these variables already exist globally because of browsers named access for elements IDs.
2 - By kiranvj's answer, a solution can be to store in an object structured as keys being the shortcuts and the full strings being the values, and calling the shortcuts with the object[key] method ; or using destructuring to assign the values to variable directly with :
const {addprop, readprop, editprop, idfooter, assocpatients} = elements;
I feel like I am missing something on this last one but it also seems to work.
In the end I will stick with my first code as condensing the function in one line seems to negate the risks of cross site scripting (?), and global values for the variables assigned though this method anyway already exist because of named access.
You can create a dictionary with all the elements with ID and then destroy it into your variables, ignoring the unused ones.
function getAllElementsWithId() {
let elements = {}
for (let el of document.querySelectorAll('[id]')) {
if (!(el.id in elements)) {
elements[el.id] = el
}
}
return elements
}
let { addprop, readprop, editprop, footer, association } = getAllElementsWithId()
This uses document.querySelectorAll (link to MDN) to get all elements with an ID. Notice that for big pages this could be a performance issue.
Also, what you would usually do is to add them into a container, in this case it seems like a dictionary.
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"]
let elementsId = Object.fromEntries(arrayElements.map(id => [id, document.getElementById(id)]))
This uses Object.fromEntries (link to MDN) to generate the dictionary. Also I'm using document.getElementById (link to MDN) instead of document.querySelector so you don't need to add the hashtag before the id.
If you are concerned about global scope, you can try something like below. Use forEach instead of map . map also work but since you are not handling the return of map, forEach would be a better choice.
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"];
let elements = {};
arrayElements.forEach(el => elements[el] = document.querySelector(`#${el}`));
// access variables like elements.ID-NAME
console.log(elements);
<div id="addprop"></div>
<div id="readprop"></div>
Object destructing can be used if you know the object key name.
example : let {addprop} = element;
Another thing which you might be interested is Automatic global variables
This means a new variable (scoped to window) with the name of element id is created for all the elements in page. See the html5 spec. I would not recommend using it though.
So you don't have to call like document.querySelector('addprop')
addprop variable will have the DOM object.
See this example
// these works due to automatic global varaibles binding
alert(addprop);
console.log(addprop);
<div id="addprop">Some contents</div>

Issue with my function using associative array (dom objects) and for loop

Knowledge: First week Javascript
I am trying to learn real javascript and avoid jquery at all cost. Now I am I recently learned that id's can be style easily but not classes. In order to style a class I need to loop through the dom for the class. My original code works, however my new one does not. Best practices aside for a moment, I am trying to learn how this works regardless if it is a perfect solution or not.
Problem specifics: In my new code I stored the two get functions in keys within an associative array. So I have objects which I would like my for loop to understand. I am trying to make it work like my first code.
What I tried: Honestly, I read something about squared bracket notation and how it can be useful. I felt a bit overwhelmed to be honest. What I tried was:
source[_class][i]
Maybe _class is undefined even though I defined it. I specified what class contains. Honestly im lost and would appreciate some help and of course I welcome best practice advice as well.
I want to be a better programmer and I would appreciate some insight. I dont want to start with jquery.
My experiment:
setTimeout(function() {
var source = {_id: document.getElementById('box'),
_class: document.getElementsByClassName('hint')};
for (var i = 0; i < source[_class].length; i++) {
source[_class + i].style.opacity = '0';
console.log(i);
}
}, 1000);
My original working code:
// setTimeout(function() {
// var divs = document.getElementsByClassName('hint');
// for (var i = 0; i < divs.length; i++) {
// divs[i].style.opacity = '0';
// console.log(i);
// }
// }, 1000);
Use source._class.length instead of source[_class].length and source._class[i] instead of source[_class + i]:
for (var i = 0; i < source._class.length; i++) {
source._class[i].style.opacity = '0';
console.log(i);
}
source is an object and has a property _class. You can access properties either as source._class or as source['_class'].
The property source._class is an collection of DOM nodes itself so it can be accessed like an array. You can access array elements like this: array[index].
So you have both an object with properties and an array with elements. You need to access their contents appropriately.
Styling should be done with css, not loops, because using css is an order of magnitude faster.
Create css class definitions for your set of styles and then simply change the name of the class on your elements to change their style.
Also, look into using css selectors to query the DOM. This is done with querySelector for a single element, or querySelectorAll for a set of elements. Note that jQuery wraps this functionality and that is where the name is derived.
For your specific example, the problem was with accessing the array, instead of adding the i index, you need to reference the array, and you also need to make sure you are using a string index or a dot notation (such as source._class) in order to reference that object's property
for (var i = 0; i < source['_class'].length; i++) {
source['_class'][i].style.opacity = '0';
console.log(i);
}
You missed a square bracket and it's text not a variable:
source[_class + i].style.opacity = '0';
should be
source["_class"][i].style.opacity = '0';

looping over array of elements just affects the last element

In one of my projects I just discovered, that sometimes iterating over an array of html elements (and change all of them) just affects the last element. When I log the element's attributes I can see that the loop definitily adresses every element but nevertheless visibly just the last element is getting changed.
Can anyone explain me why?
I already figured out, that a solution is to use createElement() and appendChild() instead of insertHTML. I just want to understand why javascript behaves like this.
Here is my example code:
/* creating 5 elements and storing them into an array */
var elementArray = [];
for(var n = 0;n<5;n++)
{
document.body.innerHTML += "<div id='elmt_"+n+"'>"+n+"</div>\n";
elementArray[n] = document.getElementById("elmt_"+n);
}
/* loop over these 5 elements */
for(var n = 0;n<5;n++)
{
console.log(elementArray[n].id); // logs: elmt_0 elmt_1 elmt_2 elmt_3 elmt_4
elementArray[n].innerHTML = "test"; // changes just the last element (elmt_4) to "test"
}
I created an example here: http://jsfiddle.net/qwe44m1o/1/
1 - Using console.log(elementArray[n]); in your second loop shows that innerHTML in this loop is modifying html inside your array, not in your document. That means that you are storing the div element in your array, not a shortcut to document.getElementById("elmt_"+n)
See the JSFiddle
2 - If you want to store a shortcut in order to target an element by ID, you have to add quotes for elementArray[n] = "document.getElementById('elmt_"+n+"')";, and use it with eval like this : eval(elementArray[n]).innerHTML = n+"-test";
See the JSFiddle for this try

Retrieving links from webpage using javascript

I'm new to javascript and simply trying to pull links from a webpage so I'm doing the following:
for(link in document.links) {
console.log(link.getAttribute("href");
}
But if I do this:
document.links.item(0).getAttribute("href")
It returns the link for the first href
What am I doing wrong?
Here is the webpage I'm testing against: http://en.wikipedia.org/wiki/JavaScript_syntax
Just get the elements by tag name and avoid the for in loop.
var links = document.getElementsByTagName('a'),
i;
for(i = 0; i < links.length; i += 1){
console.log(links[i].getAttribute("href"));
}
Example Here
For your example, you would have used:
for(link in document.links) {
console.log(document.links[link].getAttribute("href"));
}
While that technically works, it returns prototype properties in addition to the link elements. This will throw errors since .getAttribute("href") won't work for all the return elements.
You could use the hasOwnProperty() method and check.. but still, i'd avoid the for in loop.
for (link in document.links) {
if (document.links.hasOwnProperty(link)) {
console.log(document.links[link]);
}
}
document.links.item
is an array of items.
document.links.item(0) gets the first item in that array.
document.links.item(1) gets the second item in that array.
To answer your question, what you are doing wrong is that you are not looping the links.item array as you did in your first example.
In your code, you are accessing item 0 and only getting the href from that. For that reason, you will only get one link.
What you probably want to do is get the href for all of the of the links at once
var hrefs = [], i
for (i=0;i<document.links.length;++i) {
hrefs.push(document.links.item(i).getAttribute('href'))
}
Then your hrefs array will contains all the urls

can't iterate through a nodelist in chrome

I'm developing a chrome extension and having a problem with a nodelist type.
var compoentChange = document.getElementById("component_change");
var items = compoentChange.getElementsByTagName("option");
When I console.log(items), it shows [item: function]. When I expand it, it has all the option elements and length property.
The problem is that I can't access those elements. When I console.log(items.length), I get undefined.
How do I iterate through items variable?
for(i in items){} and for loop do not work.
NodeLists are array-like objects. You can iterate with regular for loop (not for..in):
for (var i = 0; i < items.length; i++) { ... }
Or you can convert this array-like object to a real array and use native array methods on it:
[].forEach.call(items, function(item) { ... });
You can still do items.length, so just make a for loop like this. I suggest pushing it into an array.
var myArray = [];
for(var i=0; i<items.length; i++){
myArray.push(items[i]);
}
Alright if this isn't an option maybe try something like this:
var myArray = [];
for(var i=0, e=1; i<e; i++ ){
if(items[i] != undefined){
e++;
myArray.push(items[i]);
}else{
break;
}
}
If you're logging the two during the pageload, the reason that you can console.log() the NodeList, but not the length attribute is because the NodeList is a "live" collection. Both are undefined until the DOM finishes loading, but because the NodeList is live, it will update in the Chrome console. The lengthattribute was undefined when it was logged, and because it's not live, it'll stay undefined in the console.
You can set a variable to reference the nodeList at any time, but wait until the DOM is ready before trying to use the data (using the document.ready function or perhaps document.addEventListener()).
I met similar problem with you. It turns out that it is because I should access data after DOM finishes loading.
document.addEventListener("DOMContentLoaded", function () {
divs = document.getElementsByTagName("div");
console.log(divs);
}, false);
for...of can be used for this situation. However due to a bug, this opportunity cannot be used on chromium.

Categories

Resources