setInterval works only the first time - javascript

I am trying to use setTimer to animate a slide show using straightforward jQuery. I provide the user with a button (in the form of a "DIV" with a button background image) that he clicks to start the show and which then turns into a pause button. The slides are supposed to change every 3 seconds. Here is the relevant code:
playLink = $('<div id="lbPlayLink" />').click(function(){
$(playLink).hide();
$(pauseLink).show();
slideInterval = setInterval(function(){next()}, 3000)
})[0];
pauseLink = $('<div id="lbPauseLink" />').click(function(){
$(playLink).show();
$(pauseLink).hide();
clearInterval(slideInterval);
}).hide()[0];
The next() function call does the work of replacing the slide with the next one. I have checked out this function and it works perfectly if I call it directly (synchronously), however, when it gets called asynchronously by the setInterval, it works fine the first time (3 seconds after I click on the button), but is never called again, even though it should be called 3 seconds later. I know it's never called as I put an "alert" call at the beginning and end of the function.
If I replace the next() call in the setInterval with alert('test') then I can see the setInterval is doing what it is supposed to. I can't for the life of me see why alert() is OK but next() isn't, unless it has something to do with "scope" of functions, but in that case why does it work the first time?
I've tried debugging the code with firebug, but it can't really help with timeout functions like this. Neither Firefox nor IE8 show any error messages.
I've looked through the various posts here and elsewhere on setInterval, but can't see anything relevant that I haven't tried already. I've been experimenting now for about 3 hours and it's doing my head in. Can anyone suggest what I can try next?

Hmm, I don't like the way you wrote the code, it is not very readable, I would rather suggest something like the following (not tested yet, not sure if it works):
The CSS:
#slides{
/* So you can position your elements under the div#slides */
position: relative;
}
.button {
width: 50px;
height: 10px;
/* So you can position your button anywhere you like */
position: absolute;
bottom: 10px;
right: 10px;
}
.play {
background:url(..) no-repeat;
}
.pause {
background:url(..) no-repeat;
}
The HTML consist of the parent slides holding everything relating to slides, and the controller, basically holds your button image.
<div id="slides">
<div id="controller" class="button play"></div>
</div>
The code:
(function() {
//Let's wrap everything in an anonymous function, so to avoid variable confusion
function next() {
//Assume this is your code doing the sliding. I don't touch
}
var invt;
function play() {
//Always clear interval first before play
if (invt) clearInterval(invt);
invt = setInterval(function() {
next();
}, 3000);
}
function pause() {
if (invt) clearInterval(invt);
}
$(document).ready(function() {
$('#controller').click(function() {
//It's not playing so it has the play class
if ($(this).hasClass('play')) {
$(this).removeClass('play').addClass('pause');
pause();
}else{
$(this).removeClass('pause').addClass('play');
play();
}
});
});
})();

