Not using correctly javascript setTimeout / clearTimeout? - javascript

I have problem with simple javascript and some ajax.
I have link that calls javascript function like this:
<div id="Button11" onmouseover="changeContent4()">Actions</div>
Javascript function that is called above is like this:
function changeContent4()
{
BubbleOn()
document.getElementById("text1").innerHTML='Some text here';
clearTimeout(BOffi);
var BOffi = setTimeout(BubbleOff, 20000);
}
This works, it runs BubbleOn function, places text to element text1, most likely it empties BOffi timeout and sets new timeout 20000ms for it.
Here is BubbleOn:
function BubbleOn() {
$("#bubble").fadeIn(function() {
})
}
And here is BubbleOff:
function BubbleOff() {
$("#bubble").fadeOut(function() {
})
}
As in functions BubbleOn and BubbleOff works. They just hide or show div named bubble which contains text1 element. When BOffi goes timeout it just runs the BubbleOff function. This works fine. The problem is that when BubbleOff has been run and mouse is placed immediately over link that runs changeContent4(), It does make the bubble div visible again and places text there again but then bubble div fades out inside a second! Not after 20000ms. After this if the mouse is placed again to run changeContent4() everything works great. If there is about millisecond longer time than a second between the bubble fadeout and placing the mouse over changeContent4() launcher it works and waits 20000ms. Less than a second and bubble is shown about second...
What can cause this? Could it be that fadeOut is still running even the bubble is vanished from the screen and therefore it does not reset the BOffi counter? Which could have 1 second or less time left and then runs BubbleOff again after that magical second?

Two ideas to try:
put "clearTimeout(BOffi);" at the top of the function before "BubbleOn();".
declare BOffi as a global variable.
So:
var BOffi;
function changeContent4()
{
clearTimeout(BOffi);
BubbleOn();
document.getElementById("text1").innerHTML='Some text here';
BOffi = setTimeout(BubbleOff, 20000);
}
or you can use window.BOffi instead.

At a first glance I notice this var BOffi = setTimeout(BubbleOff, 20000);. This creates a local variable. After the function is executed, is lost. Second time function is called Boffi is some random residual value.
Make it global and you should be ok (remove var).

function BubbleOn() {
$("#bubble").fadeIn(function(){},1000)
}
function BubbleOff() {
$("#bubble").fadeOut(function() {},1000)
}
You should set duration for both fadeIn and fadeOut functions.

Because the animations are queued but your script keep running, try this one :
function changeContent4()
{
bubble(function(){
document.getElementById("text1").innerHTML='Some text here';
});
}
var fadeTimeout = null;
function bubble(callback) {
if(fadeTimeout==null)
$("#bubble").fadeIn(1000, function(){
if($.isFunction(callback))
callback();
fadeTimeout = setTimeout(bubbleOff, 20000);
});
}
function bubbleOff() {
$("#bubble").fadeOut(1000, function(){
fadeTimeout =null;
});
}
Fiddle here
You might wanna move the callback() call before the fadeIn as you are modifying the text inside the bubble but that exemple is just to let you see the order of every changes
EDIT : now allowing multiple call
var i = 0;
function changeContent4() {
bubble(function () {
$("#text1").text('Some text here ' + (i++));
});
}
var fadeTimeout = null;
function bubble(callback) {
if ($.isFunction(callback)) callback();
if (fadeTimeout == null) {
$("#bubble").fadeIn(1000, function () {
fadeTimeout = setTimeout(bubbleOff, 20000);
});
} else {
clearTimeout(fadeTimeout);
fadeTimeout = setTimeout(bubbleOff, 20000);
}
}
function bubbleOff() {
$("#bubble").fadeOut(1000, function () {
fadeTimeout = null;
});
}
FIDDLE

Related

Debounce for 500ms if True else execute

