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();
Related
I do some string manipulations and wanna create a string like below. [sample] and [express] are hyperlinks.
<a> element should have click function and it should call searchFromURL().
But once it is rendered, the click function is not available, what might be the reason behind that?
Or is there any other way to accomplish this?
home.page.html
<ion-row innerHTML="{{buildNavigationSearchElement(dicDat.translation)}}"></ion-row>
home.page.ts
buildNavigationSearchElement(elementText: String){
let retElem = elementText + 'Search Text';
return retElem;
}
Is there a safest way to build this element with click function?
Finally I came up with this solution. As follows the hyperlink is generated.
home.page.html
<div [innerHTML]="buildNavigationSearchElement()"></div>
home.page.ts
buildNavigationSearchElement(){
let myText = This is a + '<a class="myhyperlink_1">sample</a>' + text to + '<a class="myhyperlink_2">express</a>' + my issue.
return myText;
}
Inside ngAfterViewChecked() event each and every hyperlink is bound to a class method.
ngAfterViewChecked() {
var myElements =
this.elementRef.nativeElement.querySelectorAll('[class^="myhyperlink"]');
var i: number;
for (i = 0; i < myElements.length; i++) {
myElements[i].addEventListener('click', this.openAlert.bind(this));
}
}
Property array is stored in class with actions and parameters for each hyperlinks.
openAlert(parameter: any) {
let className = parameter.srcElement.className;
//do what ever required
}
I am using a forEach loop and creating a link based on a condition. However, the problem I am facing is that the event listener is only added to the last item. I know this question has been asked before and I know the reason for the issue is Hoisting and I should use closure. I have tried to do that but I do not think I am doing it correctly. I would really appreciate any help as I am kind of stuck. Below are some snippets from my code (I have deleted some pieces from the code for the purpose of the question):
function edit(post, post_id){
alert(post_id);
}
function load_posts(event) {
fetch(`/posts?start=${start}&end=${end}`)
.then(response => response.json())
.then(data => {
document.querySelector('#posts').innerHTML = "<div></div>";
let c = 0;
data.forEach(i => {
if (c === data.length - 1){
return;
};
document.querySelector('#posts').innerHTML += `<div class = 'border' >`
let current_user = document.getElementById("logged_in").innerHTML;
document.querySelector('#posts').innerHTML += `<div class="row" ><div class="col-2"
id = border${i.id}></div>
</div></div>`;
if (i.user_id === parseInt(current_user)){
let element = document.createElement('a');
element.innerHTML = "Edit";
element.setAttribute('data-id', `${i.id}`);
element.setAttribute('data-post', `${i.id}`);
element.setAttribute('href', `#${i.id}`);
element.addEventListener('click', function()
{edit(this.dataset.id,this.dataset.post);});
console.log(element);
document.querySelector(`#border${i.id}`).append(element);
};
}
c++;
});});
Assigning to the innerHTML of an element will corrupt any existing listeners its children have:
document.querySelector('button').addEventListener('click', () => {
console.log('clicked');
});
container.innerHTML += '';
// click listener doesn't work anymore now
<div id="container">
<button>click</button>
</div>
Create the element with document.createElement instead of concatenating an HTML string:
const currentUserId = Number(document.getElementById("logged_in").textContent);
data.slice(0, data.length - 1).forEach((i, index) => {
const newPost = document.querySelector('#posts').appendChild(document.createElement('div'));
newPost.className = 'border';
newPost.innerHTML = `<div class="row" ><div class="col-2"
id = border${i.id}></div>
</div>`;
if (i.user_id === currentUserId) {
let element = document.createElement('a');
element.textContent = "Edit";
element.dataset.id = i.id;
element.dataset.post = i.id;
element.href = `#${i.id}`;
element.addEventListener('click', function() {
edit(this.dataset.id, this.dataset.post);
});
document.querySelector(`#border${i.id}`).append(element);
}
});
I'd also highly recommend changing your i variable name to something else - i is almost always used to indicate the index that you're iterating over. The element being iterated over should not be named i - call it something like postData instead, to avoid confusion.
I have searched for a solution to this problem but couldn't find anything specific. I am writing a javascript memory card game using a deck of cards thats i have individual images for. In the html table thats printed I have an img with an onlick event that calls my selectCard() function. this function takes the argument (img id) and uses that id to change the img src from back.gif to the corresponding front card (as stored in an array called preloadImages)
My problem is that when this happens the image src becomes [object%20HTMLImageElement].png instead of 0.png or 23.png or whichever card was clicked.
Can someone please help? Codeblock below
//sets up the table for the game
function setGame(){
document.getElementById("gameArea").innerHTML = "";
var newTable = "<table border ='1' align='center'><tr>";
for(i=0; i<4; i++){
newTable +="<tr>";
for(j=0; j<13; j++){
var cardId = j+i*13;
newTable += "<td><img id = "+cardId+" src='back.gif' width='100' height='140' onclick='selectCard("+cardId+")'/></td>";
}
newTable += "</tr>";
}
newTable += "</table>";
document.getElementById("gameArea").innerHTML = newTable;
}
//selectCard Function
function selectCard(Id){
var imageRef = document.getElementById(Id);
if (imageRef.src.match("back.gif")) {
imageRef.src = preloadImages[imageRef]+'.png';
}
else {
imageRef.src = "back.gif";
}
}
I'm pretty sure [object%20HTMLImageElement] is an HTML element, not the ID of an image. If I understand what you are trying to do correctly, you are taking the ID of the image and adding the extension ".png" to the end of it before setting the src of the image to that value. Whatever you are doing in your code is changing the src of imageRef to imageRef itself, not it's ID. Try this:
function selectCard(Id){
var imageRef = document.getElementById(Id);
if (imageRef.src == "back.gif") {
imageRef.src = Id + ".png";
} else {
imageRef.src = "back.gif";
}
}
I was able to resolve this by using the following code line
imageRef.src = preloadImages[Id].src;
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 :(
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.