Javascript document.getElementsByClassName returning undefined - javascript

I have a function which should be fairly straightforward and is supposed to be done after loading in order to reduce initial load time.
Basically I am using this code to get all of the elements with class "prefImg" and do some stuff with them. But when debugging in firebug, it says that the var divsList is undefined.
function populatePrefsList()
{
var divsList = new Array();
divsList = document.getElementsByClassName("prefImg");
var x = divsList.length;
var i = 0;
for(i=0; i<divsList.length; i++) {
var imgs = divsList[i].getElementsByTagName("img");
var imgSRC = imgs[0].src;
var alt = imgs[0].alt;
var descs = divsList[i].getElementsByTagName("h4");
var desc = descs[0].innerHTML;
//var thisPref = new preference(imgSRC, alt, desc);
//prefsList[i] = thisPref;
}
}
Obviously I have the breakpoint on var x = divsList.length...
I cannot understand this, I initially had the script in the Head of the page, but figuring it may have not loaded the divs yet, have moved it to the bottom of the Body. This did not solve it.
I have had var divsList = document.getElementsByClassName("prefImg");
If anyone could tell me where I have gone wrong then I would be grateful. There are about 50 divs with the className prefImg.
Cheers

You can use querySelectorAll instead of getElementsByClassName:
change divsList = document.getElementsByClassName("prefImg");
to this divsList = document.querySelectorAll(".prefImg");
DEMO - http://jsfiddle.net/ya3gU/
BTW, you do not need to declare the array divsList before you set it. Just do:
var divsList = document.querySelectorAll(".prefImg");

do not write this code in the head..
because this means the body did not load yet.
do it in the end of your body tag or use-
window.addEventListener("load", function()
{
// code here
});

you can use an eventhandler to the load event of the window object, to run the script only when the window has finished load
function populatePrefsList()
{
var divsList = new Array();
divsList = document.getElementsByClassName("prefImg");
var x = divsList.length;
var i = 0;
for(i=0; i<divsList.length; i++) {
var imgs = divsList[i].getElementsByTagName("img");
var imgSRC = imgs[0].src;
var alt = imgs[0].alt;
var descs = divsList[i].getElementsByTagName("h4");
var desc = descs[0].innerHTML;
//var thisPref = new preference(imgSRC, alt, desc);
//prefsList[i] = thisPref;
}
}
window.onload = function(){
populatePrefsList();
}

Older browsers (like IE6, IE7, IE8) doesn´t support getElementsByClassName and so they returns undefined.
In newer browsers the return value is never undefined. It is
mostly a HTMLCollection (but after W3C spec it should be a NodeList).
https://developer.mozilla.org/en/DOM/document.getElementsByClassName
But I think in your case the real problem is a bug in Firebug:
http://code.google.com/p/fbug/issues/detail?id=5336
It is fixed and a patch is committed and will be part of Firebug 1.10a6

Because it returns a HTMLCollection, so you should add a [number] at the end of the line:
divsList = document.getElementsByClassName("prefImg")[0];
Also it is a good idea to load this function after everything load completely by using:
window.load = function() {
populatePrefsList();
}

This is not supported by all browsers...any browser that does not support it, you would have to implement your own.
function getElementsByClassName(node,classname) {
if (node.getElementsByClassName)
return node.getElementsByClassName(classname);
else {
// custom
}
}

Related

Testing multiple browsers with protractor backed by page objects

