Javascript Array Undefined - javascript

I keep getting the error links[i] is undefined.
I define it explicitly and yet it keeps giving me that error -- any ideas?
I am trying to do unobtrusive image rolovers on 5 links that I have.
function loadImages(){
path = 'uploads/Splash-4/nav/';
links = new Array();
for (i=1;i<=5;i++){
var id = "link-"+i;
var defaultState = '<img src="' +path+i+'.jpg" border="0" />';
links[i] = document.getElementById(id);
// Place all image linksinto anchor
links[i].innerHTML = defaultState;
// What to do on mouseover
links[i].onmouseover = function() {
links[i].innerHTML = '<img src="' +path+i+'a.jpg" border="0" />';
}
// What to do on mouse oUt
links[i].onmouseout = function() {
links[i].innerHTML = defaultState;
}
}
}
window.onload = loadImages;
HTML:
<br />
<br />
<a href="?page=free-home-appraisal" id="link-4" /></a><br />
<br />

First off, you should be saying:
var links = [];
It's generally discouraged to use the Array constructor itself, and by not specifying var, you're making the links variable reside in the global space, which is generally bad.
Now, as to your actual problem.
Your event handlers are carrying a reference to the path and i variables from the outer scope, but by the time they're actually encountered, the variable i has the value 6 -- not what you intended at all! In order to fix that, you can change:
// What to do on mouseover
links[i].onmouseover = function() {
links[i].innerHTML = '<img src="' +path+i+'a.jpg" border="0" />';
}
// What to do on mouse oUt
links[i].onmouseout = function() {
links[i].innerHTML = defaultState;
}
to
// What to do on mouseover
links[i].onmouseover = (function(path, i) {
return function () {
links[i].innerHTML = '<img src="' +path+i+'a.jpg" border="0" />';
};
})(path, i);
// What to do on mouseout
links[i].onmouseout = (function(i) {
return function () {
links[i].innerHTML = defaultState;
}
})(i);
This creates a new closure to hold the variables you want to capture. This way the inner i can still be, oh, 3 while the outer i goes to 6.

The problem is that when your onmouseover() function gets called, your variable i = 6 because your last iteration yielded i = 6, causing the loop to end. Therefore, you must protect i somewhere. For example :
function loadImages(){
path = 'uploads/Splash-4/nav/';
var links = [];
for (i=1;i<=5;i++){
(function(j) {
var id = "link-"+j;
var defaultState = '<img src="' +path+j+'.jpg" border="0" />';
links[j] = document.getElementById(id);
// Place all image linksinto anchor
links[j].innerHTML = defaultState;
// What to do on mouseover
links[j].onmouseover = function() {
links[j].innerHTML = '<img src="' +path+j+'a.jpg" border="0" />';
}
// What to do on mouse oUt
links[j].onmouseout = function() {
links[j].innerHTML = defaultState;
}
})(i); // call the anonymous function with i, thus protecting it's value for
// the mouseover/mouseout
}
}

