Slideshow program, why is the last slide showing twice? - javascript

All my images are in the html, and it couldn’t be a simpler program.
I just cannot figure out where the repetition comes from:
$(document).ready(function() {
var imgArray = $("#slides img:first-child");
var newCaption;
var nextImg;
var SlideShow = function() {
$("#slide").fadeOut(1000, function() {
if (imgArray.length == 0) {
imgArray = $("#slides img:first-child");
} else {
imgArray = imgArray.next();
}
nextImg = imgArray.attr("src");
newCaption = imgArray.attr("alt");
$("#slide").attr("src", nextImg).fadeIn(1000);
$("#caption").text(newCaption).show(1000);
});
};
var stop = setInterval(SlideShow, 2000);
$("#slide").click(function() {
stop = setInterval();
});
});

The logic is wrong here:
if (imgArray.length == 0) {
imgArray = $("#slides img:first-child");
} else {
imgArray = imgArray.next();
}
Imagine, you’re at the last <img> inside the id="slides" element.
Is imgArray.length == 0?
Of course not.
You’re at the last one, so imgArray contains one element, so its length is 1.
The else part will now get executed and set imgArray to the .next sibling.
But there is no next sibling, because you just were at the last <img>.
So now, imgArray is an empty jQuery object.
No next element exists.
Then you go ahead and get .attr("src") of nothing.
Then you go ahead and get .attr("alt") of nothing.
Both of these result in undefined as documented.
Then, you .fadeIn(1000) the id="slide" element, but, weirdly, also set its src attribute.
What do you think this does?
An <img> cannot contain another <img>, so setting the src of the container that contains all <img>s is meaningless.
And once the setInterval is triggered again, the cycle repeats with a fadeOut.
And only now do you finally check if imgArray even exists, after wrongly assuming that it does, setting src and alt, and doing fade-out animations.
Only now do you set imgArray = $("#slides img:first-child");.
Rubber Duck Debug your code.
This means: go through the execution by hand and try to explain each step.
It repeats at the end, you say, so what happens if imgArray contains the last <img>?
What is the next statement that is executed?
Does it make sense?
Why do you think so?
Use the debugger built into your browser.
You can observe the imgArray expression, or the imgArray.length expression.
Do these expressions always produce the values you expect?
At which point do they no longer do that?
Use console.log debugging.
The single statement console.log({ imgArray, imgArrayLength: imgArray.length, imgArrayNext: imgArray.next() }) may already answer several questions; you can copy-paste it after any one of your statements.
So move the check:
var SlideShow = function(){
$("#slide").fadeOut(1000, function(){
imgArray = imgArray.next();
if (imgArray.length === 0) {
imgArray = $("#slides img:first-child");
}
// Etc.
});
};
Next problem: stop = setInterval();.
stop is never used, other than as an assignment target.
Reassigning some variable doesn’t affect previously assigned objects.
It doesn’t stop the interval.
Stopping the inveral is done with clearInterval(stop);.

Related

Javascript on click event not reading else statement or variables

