Why does onclick's handling function not work as expected? - javascript

I have the following little piece of code:
var instance = this;
window.onload = function () {
for (var i = 0; i < array.length; ++i) {
var currentDivId= array[i];
var currentDiv = document.getElementById(currentDivId);
try {
if (!currentDiv) {
throw 'Div id not found: ' + currentDivId;
}
var image = document.createElement('img');
image.src = 'img.jpg';
image.onclick = function() {
instance.doSomething(currentDivId);
};
currentDiv.appendChild(image);
}
catch(e) {
console.warn('oops');
}
}
};
This code is passed an array of id of divs. What it does is that, it renders an image at each of those divs and set their onclick property.
Say I have an array of strings: ['abc', 'xyz']
I want the code to place an image inside <div id="abc"></div> and another image inside <div id="xyz"></div>.
When you click the first image, instance.doSomething function should be called with parameter 'abc' and vice versa.
But the code does not work as expected. It always calls instance.doSomething with the last parameter in the array, in this case, 'xyz'.
I'm new to JS and still don't have a solid grasp of its inner workings. What's wrong here and how can I fix it?
Any help appreciated.

image.onclick = function() {
instance.doSomething(this.parentNode.id);
};
That should do it. Since we know that the image is inside the div we want to get at, just go one dom element up and get its id.

Welcome to the wonderful world of Javascript scoping issues. As it stands now, JS is treating your onclick code as something like "when this object is clicked, fetch the value stored in the currentDivID variable AT THE TIME THE CLICK occurs and pass it to the doSomething function".
What you should do is base the argument on the image object itself. Every DOM object knows where it is in the DOM tree, so at the time it's clicked, the onclick code should use DOM traversal operations to figure out which div it's inside of and dynamically retrieve its ID. That way you don't have to worry about binding variables and scoping issues... just figure out which div contains your image and get the ID at run time.

Try:
image.onclick = (function() {
var currentD = currentDivId;
return function() {
instance.doSomething(currentD);
}
})();
Hope it helps

Related

How do I call this function in other parts of my code?

Pardon me if this is a very silly question. I'm brand new to JS and I was wondering how I can use this function in other parts of my code. I looked at tutorials and other websites, but the way they define functions seems to be different than what I have here. Could anyone please nudge me in the right direction?
$('.message_div').each(function message_function()
{
console.log("One liner");
var th = $(this);
var ih = $(this).outerHeight(); // outer height
var oh = $(this).find('.message').outerHeight();
console.log("Oh", oh);
var txt = $(this).find('.message').html();
console.log("Ih", ih);
if (oh > ih)
{
th.html('');
th.html('<marquee class="message" direction="up" scrollamount="1" scrolldelay="0">' + txt + '</marquee>')
}
});
//message_function(); -----> Is this the right way?
There are several intricacies here with regards to what jQuery does. The simple way of referencing this function later on would be to store it in a variable:
function message_function()
{
console.log("One liner");
var th = $(this);
//... (rest of function omitted for brevity)
}
$('.message_div').each(message_function);//note that the function handle is used here,
//and not the result of the function which would
//have used the () to call it
///and then later on
message_function();
However, the problem here is with this. jQuery will bind this behind the scenes (which means it works fine in each), however in order to properly call the message function separately, you would need to have an element ready to bind. For example,
var element = document.querySelector("div");
message_function.call(element);
alternatively:
var element = document.querySelector("div");
var elementMessage = message_function.bind(element);
elementMessage();
Here is a broader explanation of what this is, and how jQuery interacts with it: https://stackoverflow.com/a/28443915/1026459
Inside the same file :
Move that code inside a function
Call the function
Outside of that file :
Move the function (you just created) to a .js file
Include the .js file in the desired document
Make sure the DOM elements properties match what's in the script

How to make variable accessible within jQuery .each() function?

