Loop through array several times - javascript

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

Related

loading an unknown number of images

I'm trying to create a lightbox for my site, and I want it to load all the images from a given directory with a filename like image#.jpg.
This is the code I have:
for(var i=0; i<1000; i++)
{
var filename = "images/image"+i+".jpg";
$.get(filename)
.done(function() {
$('#lightbox').append('<img src="placeholder.gif">');
})
.fail(function() {
i=1000; //ugh
});
}
It kind of works, but only tries to load image1000.jpg.
Also, is there a better way to do something like this? I'm sure saying 'do this a ton of times and stop when I manually change the for loop counter' is frowned on.
If your image names are sequential like your said, you can create a loop for the names, checking at every iteration if image exists - and if it doesn't - break the loop:
var bCheckEnabled = true;
var bFinishCheck = false;
var img;
var imgArray = new Array();
var i = 0;
var myInterval = setInterval(loadImage, 1);
function loadImage() {
if (bFinishCheck) {
clearInterval(myInterval);
alert('Loaded ' + i + ' image(s)!)');
return;
}
if (bCheckEnabled) {
bCheckEnabled = false;
img = new Image();
img.onload = fExists;
img.onerror = fDoesntExist;
img.src = 'images/myFolder/' + i + '.png';
}
}
function fExists() {
imgArray.push(img);
i++;
bCheckEnabled = true;
}

jquery html(array) doesn't insert all items in array

When I run the javascript code below, it load specified amount of images from Flickr.
By var photos = photoGroup.getPhotos(10) code, I get 10 images from cache.
Then, I can see the object has exactly 10 items by checking console.log(photos);
But actual image appeared on the page is less than 10 items...
I have no idea why this work this way..
Thank you in advance.
<html>
<head>
<script src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
<script>
var PhotoGroup = function(nativePhotos, callback) {
var _cache = new Array();
var numberOfPhotosLoaded = 0;
var containerWidth = $("#contents").css('max-width');
var containerHeight = $("#contents").css('max-height');
$(nativePhotos).each(function(key, photo) {
$("<img src='"+"http://farm" + photo["farm"] + ".staticflickr.com/" + photo["server"] + "/" + photo["id"] + "_" + photo["secret"] + "_b.jpg"+"'/>")
.attr("alt", photo['title'])
.attr("data-cycle-title", photo['ownername'])
.load(function() {
if(this.naturalWidth >= this.naturalHeight) {
$(this).attr("width", containerWidth);
} else {
$(this).attr("height", containerHeight);
}
_cache.push(this);
if(nativePhotos.length == ++numberOfPhotosLoaded)
callback();
})
});
var getRandom = function(max) {
return Math.floor((Math.random()*max)+1);
}
this.getPhotos = function(numberOfPhotos) {
var photoPool = new Array();
var maxRandomNumber = _cache.length-1;
while(photoPool.length != numberOfPhotos) {
var index = getRandom(maxRandomNumber);
if($.inArray(_cache[index], photoPool))
photoPool.push(_cache[index]);
}
return photoPool;
}
}
var Contents = function() {
var self = this;
var contentTypes = ["#slideShowWrapper", "#video"];
var switchTo = function(nameOfContent) {
$(contentTypes).each(function(contentType) {
$(contentType).hide();
});
switch(nameOfContent) {
case("EHTV") :
$("#video").show();
break;
case("slideShow") :
$("#slideShowWrapper").show();
break;
default :
break;
}
}
this.startEHTV = function() {
switchTo("EHTV");
document._video = document.getElementById("video");
document._video.addEventListener("loadstart", function() {
document._video.playbackRate = 0.3;
}, false);
document._video.addEventListener("ended", startSlideShow, false);
document._video.play();
}
this.startSlideShow = function() {
switchTo("slideShow");
var photos = photoGroup.getPhotos(10)
console.log(photos);
$('#slideShow').html(photos);
}
var api_key = '6242dcd053cd0ad8d791edd975217606';
var group_id = '2359176#N25';
var flickerAPI = 'http://api.flickr.com/services/rest/?jsoncallback=?';
var photoGroup;
$.getJSON(flickerAPI, {
api_key: api_key,
group_id: group_id,
format: "json",
method: "flickr.groups.pools.getPhotos",
}).done(function(data) {
photoGroup = new PhotoGroup(data['photos']['photo'], self.startSlideShow);
});
}
var contents = new Contents();
</script>
</head>
<body>
<div id="slideShow"></div>
</body>
</html>
I fix your method getRandom() according to this article, and completely re-write method getPhotos():
this.getPhotos = function(numberOfPhotos) {
var available = _cache.length;
if (numberOfPhotos >= available) {
// just clone existing array
return _cache.slice(0);
}
var result = [];
var indices = [];
while (result.length != numberOfPhotos) {
var r = getRandom(available);
if ($.inArray(r, indices) == -1) {
indices.push(r);
result.push(_cache[r]);
}
}
return result;
}
Check full solution here: http://jsfiddle.net/JtDzZ/
But this method still slow, because loop may be quite long to execute due to same random numbers occurred.
If you care about performance, you need to create other stable solution. For ex., randomize only first index of your images sequence.

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.

Detect img src and then change it with javascript

This is my code:
var i;
var pic = document.getElementById('image');
var picSrc = pic.src;
var fullSrc = picSrc.split('h.jpg')[0] + '.jpg';
pic.src = fullSrc;
document.getElementById('next').onmousedown = function () {
i = 0;
// it works up to here
pic.addEventListener("DOMAttrModified", function(event) {
if (i == 0 && event.attrName == "src") {
pic = document.getElementById('image');
i = 1; // this is to prevent endless loop
picSrc = pic.src;
fullSrc = picSrc.split('h.jpg')[0] + '.jpg';
pic.src = fullSrc;
}
});
return true;
};
It should work on imgur's horizontal layout albums, and replace the low-res images with full-res ones, one image at a time (currently displayed image).
On click of the "next" button, a new image is displayed. However, the script does not load the next full-res image. It only works with the first image loaded.
You're messing up your scope completely, invalidating the entire code after the first run. This should also pop up more than enough errors in your console. Reshuffle assignments to the right spot:
document.getElementById('next').onmousedown = function () {
var i;
var pic = document.getElementById('image');
var picSrc = pic.src;
var fullSrc = picSrc.split('h.jpg')[0] + '.jpg';
pic.src = fullSrc;
pic.addEventListener("DOMAttrModified", function(event) {
if (i == 0 && event.attrName == "src") {
pic = document.getElementById('image');
i = 1; // this is to prevent endless loop
picSrc = pic.src;
fullSrc = picSrc.split('h.jpg')[0] + '.jpg';
pic.src = fullSrc;
}
});
return true;
};
Depending on how the page works specifically (I can't see without having a real world use case) you might have to reassign the entire mousedown event as well.
On every mousedown you are adding a new DOMAttrModified event listener.
Try arranging your code to something like following:
var pic = document.getElementById('image');
var i;
pic.addEventListener("DOMAttrModified", function(event) {
if (i == 0 && event.attrName == "src") {
//pic = document.getElementById('image');
i = 1; // this is to prevent endless loop
picSrc = pic.src;
fullSrc = picSrc.split('h.jpg')[0] + '.jpg';
pic.src = fullSrc;
i = 0;
});
document.getElementById('next').onmousedown = function () {
var picSrc = pic.src;
var fullSrc = picSrc.split('h.jpg')[0] + '.jpg';
pic.src = fullSrc;
});
You should also try using the addEventListener instead of onmousedown

Variable scope issue in 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.

Categories