I'm trying to make a click handler that calls a function; and that function gets a string and basically slices the last character and adds it to the front, and each time you click again it should add the last letter to the front.
It seem so easy at first that I thought I could just do it using array methods.
function scrollString() {
var defaultString = "Learning to Code Javascript Rocks!";
var clickCount = 0;
if (clickCount === 0) {
var stringArray = defaultString.split("");
var lastChar = stringArray.pop();
stringArray.unshift(lastChar);
var newString = stringArray.join('');
clickCount++;
} else {
var newArray = newString.split("");
var newLastChar = newArray.pop();
newArray.unshift(newLastChar);
var newerString = newArray.join("");
clickCount++;
}
document.getElementById('Result').innerHTML = (clickCount === 1) ? newString : newerString;
}
$('#button').on('click', scrollString);
Right now it only works the first time I click, and developer tools says newArray is undefined; also the clickCount stops incrementing. I do not know if it's an issue of scope, or should I take a whole different approach to the problem?
Every time you click you are actually reseting the string. Check the scope!
var str = "Learning to Code Javascript Rocks!";
var button = document.getElementById("button");
var output = document.getElementById("output");
output.innerHTML = str;
button.addEventListener("click", function(e){
str = str.charAt(str.length - 1) + str.substring(0, str.length - 1);
output.innerHTML = str;
});
button{
display: block;
margin: 25px 0;
}
<button id="button">Click Me!</button>
<label id="output"></label>
It is, in fact, a scoping issue. Your counter in inside the function, so each time the function is called, it gets set to 0. If you want a counter that is outside of the scope, and actually keeps a proper count, you will need to abstract it from the function.
If you want to keep it simple, even just moving clickCount above the function should work.
I do not know if it's an issue of scope
Yes, it is an issue of scope, more than one actually.
How?
As pointed out by #thesublimeobject, the counter is inside the function and hence gets reinitialized every time a click event occurs.
Even if you put the counter outside the function, you will still face another scope issue. In the else part of the function, you are manipulation a variable (newString) you initialized inside the if snippet. Since, the if snippet didn't run this time, it will throw the error undefined. (again a scope issue)
A fine approach would be:
take the counter and the defaultString outside the function. If the defaultString gets a value dynamically rather than what you showed in your code, extract its value on page load or any other event like change, etc. rather than passing it inside the function.
Do not assign a new string the result of your manipulation. Instead, assign it to defaultString. This way you probably won't need an if-else loop and a newLastChar to take care of newer results.
Manipulate the assignment to the element accordingly.
You can use Javascript closure functionality.
var scrollString = (function() {
var defaultString = "Learning to Code Javascript Rocks!";
return function() {
// convert the string into array, so that you can use the splice method
defaultString = defaultString.split('');
// get last element
var lastElm = defaultString.splice(defaultString.length - 1, defaultString.length)[0];
// insert last element at start
defaultString.splice(0, 0, lastElm);
// again join the string to make it string
defaultString = defaultString.join('');
document.getElementById('Result').innerHTML = defaultString;
return defaultString;
}
})();
Using this you don't need to declare any variable globally, or any counter element.
To understand Javascript Closures, please refer this:
http://www.w3schools.com/js/js_function_closures.asp

Loop to check last image in array

