Dynamically assign event in javascript object - javascript

I have following javascript class:
var ImageGallery = function(arr) {
var ImgArr = arr;
var MainImgId = "";
this.build = function() {
var div = document.createElement("div");
for (var i = 0; i < ImgArr.length; i++) {
var img = document.createElement("img");
img.scr = ImgArr[i];
// what to do next, how to assign loadMainImage function ?????
img.onclick =
div.appendChild(img);
}
}
this.loadMainImage = function(imgPath) {
document.getElementById(MainImgId).src = imgPath;
}
}
next, in code I create an object of that class:
var gallery = new ImageGallery(someArrWithPaths);
gallery.MainImgId = 'idOfMainImg';
gallery.build();
How can I assign img.onclick event to launch loadMainImage(imgPath) function every time I click on the image?
Thanks a lot!

You need to capture the current value of this (which refers to the ImageGallery object) and invoke loadMainImage on it within onclick. Inside onclick, this refers to the image itself, so this.src will give you the image source:
var self = this;
img.onclick = function() {
self.loadMainImage(this.src);
};

I would try to avoid using methods like onevent (in your case onclick), because it is easy to loose this handler (just writing img.onclick somewhere else will override previous handler)
try to use some javascript library (like jQuery), it will simplify your development at least a bit. And back to your question, using YUI 2 I would write so:
var imgCount = ImgArr.length; // do not use ImgArr.length in for definition, it is better
// to save it once and then use... otherwise each time in loop will look up ImgArr object to find length property
for (var i = 0; i < imgCount; i++) {
var img = document.createElement("img");
var src = ImgArr[i];
img.scr = src ;
YAHOO.util.Event.addListener(img, "click", this.loadMainImage, src, this);
}
You can find similar function in jQuery or other frameworks. What it does:
YAHOO.util.Event.addListener(obj, event, handler, argument, context) - attach event listener to obj, for event and handler specified. argument - what to pass to that handler, and context to execute that handler (in your case it will be just this)
Doing this just don't forget to unattach this handler when you unload your page (or whatever you do), e.g. YAHOO.util.Event.removeListener(img, "click", this.loadMainImage), or calling function which removes all listener attached (like YUI's purgeElement). Yes, things are a bit easier when you use img.onclick = ...;, seems like in this case browser will take care about it.

Related

naturalWidth and naturalHeight returns 0 using onload event

I have read countless of answers of this issue and I came up with the following, but it doesn't work either.
function fitToParent(objsParent, tagName) {
var parent, imgs, imgsCant, a, loadImg;
//Select images
parent = document.getElementById(objsParent);
imgs = parent.getElementsByTagName(tagName);
imgsCant = imgs.length;
function scaleImgs(a) {
"use strict";
var w, h, ratioI, wP, hP, ratioP, imgsParent;
//Get image dimensions
w = imgs[a].naturalWidth;
h = imgs[a].naturalHeight;
ratioI = w / h;
//Get parent dimensions
imgsParent = imgs[a].parentNode;
wP = imgsParent.clientWidth;
hP = imgsParent.clientHeight;
ratioP = wP / hP;
//I left this as a test, all this returns 0 and false, and they shouldn't be
console.log(w);
console.log(h);
console.log(ratioI);
console.log(imgs[a].complete);
if (ratioP > ratioI) {
imgs[a].style.width = "100%";
} else {
imgs[a].style.height = "100%";
}
}
//Loop through images and resize them
var imgCache = [];
for (a = 0; a < imgsCant; a += 1) {
imgCache[a] = new Image();
imgCache[a].onload = function () {
scaleImgs(a);
//Another test, this returns empty, for some reason the function fires before aplying a src to imgCache
console.log(imgCache[a].src);
}(a);
imgCache[a].src = imgs[a].getAttribute('src');
}
}
fitToParent("noticias", "img");
To summarise, the problem is the event onload triggers before the images are loaded (or that is how I understand it).
Another things to add:
I don't know at first the dimensions of the parent nor the child,
because they varied depending of their position on the page.
I don't want to use jQuery.
I tried with another function, changing the onload event to
window, and it worked, but it takes a lot of time to resize because
it waits for everything to load, making the page appear slower,
that's how I came to the conclusion the problem has something to do
with the onload event.
EDIT:
I made a fiddle, easier to look at the problem this way
https://jsfiddle.net/whn5cycf/
for some reason the function fires before aplying a src to imgCache
Well, the reason is that you are calling the function immedeatly:
imgCache[a].onload = function () {
}(a);
// ^^^ calls the function
You call the function and assign undefined (the return value of that function) to .onload.
If you want to use an IIFE to capture the current value of a, you have to make it return a function and accept a parameter to which the current value of a is assigned to:
imgCache[a].onload = function (a) {
return function() {
scaleImgs(a);
};
}(a);
Have a look again at JavaScript closure inside loops – simple practical example .

Not able attach click event for a button using Javascript oop