I'm writing a test where two browsers need to interact. The problem with simply forking the browser is that my page objects still reference the old browser. I didn't want to rewrite all of my PO's to take the browser as a parameter so I tried the first solution found in the link below where they overwrite the global variables with the new browser's version :
Multiple browsers and the Page Object pattern
However, changing the global variables doesn't seem to work as all the subsequent page object functions that I call are performed against the original browser instance. I have tried logging the window handler before and after the switch and they are indeed different which only baffles me further. Here's some of the code.
spec:
var MultiBrowserFunctions = require('../common/multiBrowserFunctions.js');
var HomePage = require('../home/home.po.js');
describe('blah', function(){
it('blah', function(){
MultiBrowserFunctions.openNewBrowser(true);
HomePage.initializePage();
});
});
MultiBrowserFunctions:
(function() {
var browserRegistry = [];
module.exports = {
openNewBrowser: function(isSameUrl){
if(typeof browserRegistry[0] == 'undefined'){
browserRegistry[0] = {
browser: browser,
element: element,
$: $,
$$: $$,
}
}
var tmp = browser.forkNewDriverInstance(isSameUrl);
var id = browserRegistry.length;
browserRegistry[id] = {
browser: tmp,
element: tmp.element,
$: tmp.$,
$$: tmp.$$,
}
switchToBrowserContext(id);
return id;
},
resetBrowserInstance : function(){
browserRegistry.splice(1,browserRegistry.length);
switchToBrowserContext(0);
}
}
function switchToBrowserContext(id){
console.log('---------------------------switching to browser: ' + id);
browser=browserRegistry[id].browser;
element=browserRegistry[id].element;
$=browserRegistry[id].$;
$$=browserRegistry[id].$$;
}
}());
My questions are:
(1) why doesn't this work?
(2) Is there some other solution that doesn't involve rewriting all of my po's?
What you can do is, save the browsers in different variables and then switch between them by overriding the globals via a utility or something.
describe('Switching browsers back and forth', function () {
var browserA, browserB;
it('Browser Switch', function () {
var browsers = {
a : browser,
b : browser.forkNewDriverInstance(true)
};
browserA = browsers.a;
browserB = browsers.b;
var browserAndElement = switchBrowser(browserB);
browser = browserAndElement.browser;
element = browserAndElement.element;
//do your stuff
var browserAndElement = switchBrowser(browserA);
browser = browserAndElement.browser;
element = browserAndElement.element;
//do your stuff
});
});
The switchBrowser() can look like following:
this.switchBrowser = function (currentBrowser) {
browser = currentBrowser;
element = currentBrowser.element;
return {
browser : browser,
element : element
}
}
In this way you don't have to rewrite your POs to take in the new globals.
Hope it helps!
Cheers

Duplicating elements with Javascript and naming w/ counter

I think I've searched long enough to warrant asking this, and I hope I'm not missing something obvious, but I'm at my wits' end with this. I'm a complete JavaScript noob, and I'm having difficulty getting a script I found online to work correctly.
The project I was assigned was to make it so this form could be extended by clicking a button, and I thought I'd be able to accomplish it with HTML alone, but that doesn't seem possible. I found this script, and was able to get the duplication part of it to work:
http://www.quirksmode.org/dom/domform.html
However, the part of the script that's supposed to append a counter to the names of the fields isn't working, and therefore when the form is submitted, everything is recorded under the first form's name value. My guess is that that part of the script is trying to get the name of the wrong node, but I really don't know. Here's a shortened version of what I have. Ugly, but hopefully it gets the point across...
http://pastebin.com/nQhnXXKx
Let me know if I can clarify, and any help would be greatly, greatly appreciated!
Reorganizing the code, you could use something like this:
(function () {
"use strict";
var counter, init, addWorkshop, renameInputs, removeWorkshop;
counter = 0;
init = function () {
document.getElementById("moreWorkshops").onclick = addWorkshop;
addWorkshop();
};
addWorkshop = function () {
var clonedWorkshop, targetInsert;
counter++;
clonedWorkshop = document.getElementById("readroot").cloneNode(true);
clonedWorkshop.id = "";
clonedWorkshop.className = "";
clonedWorkshop.querySelector(".remover").onclick = removeWorkshop;
renameInputs(clonedWorkshop);
targetInsert = document.getElementById("writeroot");
targetInsert.parentNode.insertBefore(clonedWorkshop, targetInsert);
};
renameInputs = function (container) {
var children, i, j, cur, theName;
children = container.children;
for (i = 0, j = children.length; i < j; i++) {
cur = children[i];
if (cur.nodeName.toLowerCase() === "input") {
theName = cur.name;
if (theName) {
cur.name = theName + counter;
}
} else {
renameInputs(cur);
}
}
};
removeWorkshop = function () {
this.parentNode.parentNode.removeChild(this.parentNode);
};
window.onload = init;
}());
DEMO: http://jsfiddle.net/gAaxS/
Note that this is very structure-specific - for example, the this.parentNode.parentNode means that it has exactly two ancestors that you want to target. If you changed the HTML, you'd have to change the JS (which is usual).