Your code snippet doesn't include a declaration of the variable links. If you haven't defined it elsewhere (i.e., if it's a local variable), you'll need to do it here:
Instead of
links = new Array();
You could do
var links = new Array();
One example can be found here.
If you have declared it elsewhere, it could be that this line
document.getElementById(id);
is returning null.

Related

JavaScript Mutliple Counters

I am following a tutorial on Udacity. My task is to create a webpage which displays cat images. When I click on the image of a cat an counter for this cat will increase. Everything works as planed, but the onclick event does not get binded properly to the image.
This is my JavaScript file (the html simply adds this script to the body. But I have copied it for reference to the end of this question.).
What is going wrong with my onclick function?
// create images from list of objects.
const images = [
new Image("Lorro", "http://placekitten.com/200/300"),
new Image("Locho", "http://placekitten.com/300/300"),
];
function Image(name, src) {
this.name = name;
this.src = src;
this.alt = `This is the image of ${this.name}`;
this.clicks = 0;
this.increment = function increment() {
this.clicks++;
};
}
function createElements() {
document.body.innerHTML =
"<ul>" +
images.map((image) => {
return `<li>
<h2>${image.name}</h2>
<img src="${image.src}" alt="${image.alt}" onclick="${image.increment()}">
<p>${image.name} was clicked ${image.clicks} times.</p>
</li>`}).join(" ") +
"</ul>";
}
createElements();
You need to pass a function name and then paranthesis after the name like click().
To achieve this in your case use onclick="(${image.increment})()"
${image.increment} returns a function, and then make sure to wrap the function name inside parenthesis '(${image.increment})' and then call it like '(${image.increment})()'.
// create images from list of objects.
const images = [
new Image("Lorro", "http://placekitten.com/200/300"),
new Image("Locho", "http://placekitten.com/300/300"),
];
function Image(name, src) {
this.name = name;
this.src = src;
this.alt = `This is the image of ${this.name}`;
this.clicks = 0;
this.increment = function increment() {
this.clicks++;
alert(`Image Clicked`);
};
}
function createElements() {
document.body.innerHTML =
"<ul>" +
images
.map((image) => {
return `
<li>
<h2>${image.name}</h2>
<img src="${image.src}" alt="${image.alt}" onclick="(${image.increment})()">
<p>${image.name} was clicked ${image.clicks} times.</p>
</li>`;
})
.join(" ") +
"</ul>";
}
createElements();
Currently the way your code is setup it won't give you the desired results.
What I would suggest is create a general handler and inside that check which cat image was click (I've given an id to each image to easily figure out which image was clicked) and then call its increment method.
Explanation for the handleClick method.
The handleClick method gets the id of the image clicked using the event object passed to the click handler.
Using the id get the correct Image object from the images array and call its increment method.
And finally update the innerText of the p tag associated with this image (which I've grabbed using nextElementSibling).
NOTE: You need to update the DOM to reflect the changes.
const images = [
new Image("Lorro", "https://via.placeholder.com/100?text=Lorro"),
new Image("Locho", "https://via.placeholder.com/100?text=Locho"),
];
function Image(name, src) {
this.name = name;
this.src = src;
this.alt = `This is the image of ${this.name}`;
this.clicks = 0;
this.increment = function increment() {
console.clear();
console.log(this.name);
this.clicks++;
};
}
function handleClick(e) {
const {id} = e.target;
images[id].increment();
e.target.nextElementSibling.innerText = `${images[id].name} was clicked ${images[id].clicks} times.`
}
function createElements() {
document.body.innerHTML =
"<ul>" +
images.map((image, i) => {
window.copyFunc = this.increment;
return `
<li>
<h2>${image.name}</h2>
<img src="${image.src}" alt="${image.alt}" id="${i}" onclick="handleClick(event)">
<p>${image.name} was clicked ${image.clicks} times.</p>
</li>`
}).join(" ") +
"</ul>";
}
createElements();
The problem is you onclick assignment. Inside your template literal you assign to the attribute what your image.increment() call is returning. Since that function doesn't return anything explicitly, the default return value for Javascript functions is returned, which is undefined, resulting in
<img src="http://placekitten.com/200/300" alt="This is the image of Lorro" onclick="undefined">
There is no easy fix to this using onclick attribute, so I've refactored your code slightly to using a class Imag extends Image. Please also note that your function name Image was conflicting with the built-in window.Image, which is why I renamed it to Imag.
class Imag extends Image {
constructor(name, src) {
super();
this.name = name;
this.src = src;
this.alt = `This is the image of ${this.name}`;
this.clicks = 0;
this.increment = this.increment.bind(this);
this.addEventListener('click', this.increment);
}
increment() {
this.clicks++;
this.closest('li').querySelector('.clicks').textContent = this.clicks;
}
}
// create images from list of objects.
const images = [
new Imag("Lorro", "http://placekitten.com/200/300"),
new Imag("Locho", "http://placekitten.com/300/300"),
];
function createElements() {
document.body.innerHTML =
"<ul>" +
images.map((image) => {
return `<li>
<h2>${image.name}</h2>
<span class="image-container"></span>
<p>${image.name} was clicked <span class="clicks">${image.clicks}</span> times.</p>
</li>`
}).join("\n") +
"</ul>";
document.querySelectorAll('.image-container').forEach((span, index) => {
span.appendChild(images[index]);
});
}
createElements();

Is there a way to make a function that can be accessed later in a google chrome extension?

I am trying to make an extension that makes a button which then executes a function, however, because the extension executes a javascript file after the page loads. However, when I create the button I want to run a function. Is there a way I can store a function and variables that can be run and access later by the button?
var effect = [[1,100],[2,32], [5,3]];
var points = (function(){var adding = 0;for(var i = 0; i<effect.length;i++){adding+=effect[i][0];};return adding;})()
var score = (function(){var adding = 0;for(var i = 0; i<effect.length;i++){adding+=effect[i][1];};return adding;})()
var percentage=(score/points*100).toString()+"%";
this.effect = effect;
this.points = points;
this.score = score;
var percentage = points/score;
function list_effect(){
var effect_string = "";
for(var i = 0; i<effect.length;i++){
effect_string += ((points-effect[i][0])/score) - ((points)/score);
}
alert(effect_string);
}
if(percentage == 'NaN%'){
// alert('ERROR');
}else{
document.getElementsByClassName("agenda")[0].innerHTML = "<button type=\'button\' id=\'get_list\'>Get List</button>"+ document.getElementsByClassName("agenda")[0].innerHTML;
document.getElementsByClassName("agenda")[0].innerHTML += "<script>" + "this.effect=" + effect.toString() + ";\nthis.points=" + points.toString()+ ";\nthis.score=" + score.toString() +"document.getElementById(\"get_list\").addEventListener(\"onclick\", list_effect());" + "</script>"
}
I have tryied useing this. however that does not work
<script> won't run when added via innerHTML, you should add it using appendChild
normally you don't need to add a <script> element at all, just use createElement and attach the listeners directly
var button = document.createElement('button');
button.id = 'get_list';
button.onclick = function (e) {
// use your variables here directly
};
// clear the previous contents
document.querySelector('.agenda').textContent = '';
// add the button
document.querySelector('.agenda').appendChild(button);

javascript adding click events to buttons in a loop

The code below gets info from xml file.
I succesfully presents the id and name of each planet with a button.
I want to add an onclick event on the button.
Problem now is: it does add the onclick event but only on the last button created in the loop.
What am i doing wrong? Why doesnt it create a onclick event for each button, but only for the last one in loop?
function updatePlaneten() {
var valDiv, planets, valButton, textNode;
// Get xml files
planets = this.responseXML.getElementsByTagName("planeet");
// loop through the <planet> tags
for (var i = 0; i < planets.length; i++) {
valDiv = ''; // clear valDiv each time loop starts
// Get the id and the name from the xml info in current <planet> tag
valDiv += planets[i].getElementsByTagName("id")[0].childNodes[0].nodeValue + "<br>";
valDiv += planets[i].getElementsByTagName("name")[0].childNodes[0].nodeValue + "<br>";
document.getElementById("planetenID").innerHTML += valDiv + "<br>";
// Create button with a value and pass in this object for later reference use (valButton.object=this)
valButton = document.createElement("input");
// valButton.setAttribute("planeetID", planets[i].getElementsByTagName("id")[0].childNodes[0].nodeValue);
valButton.setAttribute("value", 'Meer info');
valButton.setAttribute("type", 'button');
valButton.id = (i + 1);
valButton.object = this;
//
// Here is the problem i cant get fixed
//
//valButton.onclick = function(){ showinfo(); }
valButton.addEventListener('click', showinfo);
// Place the button on screen
document.getElementById("planetenID").appendChild(valButton);
}
}
// simple function to check if it works
function showinfo() {
console.log(this.object);
console.log(this.id);
}
The trouble is this line:
document.getElementById("planetenID").innerHTML += valDiv + "<br>";
When you set innerHTML the content currently in there gets destroyed and replaced with the new html, meaning all your old buttons are now destroyed and new ones are created. The previously attached event listeners do not get attached to the new buttons.
Instead simply create a div/span or whatever container would best help, add your planet text or whatever to it and then use appendChild
valDiv = document.createElement("div");
var id = planets[i].getElementsByTagName("id")[0].childNodes[0].nodeValue;
var name = planets[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
valDiv.innerHTML = id+"<br>"+name+"<br>";
document.getElementById("planetenID").appendChild(valDiv);
You could also use insertAdjacentHTML
var id = planets[i].getElementsByTagName("id")[0].childNodes[0].nodeValue;
var name = planets[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
valDiv = id+"<br>"+name+"<br>";
document.getElementById("planetenID").insertAdjacentHTML("beforeend",valDiv);
function updatePlaneten() {
var valDiv, planets, valButton, textNode;
// Get xml files
planets = this.responseXML.getElementsByTagName("planeet");
// loop through the <planet> tags
for (var i = 0; i < planets.length; i++) {
(function(num){
valDiv = document.createElement("div");
// Get the id and the name from the xml info in current <planet> tag
var id = planets[num].getElementsByTagName("id")[0].childNodes[0].nodeValue + "<br>";
var name = planets[num].getElementsByTagName("name")[0].childNodes[0].nodeValue + "<br>";
valDiv.innerHTML = id+"<br>"+name+"<br>";
document.getElementById("planetenID").appendChild(valDiv);
// Create button with a value and pass in this object for later reference use (valButton.object=this)
valButton = document.createElement("input");
// valButton.setAttribute("planeetID", planets[i].getElementsByTagName("id")[0].childNodes[0].nodeValue);
valButton.setAttribute("value", 'Meer info');
valButton.setAttribute("type", 'button');
valButton.id = (num + 1);
valButton.object = this;
// FIX: PASS showinfo TO AN ANONYMOUS FUNCTION CONTAINING THE OBJECT
valButton.addEventListener('click', function(){
showinfo(valButton);
});
// Place the button on screen
document.getElementById("planetenID").appendChild(valButton);
}(i));
}
}
// simple function to check if it works
function showinfo(valButton) {
console.log(valButton.object);
console.log(valButton.id);
}

Javascript Weird - I'm clueless

I'm working on this menu-system that's very similar to how operating systems do them.
Using jquery etc.
I have 2 comments down in the For Loop. It's basically outputting the last index each in the $(document).on('click')... function. But outside the document.on it works fine.
It's probably just an obvious problem but I've spent about an hour on this.. Thanks in advance!
menu: function(title) {
this.title = title;
this.slug = slugify(title);
this.icon = false;
this.buttons = Object();
this.num_buttons = 0;
this.visible = false;
this.timeout_id = null;
this.is_hovering_dropdown = false;
this.is_hovering_menu = false;
this.render = function() {
var that = this;
var slug = that.slug;
var str = '<li id="menu-' +slug +'">' + this.title + '';
if (this.num_buttons > 0) {
str += '<ul id="menu-dropdown-' + slug + '" style="display: none;" class="dropdown">';
for (var button in this.buttons) {
str += '<li>' +that.buttons[button]['title'] +'</li>'
alert(button) //new project, open project, save as etc.
$(document).on("click", "#menu-dropdown-" +slug + '-' + that.buttons[button]['slug'], function() {
$("#menu-dropdown-" + slug).hide("fade", 200);
that.visible = false;
alert(button);//save as, save as, save as, save as etc.
});
}
}
}
}
Here you go:
Thanks to the order of operations, and scoping, all of your buttons are being saved with a reference to the LAST value of button.
What you want to do is put that assignment inside of an immediately-invoking function, and pass the button into that particular function-scope.
(function (button) { $(document). //...... }(button));
Everything inside of the immediate function should still have access to the static stuff outside of the immediate-function's scope (ie: that), AND it will also have a reference to the current value of button, as it's being invoked then and there.
The longer version of the story is that your buttons, when being created are being given a reference to button, rather than the value of button, therefore, when they're actually invoked at a later time, they reference the value of button as it currently exists (ie: the last value it was assigned in the loop).

How can I pass an object to a function in innerHTML (JavaScript)?

How can I pass an object to a function in innerHTML?
Here is an example:
function clickme()
{
var coord = {x:5, y:10};
testimageih(coord);
}
function testimageih(coord)
{
var html = "<img id=\"sam1\" border=\"0\" name=\"sam1\" src=\"sample.gif\" " +
"onLoad=\"ImageOnLoad(" + coord + ");\"></img>";
document.getElementById("content").innerHTML = html;
}
function ImageOnLoad(coord)
{
if (coord) alert("Success");
else alert("Fail");
}
How can I pass this object, coord? It's my only other recourse, at the moment, is passing coord.x and coord.y, instead of the object.
Thank you.
The simplest way is to create an image, attach the event handler and insert the element using DOM methods.
function testimageih(coord)
{
var img = document.createElement('img');
img.id = 'sam1';
img.border = 0;
img.name = 'sam1';
img.src = 'sample.gif';
img.onload = function() {
ImageOnLoad(coord);
};
document.getElementById('content').appendChild(img);
}
Note that this has one difference to the code you have above: it doesn't remove any elements currently in #content. If that will happen, you will have to do the removal separately.
You could use document.createElement instead of innerHTML.
// Create the element
var element = document.createElement('img');
element.setAttribute('border', 0);
element.setAttribute('name', 'sam1');
element.setAttribute('src', 'sample.gif');
// Attach onLoad handler to it (passing it the object)
element.onload = function() { ImageOnLoad(coord) };
// Replace the contents of your... div?
var content = document.getElementById("content")
content.innerHTML = '';
content.appendChild(element);
The way you are implementing it now, yes--you're creating HTML as a string, and embedding JavaScript within that string; your options are limited.
And geez, use single-quotes around the html var so you don't have to escape everything :(

Categories