Variable scope issue in JavaScript - javascript

I have quickly coded up a sort of product display thing that gets half of its input from the page, and the other half from an AJAX query.
Here is the code...
function productDisplay() {
products = [];
this.index = 0;
setupProductDisplay();
processListItems();
showProduct();
function setupProductDisplay() {
var productInfoBoxHtml = '<div id="product-info"><h3 class="hide-me"></h3><span id="dimensions" class="hide-me"></span><div id="product-gallery"><img alt="" src="" /></div><ul id="product-options" class="hide-me"><li id="spex-sheet">Download full spex sheet</li><li id="enlarge-image">Enlarge image</li></ul><div id="product-description" class="hide-me"></div><span id="top"></span><span id="bottom"></span><span id="side"></span><span class="loading"></span></div>';
$('#products').after(productInfoBoxHtml);
}
function processListItems() {
$('#products > li')
.append('<span class="product-view">View</span>')
.filter(':even')
.addClass('even')
.end()
.each(function() {
products.push({
id: $(this).find('h3').html(),
title: $(this).find('h3').html(),
dimensions: $(this).find('.dimensions').html(),
description: $(this).find('.product-description').html()
});
})
.find('.product-view')
.click(function() {
var $thisListItem = $(this).parents('ul li');
var index = $('#products > li').index($thisListItem);
this.index = index;
showProduct();
});
};
function showProduct() {
var index = this.index;
console.log('INDEX = ' + index);
// hide current data
$('#product-info')
.show()
.find('.hide-me, #product-gallery')
.hide()
.parent()
.find('.loading')
.show();
// get data contained in the page
$('#product-info')
.find('h3')
.html(products[index].title)
.parent()
.find('#dimensions')
.html(products[index].dimensions)
.parent()
.find('#product-description')
.html(products[index].description)
// get id & then product extra info
var id = $('#products > li').eq(index).attr('id').replace(/id-/, '');
var downloadPath = PATH_BASE + 'downloads/';
var imagePath = PATH_BASE + 'images/products/'
$.getJSON(PATH_BASE + 'products/get/' + id + '/',
function(data){
var file = '';
var images = [];
file = data.file;
images = data.images;
// show file list item if there is a file
if (file) {
$('#spex-sheet').show().find('a').attr( { href: downloadPath + file } );
} else {
$('#spex-sheet').hide();
}
// image gallery
if (images.length != 0) {
$('#product-gallery').show();
// preload image thumbnails
$.each(images, function(i, image){
var img = new Image();
img.src = imagePath + 'thumb-' + image;
img = null;
});
// set first image thumbail and enlarge link
if (images[0]) {
$('#enlarge-image').show().find('a').attr({ href: imagePath + images[0] });
$('#product-gallery img').attr ( { src: imagePath + 'thumb-' + images[0]} )
}
console.log(images);
// setup gallery
var currentImage = 0;
clearInterval(cycle);
console.log(cycle);
var cycle = setInterval(function() {
console.log(currentImage + ' = ' + index);
if (currentImage == images.length - 1) {
currentImage = 0;
} else {
currentImage ++;
};
var obj = $('#product-gallery');
var imageSource = imagePath + 'thumb-' + images[currentImage];
obj.css('backgroundImage','url(' + imageSource +')');
obj.find('img').show().fadeOut(500, function() { $(this).attr({src: imageSource}) });
$('#enlarge-image a').attr({ href: imagePath + images[currentImage] });
}, 5000);
// setup lightbox
$("#enlarge-image a").slimbox({/* Put custom options here */}, null, function(el) {
return (this == el) || ((this.rel.length > 8) && (this.rel == el.rel));
});
} else {
// no images
$('#enlarge-image').hide();
$('#product-gallery').hide();
};
// show the product info
$('#product-info')
.find('.hide-me')
.remove('#product-gallery, #spex-sheet')
.show()
.parent()
.find('.loading')
.hide();
});
};
};
The important function is showProduct(). Now generally I don't write JS like this, but I decided to give it a go. My problem is, that when a user clicks a 'more' button, and it displays the prouduct, it doesn't reset the simple slideshow (the images var is reset, I think it has to do with the setInterval() maybe, or it seems it's making a new instance of showProduct() everytime).
Does anyone know what I'm doing wrong?

I had to reformat your code to really understand what was going on. Anyway, I found the problem with the code.
As you guessed correctly, problem is with the scope but not with the variable 'images' but with variable 'cycle'. Why?
This line
var cycle = setInterval(function() {
Always creates a new local cycle variable (notice the 'var') which is not accessible when showProduct gets called the second time. This means that this line
clearInterval(cycle);
is essentially useless as it always passes null to the clearInterval function and doesn't clear anything. This means that as you keep clicking on 'more', you are creating more and more setInterval function calls, never clearing the old ones.
Anyway, I have refactored your code a little bit, I think this should work as expected. The changes I did are:
Removed this.index variable. It's better to pass 'index' to showProduct instead of setting this.index before showProduct method call and making showProduct use that variable. Also, why did you prefix the variable with 'this'?
Declared cycler variable outside the scope of showProduct, local to the productDisplay method. This insures that you can access cycler during different showProduct calls.
Created smaller functions named showFile, showGallery, showProductInfo to make it easier to understand/maintain code.
Let me know if you have any questions OR if the code still doesn't work.
function productDisplay() {
//Instead of keeping this.index variable, it's better to make showProduct function
//take index variable.
products = [];
setupProductDisplay();
processListItems();
//We have to define cycler outside the showProduct function so that it's maintained
//in between showProduct calls.
var cycler = null;
showProduct(0);
function setupProductDisplay()
{
var productInfoBoxHtml = '<div id="product-info"><h3 class="hide-me"></h3><span id="dimensions" class="hide-me"></span><div id="product-gallery"><img alt="" src="" /></div><ul id="product-options" class="hide-me"><li id="spex-sheet">Download full spex sheet</li><li id="enlarge-image">Enlarge image</li></ul><div id="product-description" class="hide-me"></div><span id="top"></span><span id="bottom"></span><span id="side"></span><span class="loading"></span></div>';
$('#products').after(productInfoBoxHtml);
}
function processListItems()
{
$('#products > li')
.append('<span class="product-view">View</span>')
.filter(':even')
.addClass('even')
.end()
.each(
function()
{
products.push({
id: $(this).find('h3').html(),
title: $(this).find('h3').html(),
dimensions: $(this).find('.dimensions').html(),
description: $(this).find('.product-description').html()
});
})
.find('.product-view')
.click( function()
{
var $thisListItem = $(this).parents('ul li');
showProduct($('#products > li').index($thisListItem));
}
);
};
function showFile(file)
{
if (file)
{
$('#spex-sheet').show().find('a').attr( { href: downloadPath + file } );
}
else
{
$('#spex-sheet').hide();
}
}
function showGallery(images)
{
if(! images || !images.length || images.length == 0)
{
$('#enlarge-image').hide();
$('#product-gallery').hide();
return;
}
$('#product-gallery').show();
$.each(images,
function(i, image)
{
var img = new Image();
img.src = imagePath + 'thumb-' + image;
img = null;
});
// set first image thumbail and enlarge link
if (images[0])
{
$('#enlarge-image').show().find('a').attr({ href: imagePath + images[0] });
$('#product-gallery img').attr ( { src: imagePath + 'thumb-' + images[0]} )
}
var currentImage = 0;
clearInterval(cycler);
cycler = setInterval(
function()
{
currentImage = currentImage == images.length - 1 ? 0 : currentImage++;
var obj = $('#product-gallery');
var imageSource = imagePath + 'thumb-' + images[currentImage];
obj.css('backgroundImage','url(' + imageSource +')');
obj.find('img').show().fadeOut(500, function() { $(this).attr({src: imageSource}) });
$('#enlarge-image a').attr({ href: imagePath + images[currentImage] });
}, 5000);
$("#enlarge-image a").slimbox({/* Put custom options here */}, null, function(el) {
return (this == el) || ((this.rel.length > 8) && (this.rel == el.rel));
});
};
function showProductInfo()
{
$('#product-info')
.find('.hide-me')
.remove('#product-gallery, #spex-sheet')
.show()
.parent()
.find('.loading')
.hide();
}
function showProduct(index)
{
$('#product-info')
.show()
.find('.hide-me, #product-gallery')
.hide()
.parent()
.find('.loading')
.show();
// get data contained in the page
$('#product-info')
.find('h3')
.html(products[index].title)
.parent()
.find('#dimensions')
.html(products[index].dimensions)
.parent()
.find('#product-description')
.html(products[index].description)
// get id & then product extra info
var id = $('#products > li').eq(index).attr('id').replace(/id-/, '');
var downloadPath = PATH_BASE + 'downloads/';
var imagePath = PATH_BASE + 'images/products/'
$.getJSON(PATH_BASE + 'products/get/' + id + '/',
function(data)
{
showFile(data.file);
showGallery(data.image);
showProductInfo();
});
};
};

If you don't define your variables with var (e.g. var images = ...;) then they will be considered global variables (members of the window object).
If you define them with var then they are visible to the whole function (even before the variable is declared) they are declared in.
I can't immediately see what the problem is, but I would recommend minimizing the scope of your variables - if they don't need to be global then make sure they aren't global.

Related

How can I wait until all my images are loaded before adding them to html?

I am trying to get multiple images from an ajax source and load the on the page when they have all finished loading. The issue I was having that has caused me to try to find a solution was that some of the images weren't loading even though those images existed on the server.
I have tried to add code that now adds the image to an array
design_images.push({cid:designImg});
... and then when all the images have loaded will add that to the page, but I can't get that to work.
var counter = 0;
$(design_images).load(function() { // many or just one image(w) inside body or any other container
counter += 1;
}).each(function(key, value) {
this.complete && $(this).load();
console.log(value);
});
Nothing is outputted from the .each
This is the output of the array design_images
The value of design_images.length is 0 though.
Here is the complete function:
function matte_design_change_design_type(element)
{
var element_value = null;
var mattes_selected_type = get_mattes_selected_type();
matte_design_widths[mattes_selected_type] = [];
var mattes_selected_design = get_mattes_selected_design();
var count_matte_designs = 0;
var found = false;
$(document).ready(function()
{
$.ajax(
{
type: "GET",
url: SITE_URL + "/system/components/xml/" + mattes_selected_type,
dataType: 'xml',
success: function(xml)
{
var output = [];
var design_images = [];
$('component', xml).each(function(i, el)
{
matte_design_widths[mattes_selected_type][i] = 0;
count_matte_designs++;
var thumb = $("thumb", this).text(),
cid = $("cid", this).first().text(),
name = $("name", this).first().text().replace("Collage - ", ""),
alt = name,
description = $("description", this).first().text(),
if (parseInt(cid, 10) === mattes_selected_design)
{
found = true;
$("#matte_design_name").html(name);
$("#matte_design_description").html(description);
}
var designImg = new Image();
designImg.id = 'cid_' + cid;
designImg.alt = alt;
designImg.onclick = function() {
matte_design_change(cid, mattes_selected_type);
};
designImg.onload = function() {
output.push('<span class="matte_design_image_name" id="design_' + cid + '"><img id="cid_' + cid + '" />');
output.push('<br /><span class="matte_design_name" id="matte_design_name_' + mattes_selected_type + '_' + i + '">' + name + '</span></span>');
matte_design_increase_width(mattes_selected_type, this.width, i);
$('#matte_designs_strip_wrapper').html(output.join(''));
};
designImg.src = 'https://example.com/system/components/compimg/' + thumb + '/flashthumb';
});
var counter = 0;
var size = $('img').length;
$(design_images).load(function() {
counter += 1;
}).each(function(key, value) {
this.complete && $(this).load();
console.log(value);
});
}
});
});
}
I have tried waitForImages and imagesLoaded but I couldn't get them to work for me, but I'm not opposed to using either one.
Hide all images by default using CSS
img{
display: none;
}
Use Jquery to check if all loaded, then display images
JQuery
$(window).load(function(){
$('img').fadeIn(800); //or $('img').show('slow');
});

image link to an external page in JS

I have this js code
window.addEvent("domready", function () {
var maxLength = 700;
var counterFluid = 1;
var wallFluid = new Wall("wall", {
"width":180,
"height":180,
"rangex":[-14,21],
"rangey":[-8,12],
callOnUpdate: function (items) {
items.each(function (e, i) {
var a = new Element("img[src=images/"+counterFluid+".jpg]");
counterFluid++;
if( counterFluid > maxLength ) counterFluid = 1;
});
}
});
wallFluid.initWall();
});
This results in displaying images in an array. Now I would like to add links to images such that when the image is clicked it opens a new page.
I tried this code
a.onclick = function () {
window.location.href = '/"+counterFluid+".html';
};
A new page is opening but the url just shows as /"+counterFluid+".html
I know that this is not correct. Kindly help. The URL for each image will be different so trying to use +counterFluid+
Thank You
window.location.href = "/" + counterFluid + ".html";
Try it like this with only ".
You could use the id attribute of the image to store the 'counterFluid' var like this:
var img = document.createElement('img');
img.setAttribute('id', counterFluid);
img.setAttribute('src', 'images/' + counterFluid + '.jpg');
img.onclick = function() {
window.location.href = '/' + this.id + '.html';
}

cannot read property '0' of undefined JSON

I'm trying to make an image slider that changes the image 'displayMain' every few seconds. My problem is that when I call the displayMain function in setInterval, I continuously get a 'cannot read property 0 of undefined' error. Even when I use the hardcoded value of jsonData[i].name, I receive the same error. The value gets passed in displayThumbs just fine, however. Does anyone know why I can't retain the values in displayMain but can do so in displayThumbs?
window.addEventListener('load', function () {
var mainDiv = document.getElementById('main');
var descDiv = document.getElementById('main-description');
var gallery = document.querySelector('#main-img');
var ul = document.querySelector('ul');
var li;
var i = 0;
var displayThumbs;
var thumbName;
var current = 0;
var images = [];
function displayMain () {
var data = images[i];
gallery.src = 'img/' + data[0];
descDiv.innerHTML = '<h2>' + data[1] + '</h2>';
}
function displayThumbs () {
for (i = 0; i < images.length; i += 1) {
var data = jsonData[i].name.replace('.jpg', '_thumb.jpg');
// thumbnails use dom to make img tag
li = document.createElement('li');
thumbs[i] = document.createElement('img');
var createThumbNail = thumbs[i].src = 'img/' + data;
thumbs[i].setAttribute('alt', data);
thumbs[i].addEventListener('click', function() {
alert(createThumbNail);
});
ul.appendChild(thumbs[i]);
}
}
// success handler should be called
var getImages = function () {
// create the XHR object
xhr = new XMLHttpRequest();
// prepare the request
xhr.addEventListener('readystatechange', function () {
if (xhr.readyState === 4 && xhr.status == 200) {
// good request ...
jsonData = JSON.parse(xhr.responseText);
for (var i = 0; i < jsonData.length; i += 1) {
var data = [];
data.push(jsonData[i].name);
data.push(jsonData[i].description);
images.push(data);
}
displayMain();
displayThumbs();
setInterval(displayMain, 1000);
}
else {
// error
}
});
xhr.open('GET', 'data/imagedata.json', true);
xhr.send(null);
};
// setInterval(getImages, 2000);
getImages();
// displayThumbs();
});
Your problem is that your displayMain uses whatever value i is at the time, and i never gets incremented, so it'll be equal to images.length after the for loop in displayThumbs. displayThumbs increments it itself, so you won't ever go beyond the end of the array.
In your comment, you mentioned that you want to cycle through the images. This should work a bit better:
function displayMain () {
var data;
// wrap around to the first image
if (i >= images.length) {
i = 0;
}
data = images[i];
gallery.src = 'img/' + data[0];
descDiv.innerHTML = '<h2>' + data[1] + '</h2>';
i++;
}
Personally, I would use a private i, just in case another function reuses the same variable:
function displayMain () {
var data;
// wrap around to the first image
if (displayMain.i >= images.length || isNaN(displayMain.i)) {
displayMain.i = 0;
}
data = images[displayMain.i];
gallery.src = 'img/' + data[0];
descDiv.innerHTML = '<h2>' + data[1] + '</h2>';
// move to the next image
displayMain.i++;
}
This attaches a variable named i to the function displayMain. It will update this variable each time it is called, and no other function will use the same i variable.

Loop through array several times

I'm creating a horizontal string of thumbnails that scroll across the bottom of the page. These can be clicked and swapped for the main image. It works exactly as I want except that I want the list to be infinite - when the last image in the array appears it goes back to the first and runs through the array again so if someone were to watch long enough they would see the same images scroll by.
Here is what I have that works, but I seem to get lost trying to start it over again.
var bookImage = [];
bookImage[0] = "images/book1/IsseyFinal.jpg";
bookImage[1] = "images/book1/35web.jpg";
bookImage[2] = "images/book1/36web.jpg";
bookImage[3] = "images/book1/Oil.jpg";
bookImage[4] = "images/book1/3a.jpg";
bookImage[5] = "images/book1/LegsFinalCrop.jpg";
bookImage[6] = "images/book1/8a.jpg";
function swapEm() {
var lines = "";
$.each(bookImage, function (i, item) {
lines += "<img class=\"thumb\" src='" + item + "' height=\"90\"> ";
});
$("#grid_thumb").html(lines);
$('.thumbs img').click(function () {
var thmb = this;
var src = this.src;
$('.main img').fadeOut(400, function () {
this.src = thmb.src;
$(this).fadeIn(400)[0].src = src;
});
});
};
swapEm();
Here is where I scroll them ..
var thumbScroll;
var i = 1;
function myLoop () {
thumbScroll=setTimeout(function () {
con_left=(i*-.5);
$("#grid_thumb").css({"left": con_left});
i++;
if (i < 1000) {
myLoop();
}
}, 20)
}
myLoop();
Thank you for your help!
var counter=0;
$('.thumbs img').click(function () {
var thmb = this;
var src = this.src;
$('.main img').fadeOut(400, function () {
if(bookImage.length == counter)
{
counter=0;
}
this.src =bookImage[++counter];
$(this).fadeIn(400);
});
});

How to apply a javascript function to a multiple div Classes?

I have a function that creates a gallery of flickr sets pulled from my flickr account. I am getting the set numbers from a database and using a while loop to display the first image from the set. Each element of the table has the same class and i am applying a Javascript function to them. Unfortunately each table element is displaying the same photo, the last one pulled from the database.
$(document).ready(function() {
var flickrUrl="";
$('.gallery_table_data').each(function(){
flickrUrl = $(this).attr('title');
$('.flickr_div').flickrGallery({
"flickrSet" : flickrUrl,
"flickrKey" : "54498f94e844cb09c23a76808693730a"
});
});
});
and the images dont show up at all? can anyone help??
Here is the flickr jquery in case that's the problem:
var flickrhelpers = null;
(function(jQuery) {
jQuery.fn.flickrGallery = function(args) {
var $element = jQuery(this), // reference to the jQuery version of the current DOM element
element = this; // reference to the actual DOM element
// Public methods
var methods = {
init : function () {
// Extend the default options
settings = jQuery.extend({}, defaults, args);
// Make sure the api key and setID are passed
if (settings.flickrKey === null || settings.flickrSet === null) {
alert('You must pass an API key and a Flickr setID');
return;
}
// CSS jqfobject overflow for aspect ratio
element.css("overflow","hidden");
// Get the Flickr Set :)
$.getJSON("http://api.flickr.com/services/rest/?format=json&method=flickr.photosets.getPhotos&photoset_id=" + settings.flickrSet + "&api_key=" + settings.flickrKey + "&jsoncallback=?", function(flickrData){
var length = 1;
var thumbHTML = '';
for (i=0; i<length; i++) {
var photoURL = 'http://farm' + flickrData.photoset.photo[i].farm + '.' + 'static.flickr.com/' + flickrData.photoset.photo[i].server + '/' + flickrData.photoset.photo[i].id + '_' + flickrData.photoset.photo[i].secret +'.jpg'
settings.imgArray[i] = photoURL;
settings.titleArray[i] = flickrData.photoset.photo[i].title;
}
// Get the position of the element Flickr jqfobj will be loaded into
settings.x = element.offset().left;
settings.y = element.offset().top;
settings.c = settings.x + (element.width() / 2);
settings.ct = settings.y + (element.height() / 2);
// When data is set, load first image.
flickrhelpers.navImg(0);
});
}
}
// Helper functions here
flickrhelpers = {
navImg : function (index) {
// Set the global index
currentIndex = index;
// Create an image Obj with the URL from array
var thsImage = null;
thsImage = new Image();
thsImage.src = settings.imgArray[index];
// Set global imgObj to jQuery img Object
settings.fImg = $( thsImage );
// Display the image
element.html('');
element.html('<img class="thsImage" src=' + settings.imgArray[index] + ' border=0>');
// Call to function to take loader away once image is fully loaded
$(".thsImage").load(function() {
// Set the aspect ratio
var w = $(".thsImage").width();
var h = $(".thsImage").height();
if (w > h) {
var fRatio = w/h;
$(".thsImage").css("width",element.width());
$(".thsImage").css("height",Math.round(element.width() * (1/fRatio)));
} else {
var fRatio = h/w;
$(".thsImage").css("height",element.height());
$(".thsImage").css("width",Math.round(element.height() * (1/fRatio)));
}
if (element.outerHeight() > $(".thsImage").outerHeight()) {
var thisHalfImage = $(".thsImage").outerHeight()/2;
var thisTopOffset = (element.outerHeight()/2) - thisHalfImage;
$(".thsImage").css("margin-top",thisTopOffset+"px");
}
if (settings.titleArray[currentIndex] != "") {
$(".flickr_count").append(settings.titleArray[currentIndex]);
}
});
},
toggleUp : function() {
$("#flickr_thumbs").slideUp("slow");
}
}
// Hooray, defaults
var defaults = {
"flickrSet" : null,
"flickrKey" : null,
"x" : 0, // Object X
"y" : 0, // Object Y
"c" : 0, // Object center point
"ct" : 0, // Object center point from top
"mX" : 0, // Mouse X
"mY" : 0, // Mouse Y
"imgArray" : [], // Array to hold urls to flickr images
"titleArray" : [], // Array to hold image titles if they exist
"currentIndex" : 0, // Default image index
"fImg" : null, // For checking if the image jqfobject is loaded.
}
// For extending the defaults!
var settings = {}
// Init this thing
jQuery(document).ready(function () {
methods.init();
});
// Sort of like an init() but re-positions dynamic elements if browser resized.
$(window).resize(function() {
// Get the position of the element Flickr jqfobj will be loaded into
settings.x = element.offset().left;
settings.y = element.offset().top;
settings.c = settings.x + (element.width() / 2);
settings.ct = settings.y + (element.height() / 2);
});
}
})(jQuery);
The big problem is in your $.each loop. I am going to assume the plugin will work for all the elements you are looping over although have doubts that it will.
WHen you select $('.flickr_div') on each pass it affects all the elements in page with that class...so only the last pass of loop is valid
$(document).ready(function() {
var flickrUrl="";
$('.gallery_table_data').each(function(){
flickrUrl = $(this).attr('title');
/* this is your problem , is selecting all ".flickr_div" in page on each loop*/
//$('.flickr_div').flickrGallery({
/* without seeing your html structure am assuming
next class is inside "this"
try: */
$(this).find('.flickr_div').flickrGallery({
"flickrSet" : flickrUrl,
"flickrKey" : "54498f94e844cb09c23a76808693730a"
});
});
});
EDIT This same concept of using find() should also be refactoered into code within the plugin. Plugin should have all ID's replaced with classes.
Plugin really does not look well constructed for multiple instances within a page
I might be wrong here, but won't this (in your flickrGallery object)
$("body").append('<div id="flickr_loader"></div>');`
create multiple elements with the same ID? And the same for images in flickrhelpers:
element.html('<img id="thsImage" src=' + settings.imgArray[index] + ' border=0>');

Categories