JavaScript - Loop over all a tags, add an onclick to each one

I've got a list of links that point to images, and a js function that takes a URL (of an image) and puts that image on the page when the function is called.
I was originally adding an inline onlick="showPic(this.getAttribute('href'))" to each a, but I want to separate out the inline js. Here's my func for adding an onclick to each a tag when the page loads:
function prepareLinks(){
var links = document.getElementsByTagName('a');
for(var i=0; i<links.length; i++){
var thisLink = links[i];
var source = thisLink.getAttribute('href');
if(thisLink.getAttribute('class') == 'imgLink'){
thisLink.onclick = function(){
showPic(source);
return false;
}
}
}
}
function showPic(source){
var placeholder = document.getElementById('placeholder');
placeholder.setAttribute('src',source);
}
window.onload = prepareLinks();
...but every time showPic is called, the source var is the href of the last image. How can I make each link have the correct onclick?
JavaScript doesn't have block scope, so the closed variable ends up being whatever was last assigned to it. You can fix this by wrapping it in another closure:
function prepareLinks() {
var links = document.getElementsByTagName('a');
for(var i = 0; i < links.length; i++) {
var thisLink = links[i];
var source = thisLink.getAttribute('href');
if(thisLink.getAttribute('class') == 'imgLink') {
thisLink.onclick = (function(source) {
return function() {
showPic(source);
return false;
};
})(source);
}
}
}
Of course, you can make this one simpler and use this:
function prepareLinks() {
var links = document.getElementsByTagName('a');
for(var i = 0; i < links.length; i++) {
var thisLink = links[i];
if(thisLink.getAttribute('class') == 'imgLink') {
thisLink.onclick = function() {
showPic(this.href);
return false;
};
}
}
}
I believe this either breaks compatibility with IE5 or IE6, but hopefully you don't care about either of those =)
Minitech's answer should fix your problem, which is that the source variable is shared by all your onclick handlers
The way you're doing it is very wasteful, there's no need to set a separate handler for each link. Also, it won't work if any links are added dynamically. Event delegation is the way to go.
function interceptLinks() {
// Bad way to set onclick (use a library)
document.onclick = function() {
if (this.tagName.toUpperCase() != 'A' ) {
return;
}
// Bad way to check if it contains a class (use a library)
if (this.getAttribute('class') == 'imgLink') {
showPic(this.getAttribute('href'));
return false;
}
}
}
This is the age-old problem of event handlers inside of a loop that access an outer variable.
Your source variable is pulled off the scope chain at the time of the click event, and by then, it's been set to the last href attribute due to the iteration being finished.
You need to break the closure by doing one of two things.
The easiest but not supported by many browsers is to use let which lets you use block scope.
let source = thisLink.getAttribute('href');
jsFiddle. It worked in Firefox, but not Chrome.
In 2038, when we're dealing with the year 2038 problem and all browsers have implemented ES6, this will be the standard way to fix this problem.
A more difficult to understand and implement method that is compatible with all browsers is to break the closure with a pattern such as...
thisLink.onclick = (function(src) {
return function(){
showPic(src);
return false;
}
})(source);
jsFiddle.
Thanks for all the replies. Turns out I had diagnosed the problem incorrectly, sorry. Actually using a new var and annon. function to add an onclick on each loop iteration works (the passed href is correct). It was not working because I was getting at the a-tags by the "imgLink" class which I had removed from the HTML when I removed the inline onclick handlers (I get them with an ID on a parent now). Also I needed to use "return !showPic(this.href);" to stop the link being followed normally when clicked.
Working code:
function showPic(source){
var placeholder = document.getElementById('placeholder');
placeholder.setAttribute('src',source);
return true;
}
function prepareLinks() {
if(!document.getElementById('imgLinks')){ return false; };
var galLinks = document.getElementById('imgLinks');
var links = galLinks.getElementsByTagName('a');
for(var i=0; i<links.length; i++) {
var thisLink = links[i];
thisLink.onclick = function() {
return !showPic(this.href);
};
}
}
window.onload = function(){
prepareLinks();
}

Array syntax error newbie