This is and example of a frequent dilemma: how to make markup accessible inide this .each()?
I'm more interested in learning how to access outer variables from within a closure than I am in this specific issue. I could fix this problem by assigning markup from inside the each function, but I'd rather learn a more elegant way to handle this kind of problem.
// hide form & display markup
function assessmentResults(){
// get assessment responses
var markup = parseForm();
// show assessment results to user
$('#cps-assess-form fieldset').each( function() {
var q = $(this).find('.fieldset-wrapper');
var i = 0;
// hide form questions
q.slideUp();
// insert markup
$('<div>'+markup[i]+'</div>').insertAfter(q);
i++;
});
}
Read the docs, it already has an index!
.each( function(index, Element) )
No need for i
$('#cps-assess-form fieldset').each( function(index) {
var q = $(this).find('.fieldset-wrapper').slideUp();
$('<div/>').html(markup[index]).insertAfter(q);
});
The reason why yours is failing is the i is inside of the function so it is reset every iteration. You would need to move it outside of the function for it to work.

Iterate over cue method with popcorn.js

I am attempting to set multiple cue points in a video. Instead of writing each cue out, I would like to iterate over an object that has the data I need, such as the time of the cue and some info about what to do with the call back.
The problem is when I try to iterate over the object it overwrites all the cues except for the last one.
var products = myVideo.products;
var video = Popcorn('#mainVideo');
for (product in products){
var obj = products[product],
start = obj.start,
img = obj.image,
$targetDiv = $("#"+obj.targetDiv);
video.cue( start, function(){
$('<img>', {
class : "isImage",
src : 'images/' + img
}).prependTo( $targetDiv );
})
}
Any help would be greatly appreciated.
In this code, every cue has its own callback function, but every function refers to the same variable img and the same $targetDiv. By the time they run, those variables will be set to their respective values for the last item in products.
If you've ever run code through jslint and seen the warning, don't make functions in a loop, this is why. A good way to do what you're trying to do is to put those variables inside of a function that gets called immediately and returns another function, which is your callback. Like this:
function makeCallback(obj) {
return function() {
$('<img>', {
class : "isImage",
src : 'images/' + obj.img
}).prependTo( $("#"+obj.targetDiv) );
};
}
for (var product in products) {
var obj = products[product];
video.cue( obj.start, makeCallback( obj ) );
}
Alternatively, you can use forEach, which does the same thing under the hood. (Popcorn provides its own version, which handles both arrays and objects.)
Popcorn.forEach(products, function(obj) {
video.cue( start, function(){
$('<img>', {
class : "isImage",
src : 'images/' + obj.img
}).prependTo( $("#"+obj.targetDiv) );
});
});
I should note that you have another problem in this code, which is that you have Popcorn creating a new image every time you pass through the cue point. So, if the user ever skips back to replay some of the video, the images will start to pile up. Also, the images don't start loading until right when they become visible, so if there's a slow-ish network connection, the images may not show up until it's too late.
The best way to handle these is usually to create your images ahead of time but make them invisible with CSS, and then have the Popcorn events make them visible at the right time. You might consider using the image plugin, which will do most of your heavy lifting for you.

onClick replace /segment/ of img src path with one of number of values