change this line on both:
$('<div id="lbPlayLink" />').click(function(){
to:
$('<div id="lbPlayLink" />').live("click", function(){

To clarify how I solved the problem:
I took onboard Ivo's suggestion to have a single button (though in the form of a "DIV" element rather than a "BUTTON") and change the class of this button between "lbPlay" and "lbPause" to change the background image and trigger the correct action.
My main problem was that I was unknowingly setting the event on this button multiple times and I believe this is what was causing the strange behaviour.
I got round it by putting the return code from the "setInterval" call into a variable ("ppHandler") attached to the DIV, using the jQuery "data" method.
The new code is:
if (typeof($(playPauseLink).data('ppHandler')) == 'undefined'){
$(playPauseLink).click(function(){
var $this = $(this);
if ($this.hasClass('lbPlay')) {
if ($this.data('ppHandler') != -1) clearInterval($this.data('ppHandler'));
$this.data('ppHandler', setInterval(function(){next();}, slideInterval*1000));
$this.removeClass('lbPlay').addClass('lbPause');
} else {
if ($this.data('ppHandler') != -1) clearInterval($this.data('ppHandler'));
$this.data({ppHandler: -1});
$this.removeClass('lbPause').addClass('lbPlay');
}
});
Thanks to all who responded. To see the final result go to "www.trips.elusien.co.uk" and click on the "slimbox examples" link and go to "example 9".

Related

unable to remove class added by addClass on hover

i have this simple jquery which adds hide_share class after 10 seconds and it works flawlessely but i want to remove that class when users hovers over that.
.hide_share {
opacity :0.1;
}
this function adds that hide_share class
setTimeout(function() {$("#main_id").addClass("hide_share");}, 10000);
but when users brings cursor i want to remove hide_share class so that it is again visible
seems very simple but wasted my several hours then i searched whole stackoverflow and tried all ideas given by experts but none worked
here is my remove function
$("#main_id").hover(function () {$(this).removeClass("hide_share");} );
when i run same thing in console it works and i can see that class is removed but not directly.
i have tried doing everything even added script tag in head but that also did not work and removed that.
i tried child parameter and everythig,
here is html
<div id="main_id" class="button-position-left hide_share"><div class="button-label"><span>Share</span></div><div class="button2">Img_here</div></div>
can anyone tell where i am doing mistake. i have used similar code and it used to work flawlessely.
Maybe you're looking for this:
$("#main_id").hover(function () {
$(this).removeClass("hide_share");
}, function () {
$(this).addClass("hide_share");
});
setTimeout(function() {$("#main_id").addClass("hide_share");}, 10000);
.hide_share {
opacity :0.1;
}
#main_id {
width: 100px;
height: 100px;
background-color: cyan;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="main_id" class="button-position-left"></div>
.hover() method supports 2 parameters: handlerIn and handlerOut. You can set event when user leaves the cusor as the second parameter.

delay adding class to elements with setTimeout?

So I have a bunch of div elements that I want to add a class to, so it trigger a transition -- but I want the class to be added 1 at a time, not simultaneously so the animation appears 1 at a time.
This is how my javascript looks like.
var campaignInfo = document.querySelectorAll(".campaign-info-container");
setTimeout(function(){
for(i=0; i<=campaignInfo.length;i++){
campaignInfo[i].classList.add("campaign-container-slide");
console.log(campaignInfo[i]);
};
}, 2000);
so basically I use querySelectorAll so it captures all the divs with that class, which shows up when I console.log() it.
But regardless the classes are not added with a delay.. I'm not sure how I can add an iteration to the milliseconds 2000 * i if i is only defined inside the loops. I tried creating a var counter; outside the setTimeout, and increment it inside the loop with counter++, but the delay still won't work.
Also, I'm getting this error "Can't read property classList of undefined.
Thanks guys. Oh and please no jQuery :)
EDIT:
Thanks for the solution! :)
So does the setInterval work like a loop in your case?
Because I want this interval to occur whenever I click on a button, but removed when I exit out of the frame. So I added the below to remove the class from the elements. But I use a for loop instead..
for(i=0; i<=campaignInfo.length; i++){
campaignInfo[i].classList.remove("campaign-container-slide");
}
and this works, but I'm just curious how come my first initial code (not this one above) doesn't work? how come using a counter works but not looping through it?
If I understand you correctly, you could use nested setTimeout calls, or use setInterval:
var campaignInfo = document.querySelectorAll(".campaign-info-container");
var index = 0; // The index of the next `campaignInfo` div to update
var interval = setInterval(function() { // Create a new interval that fires every 500ms
campaignInfo[index++].classList.add("campaign-container-slide");
if (index === campaignInfo.length) { // The last div has been reached, so...
clearInterval(interval); // Clear the interval using the reference
}
}, 500);
body { font-size: 2em;}
.campaign-info-container { background: #555; }
.campaign-container-slide { background: #eee; }
<div class="campaign-info-container">One</div>
<div class="campaign-info-container">Two</div>
<div class="campaign-info-container">Three</div>
<div class="campaign-info-container">Four</div>

Javascript anonymous functions sync

Simply put I'm trying to sync two slideshows created using widgetkit lib in a joomla website, eg. when user clicks next slide on one, the other one also runs nextSlide() function in the slideshow.js. Same for previous. The problems I'm having is widgetkit uses anonymous functions for creating those slideshows and I dont have global references to them after they are created. With my limited programming knowledge I cant seem to trigger the nextSlide function for other slideshows once inside click handler.
If anyone can take a look it would be most welcome.
EDIT:
Of course I forgot to link the example webpage
http://www.yootheme.com/widgetkit/examples/slideshow
Mine is similar with only 2 slideshows, but is still only on local server.
Taking a brief look at widgetkit here is one possible solution. Using jquery you can search for any objects that have a class of slides with a child of next and click all others. The code provided below isn't tested but should point you in the right direction. As long as you don't call stop propagation or prevent default then the original click handlers should still fire.
var slideshow_count = $('.slides .next').length;
var cascade_countdown = 0;
$('.slides .next').each(function() {
$(this).click(function() {
// stop an infinite loop if we're already cascading till we've done it for all the elements.
if(cascade_countdown != 0) {
cascade_countdown--;
return true;
}
// we don't include the slideshow we're clicking in this count
cascade_countdown = slideshow_count - 1;
var clicked_el = this;
$('.slides .next').each(function() {
// only click elements that aren't the initiator
if(this !== clicked_el) {
$(this).click();
}
});
});
});

jQuery: prevent animation and timeout queuing?

I have probably a simple problem to solve but I don't know what's the right approach on that.
I have a div.notification bar on top of my website that is hidden by default. The bar does get an additional class of either success or warning that sets it to display:block; if needed.
So, there are two cases. Either an output message is rendered directly to the .notification bar on page-load (and it gets a class of success or warning right with it) OR the .notifcation bar is sliding down from top and is fed with json data.
Any way, the notification bar should always be visible for 10 seconds and than slide back up.
Therefore I wrote two functions that handle this bar.
var timing = 10000;
function notificationOutput(type, message) {
var note = $('.notification');
note.hide();
note.find('.message').html(message);
note.stop(true,true).slideDown().delay(timing).slideUp();
}
function notificationSlideUp(slideUp) {
var note = $('.notification');
if ( slideUp ) note.delay(timing).slideUp();
else note.stop(true,true).slideUp();
}
So the notificationOutput() function is triggered from various other functions that return json data and render that into the box.
And the notificationSlideUp() function is right now called on every page load because in my header I have this …
<script type="text/javascript">
$(document).ready(function(){
notificationSlideUp(true);
});
</script>
And there is a reason for that! Remember the case where the .notification is directly set to visible on page-load … e.g. when a user logs-in on my platform the .notification bar is immediately visibile and says "Welcome Username, you've successfully logged in".
I call the notifictaionSlideUp() function in my header to make sure the currently visible .notification bar will slideUp() again after 10 seconds.
And here occurs the problem … 
Somehow this causes the entire notifcation timing and sliding up and down to be "confused". So there must happen some queuing of the slide-functions and or of the delay() function, because without the note.stop(true,true) in the notificationOutput() function the notification wouldn't slideDown() immediately if it's triggered within the first 10 seconds after the page load. That is because the notificationSlideUp() function has already triggered the slideUp() and the delay() of the object.
And the same happens to the delay. If a .notification is put out within the first 10 seconds the delay timing isn't right because the delay counter already started on page-load.
Any idea how to solve that so that it always works?
Update:
var notificationTimer;
function notificationOutput(type, message) {
var note = $('.notification');
note.hide();
note.find('.message').html(message);
note.stop(true,true).slideDown();
clearTimeout(notificationTimer);
notificationTimer = setTimeout(function() {
note.stop(true,true).slideUp();
}, 10000);
}
function notificationSlideUp(slideUp) {
var note = $('.notification');
if ( slideUp ) {
clearTimeout(notificationTimer);
notificationTimer = setTimeout(function() {
note.stop(true,true).slideUp();
}, 10000);
} else {
note.stop(true,true).slideUp();
}
}
Unfortunately, jQuery's delay() function doesn't offer any method for canceling a delay the way setTimeout() does with clearTimeout(). I'd suggest replacing your delay()s with named setTimeout()s, and writing a condition to clearTimeout() for cases in which you need to cancel/trigger a queued animation right away.
http://api.jquery.com/delay/
The .delay() method is best for delaying between queued jQuery
effects. Because it is limited—it doesn't, for example, offer a way to
cancel the delay—.delay() is not a replacement for JavaScript's native
setTimeout function, which may be more appropriate for certain use
cases.
But you can use the following 'hint' - replace delay with some animation which does not change anything. For example with .animate({opacity:1},timing)

jQuery selector :not(:animated)

We're trying to make sure our JavaScript menu, which loads content, doesn't get overrun with commands before the content in question loads and is unfurled via .show('blind', 500), because then the animations run many times over, and it doesn't look so great. So I've got about six selectors that look like this:
("#center_content:not(:animated)")
And it doesn't seem to be having any effect. Trying only :animated has the expected effect (it never works, because it doesn't start animated), and trying :not(div) also has this effect (because #center_content is a div). For some reason, :not(:animated) seems not to be changing the results, because even when I trigger the selector while the div in question is visibly animated, the code runs. I know I've had success with this sort of thing before, but the difference here eludes me.
$("#center_content:not(:animated)").hide("blind", 500, function () {
var selector_str = 'button[value="' + url + '"]';
//alert(selector_str);
var button = $(selector_str);
//inspectProperties(button);
$("#center_content:not(:animated)").load(url, CenterContentCallback);
if (button) {
$("#navigation .active").removeClass("active");
button.addClass("active");
LoadSubNav(button);
}
});
I hope this provides sufficient context. I feel like the second selector is overkill (since it would only be run if the first selector succeeded), but I don't see how that would cause it to behave in this way.
Here's the snippet that seemed to be working in the other context:
function clearMenus(callback) {
$('[id$="_wrapper"]:visible:not(:animated)').hide("blind", 500, function() {
$('[id^="edit_"]:visible:not(:animated)').hide("slide", 200, function() {
callback();
});
});
}
Here, the animations queue instead of interrupt each other, but it occurs to me that the selector still doesn't seem to be working - the animations and associated loading events shouldn't be running at all, because the selectors should fail. While the queueing is nice behavior for animations to display, it made me realize that I seem to have never gotten this selector to work. Am I missing something?
Sometimes it's helpful to use .stop() and stop the current animation before you start the new animation.
$("#center_content").stop().hide("blind", 500, function () {});
Really depends on how it behaves within your environment. Remember that .stop() will stop the animation as it was (eg. halfway through hiding or fading)
I don't know if I understand it correctly, but if you want to make sure the user doesn't trigger the menu animation again while it's currently animating(causing it to queue animations and look retarded, this works and should help. I use an if-statement. And before any mouseover/off animation I add .stop(false, true).
$('whatever').click(function(){
//if center_content is not currently animated, do this:
if ($("#center_content").not(":animated")) {
$(this).hide(etc. etc. etc.)
}
//else if center_content IS currently animated, do nothing.
else {
return false;}
});
another example i found elsewhere:
if($("#someElement").is(":animated")) {
...
}
if($("#someElement:animated").length) {
...
}
// etc
then you can do:
$("#showBtn").attr("disabled", $("#someElement").is(":animated"));

Categories

Resources