I am lost in this error. I am trying to write a function and write a array in that function. in an external js file. This is going to be a random image loading function but i keep getting an error with the array and can't figure out why.
function randImg(){
var imgs = new Array();
img[0]="banner1.png";
img[1]="banner2.png";
img[2]="banner3.png";
var maxium = img.length;
}
I am getting the error on the var imgs line. any ideas?
Here is my new code calling the variable "img" was throwing me off it loads ok but it only prints out the text in the variable and not the actually file!? so at run it say "banner1.png" or "banner3.png"? any thoughts
function randImg(){
var banner = new Array();
banner[0] = "banner1.png";
banner[1] = "banner2.png";
banner[2] = "banner3.png";
var maxImg = banner.length;
var randNum = Math.floor(Math.random()*maxImg);
return banner[randNum];
}
A better way to define the array with its elements:
var img = [
"banner1.png",
"banner2.png",
"banner3.png",
"banner4.png"
]
And this is the function you might need:
function randImg(){
var img = [
"banner1.png",
"banner2.png",
"banner3.png",
"banner4.png"
]
var maxImg = img.length;
var randNum = Math.floor(Math.random()*maxImg)
return img[randNum]
}
Your variable names should be consistent. You created the new Array imgs (local variable), yet you added to img (defaulted to global variable, since you didn't declare it a local variable).
Also, note that in Javascript it doesn't make too much sense to add a var statement that is not the first line of a function. This is because of hoisting. Essentially, Javascript will take your var and move it to the first line, setting the variable to undefine. This may or may not affect how your program works vs how you think it should work, so it's best to add all your vars at the top of your functions. Javascript has function scope, not block scope.
(also note that maximum is a more meaningful variable name than maxium)
// This adds to the newly created array:
function randImg(){
var img = new Array(), // Note: img NOT imgs
maximum; // Even if a var is below this line, Javascript hoists it here
img[0] = "banner1.png";
img[1] = "banner2.png";
img[2] = "banner3.png";
maximum = img.length;
}
and you can use .push() if you're just adding to the end of an array (like you're doing)
function randImg(){
var img = new Array(), // Note: img NOT imgs
maximum;
img.push("banner1.png");
img.push("banner2.png");
img.push("banner3.png");
maximum = img.length;
}
or
function randImg(){
var img = new Array(), // Note: img NOT imgs
maximum;
img.push("banner1.png").push("banner2.png").push("banner3.png)";
maximum = img.length;
}
or better yet, just use [] to initialize an empty array or [a,b,c,...] to initialize an array with elements. Also, why use var twice?
function randImg(){
var img = ["banner1.png","banner2.png","banner3.png"], // note comma
maximum = img.length;
}
Finally, to live up to the name of the function
var randImg = function() {
var img = ["banner1.png","banner2.png","banner3.png"];
return img[Math.floor(Math.random()*img.length)];
}
... and here's a working example of randImg()

Why is this javascript function so slow on Firefox?

This function was adapted from the website: http://eriwen.com/javascript/measure-ems-for-layout/
function getEmSize(el) {
var tempDiv = document.createElement("div");
tempDiv.style.height = "1em";
el.appendChild(tempDiv);
var emSize = tempDiv.offsetHeight;
el.removeChild(tempDiv);
return emSize;
}
I am running this function as part of another function on window.resize, and it is causing performance problems on Firefox 3.6 that do not exist on current Safari or Chrome. Firefox's profiler says I'm spending the most time in this function and I'm curious as to why that is.
Is there a way to get the em size in javascript without doing all this work? I would like to recalculate the size on resize incase the user has changed it.
Seems like the function could just be
function getEmSize(el) {
return Number(getComputedStyle(el, "").fontSize.match(/(\d+)px/)[1]);
}
In other words, you can just get the computed font size of the element rather than creating a div and sizing it.
Try this (it's the same function with the value stashed so it only runs once):
var getEmSize = function() {
var underlyingFunction = function(el) {
var tempDiv = document.createElement("div");
tempDiv.style.height = "1em";
el.appendChild(tempDiv);
var emSize = tempDiv.offsetHeight;
el.removeChild(tempDiv);
underlyingFunction = function() {
return emSize;
};
return emSize;
};
return function(el) {
return underlyingFunction(el);
};
};

Categories

Resources