No idea what I'm doing or why it isn't working. Clearly not using the right method and probably won't use the right language to explain the problem..
Photogallery... Trying to have a single html page... it has links to images... buttons on the page 'aim to' modify the path to the images by finding the name currently in the path and replacing it with the name of the gallery corresponding to the button the user clicked on...
example:
GALLERY2go : function(e) {
if(GalleryID!="landscapes")
{
var find = ''+ findGalleryID()+'';
var repl = "landscapes";
var page = document.body.innerHTML;
while (page.indexOf(find) >= 0) {
var i = page.indexOf(find);
var j = find.length;
page = page.substr(0,i) + repl + page.substr(i+j);
document.body.innerHTML = page;
var GalleryID = "landscapes";
}
}
},
There's a function higher up the page to get var find to take the value of var GalleryID:
var GalleryID = "portfolio";
function findGalleryID() {
return GalleryID
}
Clearly the first varGalleryID is global (t'was there to set a default value should I have been able to find a way of referring to it onLoad) and the one inside the function is cleared at the end of the function (I've read that much). But I don't know what any of this means.
The code, given its frailties or otherwise ridiculousness, actually does change all of the image links (and absolutely everything else called "portfolio") in the html page - hence "portfolio" becomes "landscapes"... the path to the images changes and they all update... As a JavaScript beginner I was pretty chuffed to see it worked. But you can't click on another gallery button because it's stuck in a loop of some sort. In fact, after you click the button you can't click on anything else and all of the rest of the JavaScript functionality is buggered. Perhaps I've introduced some kind of loop it never exits. If you click on portfolio when you're in portfolio you crash the browser! Anyway I'm well aware that 'my cobbled together solution' is not how it would be done by someone with any experience in writing code. They'd probably use something else with a different name that takes another lifetime to learn. I don't think I can use getElement by and refer to the class/id name and parse the filename [using lots of words I don't at all understand] because of the implications on the other parts of the script. I've tried using a div wrapper and code to launch a child html doc and that come in without disposing of the existing content or talking to the stylesheet. I'm bloody lost and don't even know where to start looking next.
The point is... And here's a plea... If any of you do reply, I fear you will reply without the making the assumption that you're talking to someone who really hasn't got a clue what AJAX and JQuery and PHP are... I have searched forums; I don't understand them. Please bear that in mind.
I'll take a stab at updating your function a bit. I recognize that a critique of the code as it stands probably won't help you solve your problem.
var currentGallery = 'landscape';
function ChangeGallery(name) {
var imgs = document.getElementsByTagName("img") // get all the img tags on the page
for (var i = 0; i < imgs.length; i++) { // loop through them
if (imgs[i].src.indexOf(currentGallery) >= 0) { // if this img tag's src contains the current gallery
imgs[i].src = imgs[i].src.replace(currentGallery, name);
}
}
currentGallery = name;
}
As to why I've done what I've done - you're correct in that the scope of the variables - whether the whole page, or only the given function, knows about it, is mixed in your given code. However, another potential problem is that if you replace everything in the html that says 'landscape' with 'portfolio', it could potentially change non-images. This code only finds images, and then replaces the src only if it contains the given keyword.

Creating a reusable jQuery function

Instead of re-writing a massive block of code each time, I'm trying to incorporate functions into my work but I'm having trouble making it work.
Basically, I've got a selection of radio buttons and I'm performing some stuff each time a radio button is clicked. (I'm actually loading an iFrame). However, I need to make the iFrame SRC different for each radio button so how would I cater for this within the function?
The function:
jQuery.fn.switchPreview = function () {
$('.liveDemoFrame').remove();
$('.liveDemoHand').append('<iframe class="liveDemoFrame" src="themes/src/' + themeName + '/" width="100%" height="300" scrolling="no"><p>Your browser does not support iframes.</p></iframe>');
$('.liveDemoHand').append('<div class="switchPreview"><div class="loadingPreview"></div></div>');
$('.liveDemoFrame').load(function() {
$('.switchPreview').fadeOut();
$('.switchPreview').remove();
$('.liveDemoFrame').fadeIn();
});
return this;
}
Within the iFrame SRC I have a variable called themeName. I need this to somehow change for each radio button. You can see within the code that calls the function I've tried to declare the variable each time but this still gives me an undefined error.
The code that calls it:
$('#cTheme1').click(function () {
$('input:radio[name=mgChooseTheme]:nth(1)').attr('checked',true);
var themeName = 'theme1';
$('.liveDemoFrame').switchPreview();
});
$('#cTheme2').click(function () {
$('input:radio[name=mgChooseTheme]:nth(2)').attr('checked',true);
var themeName = 'theme2';
$('.liveDemoFrame').switchPreview();
});
$('#cTheme3').click(function () {
$('input:radio[name=mgChooseTheme]:nth(3)').attr('checked',true);
var themeName = 'theme3';
$('.liveDemoFrame').switchPreview();
});
I'm sure this is something very simple but I'm still learning so little things always throw me off!
Pass in the themeName as an argument of the switchPreview function.
-Change the first line of the function to:
jQuery.fn.switchPreview = function (themeName) {
-For each of the three times you are calling the function, make sure you are passing in the argument, i.e:
$('.liveDemoFrame').switchPreview(themeName);
Why not just update the src tag of the iframe ...
$('#cTheme1').click(function () {
$('input:radio[name=mgChooseTheme]:nth(1)').attr('checked',true);
$('#liveDemoFrame').attr('src', <the new url>);
});
Then your iframe would already need to be part of the page :
<iframe id='liveDemoFrame' src='<default page>'></iframe>
i notice your using a CLASS attribute on your iFrame and accessing it using that - you really need to think about using the ID attribute if you are just wanting to refer to a single object. (as in my example above)

Categories