Spoiler; I'm completely new to jQuery/Javascript.
I have a boolean field CheckMe and an input field textField.
If textField is empty, CheckMe should not be shown else it should (this means if goes from not-empty to empty CheckMe should be hidden right away again). I want to parse a delay, say 500 ms, i.e CheckMe is shown if text is not empty and after 500 ms of the last keypress
I have tried using the debounce function from this SO answer (see my implementation below), but the problem is, CheckMe is also first hidden after 500 ms of textField being empty
<script type="text/javascript">
function debounce(fn, duration) {
var timer;
return function(){
clearTimeout(timer);
timer = setTimeout(fn, duration);
}
}
$(document).ready(function () {
const textField= $("#textField");
const CheckMe= $("#CheckMe");
CheckMe.hide();
textField.on("input", debounce(()=> {
if (textField.val()) {
CheckMe.show();
} else {
CheckMe.hide();
}
},500));
});
</script>
but that removes checkMe after 500 ms when I clear textField.
I have tried moving the debounce into the True statement i.e
...
textField.on("input", function() {
if (textField.val()) {
debounce(function(){CheckMe.show();},500)
} else {
CheckMe.hide();
}
}
but that does not show CheckMe at any point
The reason the attempt with if () { debouce(()={}); } else { immediate(); } doesn't work is due to how event handlers store the function and how debounce stores its timer.
When you run .on("input", function() { }) that function definition is stored within the event handler, ready to run. However with debounce, that's not what is being done, instead it's:
.on("input", function())
there's no function definition - it calls the function itself, which happens to return another function to be called when the event runs.
It's why there there are so many questions on SO saying something like "my code runs immediately when I do .on("input", myfunction()) when it should be .on("input", myfunction)
So that one function (the debounce) runs once per event setup - not once per input event fire, but just once when setting up the event. So there's only one instance of var timer and it's all contained within the debounce function. The event fire then calls the code inside the return function() which already has var timer defined previously in its outer scope (the previous debounce call).
If you call debounce again with a 2nd input $("#secondInp").on("input", debounce(() => ... you get a second instance of the function with its own variable (so they don't conflict between inp1 and inp2).
So you can then see that if you put this inside the event handler (in the if), you're calling a new debounce each time (not the same one).
Your attempt did nothing because your code debounce(function(){CheckMe.show();},500) simply returns the function - so you would need to do
debounce(function(){CheckMe.show();},500)();`
but even that won't work as each time it's called (each event) you get a new instance of the debounce function and a new instance of var timer.
You can use two events. Inside each event check if the "check me" should be shown or not.
The debounced one will run after 500ms and the not-debounced one will run immediately.
function debounce(fn, duration) {
var timer;
return function() {
clearTimeout(timer);
timer = setTimeout(fn, duration);
}
}
$(document).ready(function() {
const textField = $("#textField");
const checkMe = $("#checkMe");
checkMe.hide();
textField.on("input", debounce(() => {
if (textField.val()) {
checkMe.show();
}
}, 500));
textField.on("input", () => {
if (!textField.val()) {
checkMe.hide();
}
});
});
#checkMe { color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="textField" type="text">
<div id='checkMe'>^-- check this</div>
is it possible to keep it in a single if-else
Given the explanation above, it is not possible using your debounce function as it is; because of the return function() { } and the single instance of timer. The key being the single instance of timer.
Without the debounce, this can be implemented in a single function, using an outer variable for the timer, but will need the debounce code repeated each time (eg for a 2nd input) - just the clearTimeout and setTimeout code - so not much - it's the "global"/outer-scope variable that becomes an issue.
$(document).ready(function() {
var textFieldTimer = null;
const textField = $("#textField");
const checkMe = $("#checkMe");
checkMe.hide();
textField.on("input", () => {
if (textField.val()) {
clearTimeout(textFieldTimer);
textFieldTimer = setTimeout(() => checkMe.show(), 500);
}
else {
checkMe.hide();
}
});
});
#checkMe { color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="textField" type="text">
<div id='checkMe'>^-- check this</div>
So how can we use both: a reusable function and inside an if inside the event handler?
By storing the single instance of timer on the element itself - using .data() in the code below, but any method to store a single instance per element will also work.
Here's an example, using a single event with an if and repeated for a second input to show how it might work.
function debounce2(fn, duration)
{
var timer = $(this).data("timer");
clearTimeout(timer);
$(this).data("timer", setTimeout(fn, duration));
}
$(document).ready(function() {
$("#checkMe, #checkMe2").hide();
$("#textField, #textField2").on("input", function() {
if ($(this).val()) {
debounce2.call(textField, () => $("#checkMe").show(), 500);
}
else {
$("#checkMe").hide();
}
});
// second input confirming `timer` is per element.
$("#textField2").on("input", function() {
if ($(this).val()) {
debounce2.call(textField, () => $("#checkMe2").show(), 500);
}
else {
$("#checkMe2").hide();
}
});
});
#checkMe, #checkMe2 { color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="textField" type="text">
<div id='checkMe'>^-- check this</div>
<hr/>
<input id="textField2" type="text">
<div id='checkMe2'>^-- check this</div>

synchronise code in javascript, wait for event to complete

I've got a problem near that one.
Somewhere I've got a binding over a file input :
var file;
function initLoadImg(){
$('#test').on('change', function() {
file = event.target.files;
// block 1
console.log("hello");
center.html('<span id="Tamb">25°C</span>');
over = true;
});
}
And i'm triggering it with another javascript function :
var over = false;
var center;
function loadImg(){
var elem = $('<div class="widget simpleimgchart center"><div class="matable"><div class="center"></div></div></div>');
center = elem.children().children();
$("#test").trigger('click');
passIfOver();
// block 2
console.log("bye");
return elem;
}
function passIfOver() {
if (over) {
return;
} else {
setTimeout(passIfOver(), 1000);
}
}
This way, I'm able to see the "hello" before the "bye" in the console.
However I don't really like this solution, (it's not clean) and user can have to wait up to 1s before getting any feedback.
Would there be another way to ensure that the return elem is executed after the end of the callback on click?
edit : My code doesn't even work, because of the setTimeout, I lose the restraint...
edit 2 : My goal is to execute the part code 1 before the part code 2. I don't want my function loadImg() to return before the code 1 has finished to execute.
I recommend you to look at PubSub pattern (http://davidwalsh.name/pubsub-javascript).
Just move the return inside the Trigger function:
var over = false;
function loadImg(){
var elem = $('<div class="widget simpleimgchart center"><div class="matable"><div class="center"></div></div></div>');
var center = elem.children().children();
$("#test").trigger('click', function(){
center.html('<span id="Tamb">25°C</span>');
return elem;
});
}
The second argument to .trigger is a callback function everything inside it will be executed After the trigger is completed.

javascript - setTimeout and clearTimeout issue

Thank you in advance for your help with this.
I'm writing a click event that sets an active state on an element, and then after a couple seconds, removes the active state. This is working fine with the exception that there is some weird behavior happening if you click on the link a few times quickly in a row (menu opens and closes quickly, or doesn't show fully before closing again after a subsequent click). My guess is that clearTimeout really isn't clearing the timer quick enough (or not at all) the way I wrote this. The function is firing though so not sure what's going on with the odd behavior. Any help would be appreciated. My code is below. -Chris
$(document).on('click', '.toggle-edit-panel', function () {
var toggleEditPanelTimeout;
// resets timeout function
function resetEditPanelTimeout() {
clearTimeout(toggleEditPanelTimeout);
}
resetEditPanelTimeout();
// declares what this is and toggles active class
var $this = $(this);
var thisParent = $this.parent();
thisParent.find('.edit-panel').toggleClass('active');
$this.toggleClass('active');
toggleEditPanelTimeout = setTimeout(toggleEditPanelTimeoutFired($this), 2000);
// sets initial timeout function
function toggleEditPanelTimeoutFired(thisLinkClicked) {
toggleEditPanelTimeout = setTimeout(function(){
thisParent.find('.edit-panel').removeClass('active');
$(thisLinkClicked).removeClass('active');
},2000);
}
});
Solution below (Thanks Aroth!):
var toggleEditPanelTimeout;
$(document).on('click', '.toggle-edit-panel', function () {
// resets timeout function
clearTimeout(window.toggleEditPanelTimeout);
// declares what this is and toggles active class
var $this = $(this);
var thisParent = $this.parent();
thisParent.find('.edit-panel').toggleClass('active');
$this.toggleClass('active');
// sets initial timeout function
var theLink = $(this);
window.toggleEditPanelTimeout = setTimeout(function(){
$(theLink).parent().find('.edit-panel').removeClass('active');
$(theLink).removeClass('active');
},2000);
});
You've got a definite order-or-operations problem going on here (among other things):
var toggleEditPanelTimeout; //value set to 'undefined'; happens first
// resets timeout function
function resetEditPanelTimeout() {
clearTimeout(toggleEditPanelTimeout); //still undefined; happens third
}
resetEditPanelTimeout(); //value *still* undefined; happens second
// declares what this is and toggles active class
//...
//the value is assigned when this happens; happens fourth:
toggleEditPanelTimeout = setTimeout(toggleEditPanelTimeoutFired($this), 2000);
As a quick fix, you can simply make the variable global, and revise the code along the lines of:
clearTimeout(window.toggleEditPanelTimeout); //clear the previous timeout
// declares what this is and toggles active class
//...
//schedule a new timeout
window.toggleEditPanelTimeout = setTimeout(toggleEditPanelTimeoutFired($this), 2000);
You'll also likely want to remove that intermediate toggleEditPanelTimeoutFired(thisLinkClicked) function that you're using in order to get the code fully working. For instance:
//schedule a new timeout
var theLink = $(this);
window.toggleEditPanelTimeout = setTimeout(function(){
$(theLink).parent().find('.edit-panel').removeClass('active');
$(theLink).removeClass('active');
}, 2000);

prevent animation double click issue

Hi I have problem with my slider please visit this site and check http://paruyr.bl.ee/
after click on my arrows it becomes work in an asynchronous way, ones it changes very fast and then slow and it repeats.
I think it is from start slider and stop slider.
var sliderPrev = 0,
sliderNext = 1;
$("#slider > img").fadeIn(1000);
startSlider();
function startSlider(){
count = $("#slider > img").size();
loop = setInterval(function(){
if (sliderNext>(count-1)) {
sliderNext = 0;
sliderPrev = 0;
};
$("#slider").animate({left:+(-sliderNext)*100+'%'},900);
sliderPrev = sliderNext;
sliderNext=sliderNext+1;
},6000)
}
function prev () {
var newSlide=sliderPrev-1;
showSlide(newSlide);
}
function next () {
var newSlide=sliderPrev+1;
showSlide(sliderNext);
}
function stopLoop () {
window.clearInterval(loop);
}
function showSlide(id) {
stopLoop();
if (id>(count-1)) {
id = 0;
} else if(id<0){
id=count-1;
}
$("#slider").animate({left:+(-id)*100+'%'},900);
sliderPrev = id;
sliderNext=id+1;
startSlider();
};
$("#slider, .arrows").hover(function() {
stopLoop()
}, function() {
startSlider()
});
function onlyNext () {
var newSlide=sliderPrev+1;
onlyShowSlide(newSlide);
}
function onlyShowSlide(id) {
if (id>(count-1)) {
id = 0;
} else if(id<0){
id=count-1;
}
$("#slider").animate({left:+(-id)*100+'%'},900);
sliderPrev = id;
sliderNext=id+1;
};
I think the best option would be to check if the animation is in progress and prevent the action if it is, something like this:
function prev () {
if(!$('#slider').is(":animated"))
{
var newSlide=sliderPrev-1;
showSlide(newSlide);
}
}
function next () {
if(!$('#slider').is(":animated"))
{
var newSlide=sliderPrev+1;
showSlide(sliderNext);
}
}
To illustrate the difference between this and just sticking a stop() in, check this JSFiddle. You will notice some choppy movements if you click multiple times in the stop() version.
What I would do is add a class to your slider when the animation starts and remove the class when it finishes:
$("#slider").animate({left:+(-id)*100+'%'}, {
duration: 900,
start: function() {
$('#slider').addClass('blocked');
},
complete: function() {
$('#slider').removeClass('blocked');
}
});
Now check on each click event if the slider is blocked or not:
function next () {
if (!$('#slider').hasClass('blocked')) {
var newSlide=sliderPrev+1;
showSlide(sliderNext);
}
}
This is a very simple solution, I'm sure there is a better one.
EDIT: As marcjae pointed out, you could stop the animations from queuing. This means when you double click, the slideshow still will move 2 slides. With my approach the second click will be ignored completely.
You can use a variable flag to control if the animation is still being done, or simply use .stop() to avoid stacking the animation.
$("#pull").click(function(){
$("#togle-menu").stop().slideToggle("slow");
});
It is occurring because your animations are being queued.
Try adding:
.stop( true, true )
Before each of your animation methods. i.e.
$("#slider").stop( true, true ).animate({left:+(-id)*100+'%'},900);
The answers about stop are good, but you have a bigger issue that is causing the described behavior. The issue is here:
$("#slider, .arrows").hover(function() {
stopLoop()
}, function() {
startSlider()
});
You have bound this to the .arrows as well as the #slider and the arrows are contained within the slider. So, when you mouse out of an arrow and then out of the entire slider, you are calling start twice in a row without calling stop between. You can see this if you hover onto the arrow and then off of the slider multiple times in a row. The slides will change many times after 6 seconds.
Similarly, consider the case of a single click:
Enter the `#slider` [stopLoop]
Enter the `.arrows` [stopLoop]
Click the arrow [stopLoop]
[startSlider]
Leave the `.arrows` [startSlider]
Leave the `#slider` [startSlider]
As you can see from this sequence of events, startSlider is called 3 times in a row without calling stopLoop inbetween. The result is 3 intervals created, 2 of which will not be stopped the next time stopLoop is called.
You should just have this hover on the #slider and more importantly, add a call to stopLoop as the first step in startSlider. That will ensure that the interval is always cleared before creating a new one.
$("#slider").hover(function() {
stopLoop()
}, function() {
startSlider()
});
function startSlider(){
stopLoop();
/* start the slider */
}

jquery combining each and setInterval on elements with shared class

I have a series of links with a class "bloglink".
They have a click event associated with them - but that is irrelevant at this point. I am trying to cycle through them and trigger the click event every X seconds. This is where I'm at:
$('a.bloglink').each(function(){
var $bl = $(this);
setInterval(function(){
$bl.trigger('click')
},2000);
})
But it just triggers the click event for all of them at once.
Any tips?
You could do something like this:
(​function Loop()​{
var arry = $("a.bloglink").get();
var traverse = function(){
$(arry.shift()).trigger('click');
if (arry.length)
setTimeout(traverse, 2000);
};
setTimeout(traverse,2000);
})();
You can see it in action here: http://jsfiddle.net/Shmiddty/B7Hpf/
To start it over again, you can just add an else case:
(​function Loop()​{
var arry = $("a.bloglink").get();
var traverse = function(){
$(arry.shift()).trigger('click');
if (arry.length)
setTimeout(traverse, 2000);
else
Loop(); // Do the whole thing again
};
setTimeout(traverse,2000);
})();
See here: http://jsfiddle.net/Shmiddty/B7Hpf/1/
Create a function that sets the timer to run your code, clears the timer, then calls itself on the next element...
function processNext($current)
{
$h = setInterval(function() {
$current.css('color', 'green');//do your business here
clearTimeout($h);
if ($current.next('a.blah').size()>0)
{
processNext($current.next('a.blah'));
}
}, 750);
}
processNext($('a.blah').eq(0));
See here: http://jsfiddle.net/skeelsave/6xqWd/2/

Categories