Thanks to a kind member I found a solution to a code problem of mine!
But I'm having struggle again with a loop.. I tried to find the solution in answered questions but nothing was working for me..
I want a loop to check if the last image in an array is loaded. I'm making a simple game where you click on a image, and it changes to the next image. The goal is that you have to click multiple times on the image, to get to the last image. If you are at the last image, you win. There needs to be a timer that checks after let's say 5 seconds, if you are at the last image. (I have multiple images that you have to click on, bet I'm showing just one right now)
So I made a for loop like this:
for (eiImg == 'img/ei4.png'){
alert("yay!");
}
It's probably very very wrong but I'm a very amateur programmer so I'm sorry! ;)
Well of course it wasn't working. And I am not even using a timer..
Can someone teach me how to successfully make a loop that checks after 5 seconds if the Image is the last image in the array. I've tried to google it but I just can't find the solution.
Here is my entire javascript code:
var eieren = 0;
var eieren = Math.floor((Math.random() * 4) + 1);
var imgArray = [ 'img/ei1.png' , 'img/ei2.png' , 'img/ei3.png', 'img/ei4.png' ];
imgArray.length;
var eiImg = imgArray[eieren - 1];
console.log( eiImg );
// thanks so much for the help Azzy Elvul!
document.getElementsByTagName( 'img' )[0].src = eiImg;
document.getElementsByTagName( 'img' )[0].addEventListener( "click", changeImage ());
var counter = eieren - 1;
function changeImage()
{
//Load new image if this is not the last image
if ( counter < imgArray.length - 1 )
{
document.getElementsByTagName( 'img' )[0].src = imgArray[ ++counter % imgArray.length];
}
};
Check this, its create alert of the last object from the array
var imgArray = [ 'img/ei1.png' , 'img/ei2.png' , 'img/ei3.png', 'img/ei4.png' ];
var alertIndex = imgArray.length-1; // the last index from the array
for(var i = 0; i<imgArray.length; i++) {
if(i == alertIndex) {
alert(imgArray[i]);
}
}
JSFiddle - look at this working example.
OK, so I was curious about this and about the solution you were working on and so I had a go at it myself just to see if I could get it to work. I think my solution is slightly different from your approach. The annotated code is below and there's a JSFiddle demonstration of the code at the end.
var imgArray = ['img/ei1.png', 'img/ei2.png', 'img/ei3.png', 'img/ei4.png'];
// get the body element and create an array to keep the image elements
var body = document.querySelector('body');
var imgs = [];
// loop over the list of images, creating new elements
// adding src attributes and click events to them, and then adding
// them to the imgs array we created
for (var i = 0, l = imgArray.length; i < l; i++) {
var img = document.createElement('img');
img.src = imgArray[i];
img.addEventListener('click', changeImage);
imgs.push(img);
}
function changeImage() {
// are there any image elements left in the img array
// if there are, continue
if (imgs.length > 0) {
// is the length of the img array less than the length
// of the imgArray - if so, remove the previous image from the page
if (imgArray.length !== imgs.length) {
body.removeChild(document.querySelector('img'));
}
// random number based on the number of image elements
// remaining in the imgs array
var random = Math.floor((Math.random() * imgs.length - 1) + 1);
// add that image to the body element
body.appendChild(imgs[random]);
// remove the image element from the imgs array
imgs.splice(random, 1);
}
}
function checkOnLastImage() {
// are you on the last image?
if (imgs.length === 0) {
console.log('congratulations')
} else {
console.log('try again');
}
}
// run changeImage for the first time
changeImage();
// use a setTimeout to check at 5 seconds whether
// the last image has been reached
setTimeout(checkOnLastImage, 5000);
DEMO
Why to iterate? Get the last object directly.
alert(arr[arr.length-1])

Simplifying a javascript function with repeated similar lines (with a loop?)

Okay, I hope you don't all facepalm when you see this - I'm still finding my way around javascript.
I am putting together an RSVP form for a wedding website.
I want the guests to be able to add their names to the RSVP form, but only have as many fields showing as required. To this end, after each name field, there is a link to click, which will, when clicked, show a name field for the next guest.
The code below works... but I am sure it can be tidier.
I have tried to insert a for() loop into the code in several different ways, I can see that the for() loop increments correctly to the last value - but when it does so, it leaves only the last addEventListener in place. I can only assume, that I should be using a different kind of loop - or a different approach entirely.
How should I tidy up the following?
<script>
function showNextGuest(i) {
document.getElementsByTagName(\'fieldset\')[i].style.display = \'block\';
}
function initiateShowNextGuest() {
document.getElementsByTagName('fieldset')[0].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(1);},false);
document.getElementsByTagName('fieldset')[1].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(2);},false);
document.getElementsByTagName('fieldset')[2].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(3);},false);
document.getElementsByTagName('fieldset')[3].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(4);},false);
document.getElementsByTagName('fieldset')[4].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(5);},false);
}
window.onload = initiateShowNextGuest();
</script>
Your intuition is right - a for loop could indeed simplify it and so could a query selector:
var fieldsSet = document.querySelectorAll("fieldset"); // get all the field sets
var fieldss = [].slice.call(asSet); // convert the html selection to a JS array.
fields.map(function(field){
return field.querySelector("a"); // get the first link for the field
}).forEach(function(link, i){
// bind the event with the right index.
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
This can be shortened to:
var links = document.querySelectorAll("fieldset a:first-of-type");
[].forEach.call(links, function(link, i){
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
function nextGuest () {
for(var i = 0; i < 5; i++){
document.getElementsByTagName('fieldset')[i]
.getElementsByTagName('a')[0]
.addEventListener('click',function(){
showNextGuest(parseInt(i + 1));
}, false);
}
}
Benjamin's answer above is the best given, so I have accepted it.
Nevertheless, for the sake of completeness, I wanted to show the (simpler, if less elegant) solution I used in the end, so that future readers can compare and contrast between the code in the question and the code below:
<script>
var initiateShowNextGuest = [];
function showNextGuest(j) {
document.getElementsByTagName('fieldset')[j].style.display = 'block';
}
function initiateShowNextGuestFunction(i) {
return function() {
var j = i + 1;
document.getElementsByTagName('fieldset')[i].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(j);},false);
};
}
function initiateShowNextGuests() {
for (var i = 0; i < 5; i++) {
initiateShowNextGuest[i] = initiateShowNextGuestFunction(i);
initiateShowNextGuest[i]();
}
}
window.onload = initiateShowNextGuests();
</script>
In summary, the function initiateShowNextGuests() loops through (and then executes) initiateShowNextGuestFunction(i) 5 times, setting up the 5 anonymous functions which are manually written out in the code in the original question, while avoiding the closure-loop problem.

Jquery: Doing an interval, timout, then interval again

I'm trying to do a reveal of a list of elements 1 after another in a setInterval for 5 seconds but after the 7th element, i want to wait for 60 seconds, meaning a timeout and continue the interval.
Following is my code, I can get it done but the problem with this code is that it does the timeout repetatively on every 7th element, however, i'm only looking to do it on the 1st 7th occurance and not on all the 7th element.
$(document).ready(function(){
var index=0;
var firstInterval=setInterval(function(){
if(index==7){
$("#love p:hidden:first").fadeIn(2000);
clearInterval(firstInterval);
index=0;
return;
}else{
$("#love p:hidden:first").fadeIn(2000);
index++;
}
var timeout=setTimeout(function(){
$("#love p:hidden:first").fadeIn(2000);
var secondInterval=setInterval(function(){
$("#love p.2nd_batch:hidden:first").fadeIn(2000);
},5000);
clearTimeout(timeout);
},60000);
},5000);
});
Any help would be greatly appreciated. thanks
Does this work for you? http://jsfiddle.net/301hsmom/3/ (watch the bottom right)
$(document).ready(function() {
var index = 1;
var firstInterval = setInterval(function() {
if (index === 7) {
$("#love p").filter(":hidden").filter(":first").fadeIn(2000);
clearInterval(firstInterval);
index = 1;
var timeout = setTimeout(function() {
$("#love p:hidden:first").fadeIn(2000);
var secondInterval = setInterval(function() {
var elem = $("#love p").filter(":hidden");
if (elem) {
elem.filter(":first").fadeIn(2000);
} else {
clearInterval(secondInterval);
}
}, 5000);
}, 60000);
} else {
$("#love p").filter(":hidden").filter(":first").fadeIn(2000);
index++;
}
}, 5000);
});
First, your var index = 0; and looping until index === 7 will run 8 times, rather than the intended 7. To fix this, change one of them to var index = 1; or index === 6.
Secondly, you create a new timeout every 5 seconds within the firstInterval code. You only want this to happen once the first 7 elements have been shown. This is fixed by moving it inside the if (index === 7) { statement so it will execute once 7 elements have been shown.
Another optimization I have applied is changing $("#love p:hidden:first") to $("#love p").filter(":hidden").filter(":first"). According to http://api.jquery.com/first-selector/:
Because :first is a jQuery extension and not part of the CSS specification, queries using :first cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method. To achieve the best performance when using :first to select elements, first select the elements using a pure CSS selector, then use .filter(":first").
Changing :hidden is for the same reason. http://api.jquery.com/hidden-selector/.
Now everything works up to the first seven elements (assuming you have at least 7 elements, otherwise you will need to check for the existence of the element in the else within firstIndex). Next, we remove clearTimeout(timeout); as timeout's only run once and do not need to be cleared.
The next thing we do, is because we do not know how many elements there are to display after the first 7, we try to find a hidden element (with var elem = $("#love p").filter(":hidden");) and check for it's existence (using if (elem)). If it exists, we get the first one, and fade it in (elem.filter(":first").fadeIn(2000);), otherwise, we stop looping (clearInterval(secondInterval);).

If Statement and Function (Basic) keep track on divs innerHTML?

Ok so I have a function set up already and it gets added in the div as numbers and letters
"var elCamLocation = $( "TEST" )"
Right!
Now I want to use a function to keep track on what is in the innerHTML of the "TEST" div and if the contents of the div changes to "0.00x2.00z0.00" then I want it to do something for example lets just say change url to keep it simple.
Here is what I have..
var Numbs = getElementById("TEST").innerHTML;
function TE()
{
if(Numbs = "0.00x2.00z0.00")
{
window.location.assign("http://google.com")
}
};
But it isn't working the window.location.assign("http://google.com")isn't triggering at all
any idea's?
= is for assignment. You should test for equality using the === operator.
if(Numbs === "0.00x2.00z0.00")
{
//etc.
}

Categories

Resources