I'm working on attaching a click event on a button which is having class "nextPage", but its not working. Let me show you the code.
function myContent() {
}
myContent.prototype.clickNext = function() {
alert("clicked");
};
var objMyContent = new myContent();
var el = document.getElementsByClassName('nextPage');
el.onclick=objMyContent.clickNext();
Please take a look into it. Please let me know where I did mistake.
You need to reference the function, not execute it, when assigning the click handler.
Instead of:
el.onclick = objMyContent.clickNext();
Use this:
el.onclick = objMyContent.clickNext;
The first piece of code executes clickNext, and assigns it's return value to el.onclick.
The second line assigns a reference to the clickNext function to el.onclick, instead. (Which is what you want)
Also, getElementsByClassName returns an HTMLCollection (Which is basically an array of HTML elements).
You'll need to assign the click handler to each found element in that collection:
for(var i = 0; i < el.length; i++){
el[i].onclick = objMyContent.clickNext;
}
iterate the array and the assign the onclick event
var objMyContent = new myContent();
var el = document.getElementsByClassName('nextPage'); for(var i=0;i<el.length;i++) el[i].onclick=objMyContent.clickNext;

Assign a Function Argument with a Loop

I have an array of list items in a piece of Javascript code. I would like to assign an onclick event handler to each one. Each handler would be the same function, but with a different input argument. Right now I have:
function contentfill(i) {
box = document.getElementById("text");
box.style.background="rgba(0,0,0,0.8)";
var content = new Array();
contentdivs = document.querySelectorAll("#contentfill>div");
box.innerHTML = contentdivs[i].innerHTML;
}
li[3].onclick = function() {contentfill(0);};
li[4].onclick = function() {contentfill(1);};
li[5].onclick = function() {contentfill(2);};
This works well enough, but I would like to achieve the same thing with a loop, for example:
for(i=3;i<=5;i++) {
j=i-3;
li[i].onclick = function() {contentfill(j);};
}
This, however, does not work. Since j seems to be defined as 2 at the end of the loop, each time I click, it only seems to call contentfill(2).
For an alternative approach, consider having each of the elements aware of what argument it should be using.
for (var i = 0; i < 3; i++) {
var el = li[i + 3];
el.dataset.contentIndex = i;
el.addEventListener('click', contentfill);
}
Then contentfill would have to extract the argument from .dataset instead of taking an argument, of course. (This is the same mechanism as jQuery's $.data.)
I tend to prefer this since (a) it doesn't generate tons of tiny wrappers, (b) it allows me to later examine and possibly change the "arguments", and (c) it lets me predefine them in the document using data- attributes. Effectively changes them from function arguments into behavior.
The value of i - 3 should be bound to the click handler function; a closure can provide this functionality:
li[i].onclick = (function(j) {
return function() {
contentfill(j);
}
)(i - 3));
Btw, it's better practice to use addEventListener or attachEvent to register click handlers.

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();
}

Using 'this' inside a link generated by a javascript object

Javascript is pretty shaky for me, and I can't seem to find the answer to this. I have some code along the lines of
var Scheduler = function(divid,startDate,mode){
this.setHeader = function(){
header.innerHTML = 'Show';
}
this.showScheduler = function period(){
...
}
};
My problem is, how do I put the onclick into the HTML so that it properly calls the showScheduler function for the appropriate instance of the current scheduler object that I'm working with?
I wouldn't do whatever it is you're doing the way you're doing it, but with the code the way you have it, I would do this (lots ofdo and doing :) ):
var Scheduler = function(divid, startDate, mode){
var that = this;
this.setHeader = function(){
header.innerHTML = 'Show';
header.firstChild.onclick = function() { that.showScheduler(1); };
}
this.showScheduler = function period(){
...
}
};
You should use a framework for this type of thing. If you don't use one then you gotta declare each instance of schedular as a global object, and you will need the name of the instance in order to call it from the link. Look at the following link
http://developer.yahoo.com/yui/examples/event/eventsimple.html
They only show a function being applied, but you can also do something like this
YAHOO.util.Event.addListener(myAnchorDom, "click", this.showScheduler,this,true);
Where myAnchorDom is the achor tag dom object. This will have showScheduler function execute within the scope of your scheduler object.
Instead of working with innerHTML use the DOM methods.
Try replacing this:
header.innerHTML = 'Show';
with this:
var x = this; // create a closure reference
var anchor = document.createElement('a');
anchor.href= '#';
anchor.innerHTML = 'Show';
anchor.onclick = function() { x.showScheduler(1); }; //don't use onclick in real life, use some real event binding from a library
header.appendChild(anchor);
Explanation:
The "this" in the original code refers to the element which fired the event, i.e. the anchor ("this' is notoriously problematic for things like, well, like this). The solution is to create a closure on the correct method (which is why you have to create something like the var x above) which then only leaves the problem of passing in the parameter which is accomplished by wrapping the method in another function.
Strictly speaking it would be much preferable to bind eventhandlers with the addEventListener/attachEvent pair (because direct event assignment precludes the ability to assign multiple handlers to one event) but it's best handled using a library like jquery if you're new to JS anyway.
You can add an event handler to the header object directly:
var me = this;
this.setHeader = function(){
header.innerHTML = 'Show';
header.addHandler("click", function(e) { me.showScheduler(1); });
}
Insite the passed function, this will refer to the header element.
var Scheduler = function(divid, startDate, mode)
{
var xthis = this;
this.setHeader = function()
{
var lnk = document.createElement("a");
lnk.addEventListener("click", xthis.showScheduler, false);
lnk.innerText = "Show";
lnk.setAttribute('href', "#");
header.appendChild(lnk);
}
this.showScheduler = function period(){
...
}
};
When using "this" inside the onclick attribute, you're actually referring to the anchor tag object. Try this and see if it works:
this.setHeader = function(){
header.innerHTML = 'Show';
}

Categories

Resources