I have a jsFiddle here: http://jsfiddle.net/dztGA/22/
The goal: Essentially, I'm trying to have 2 discrete timers on the same page that can be destroyed and re-created on mouseover/mouseout (pause), or on manual progression (restart).
The problem: What my jsFiddle's single timer will illustrate is that when I click "Stop Timer", my setInterval (stored in variable t) seems to have multiple instances albeit being destroyed with clearInterval(t). This becomes apparent when I click "Restart Timer" and it seems to have 2+ independent timers as illustrated by the quick increment.
A caveat: I have done as much research on SO as I can, but because I'll be having 2 different sliders on the page, I can't use any "clear all timers" methods, so I tried storing each in a variable.
I hope that's clear. Thanks for the view.
To fix your current issue: Add clearInterval(window.t) at the onclick function of the reset button.
A method to be able to have multiple timers. This requires a certain structure, though.
Fiddle (6 timers!): http://jsfiddle.net/dztGA/27/
(function(){ //Anonymous function, to not leak variables to the global scope
var defaultSpeed = 3000; //Used when missing
var timerSpeed = [500, 1000, 2000, 4000, 8000];
var intervals = [];
function increase(i){
return function(){
var elem = $("#count"+i);
elem.text(parseFloat(elem.text()) + 1);
}
}
function clear(i){
return function(){
clearInterval(intervals[i]);
}
}
function restart(i){ //Start AND restart
return function(){
clear(i)();
increase(i)();
intervals[i] = setInterval(increase(i), timerSpeed[i]||defaultSpeed);
}
}
// Manual increment
$('input[name=increment]').each(function(i){
$(this).click(function(){
restart(i)();
increase(i)();
});
});
// Clear timer on "Clear"
$('input[name=clear]').each(function(i) {
$(this).click(clear(i));
});
// Restart timer on "Restart"
$('input[name=reset]').each(function(i) {
$(this).click(restart(i));
//Optionally, activate each timer:
increase(i)();
});
})();
// Clear timer on "Clear"
$('input[name=clear]').click(function() {
window.clearInterval(t);
});
should be
// Clear timer on "Clear"
$('input[name=clear]').click(function() {
window.clearInterval(window.t);
});
because this is the input not Window
Related
Basically I'm trying to make a button to be performed again many times as long as the mouse is down on that button.
I need this for buttons with sliders. Right now, I click on a button for example "Increase Slider" and the slider is increased by 1 step, but now I want to be able to increase the slider many steps if I long press on that button.
How do I do that?
Your do loop runs as many times as it can in 1000 ms, and the mouseleave and mouseup handlers never get a chance to run because their events are sitting in the message queue waiting for the mousedown handler to finish running through that loop.
The loop sets up a couple thousand timeouts, to be run at least 200 ms later. Those timeouts don't actually do anything given the code you posted, because window's click handler is being called, not your button's.
The mouseleave and mouseup handlers essentially do nothing, because start will be reset to a valid time before ever being checked.
So how do we fix it?
There are two delays we want: the 1000 ms delay between the initial click and the first time the slider increases, and the 200 ms delay between slider increases. If the user cancels during the first 1000 ms, we'll count that as a single click. If the user cancels after the repetition starts, we shouldn't count that as a click. (We'll define "canceling" as releasing the mouse button or moving the cursor off the button. This means pressing the mouse button over the UI button and moving the cursor off will count as a click, but the code will be simpler.)
We can set up the delays by setting up a timeout that, after 1000 ms, sets up an interval that, every 200 ms, increases the slider. We won't be using the click event for the slider increase because of the last line of the spec:
If the user cancels after the repetition starts, we shouldn't count that as a click.
So we'll give the slider-increase code its own function, increaseSlider() (which is good practice anyway):
function startLongClick (e) {
window.setTimeout(() => {
increaseSlider();
window.setInterval(() => {
increaseSlider();
}, 200);
}, 1000);
}
$('#button').on('mousedown', startLongClick);
We put the first call to increaseSlider() in the timeout so the slider first increases 1000 ms after the initial click, not 1200. We use arrow functions in the timeout and interval because arrow functions don't redefine this, so we'd be able to refer to the triggering <button> if necessary.
I can't stop it!
As the code is now, a single click on the button will start the whole long-click process, with no way of stopping it. Stopping the process means stopping the timeout and interval, which we can do with window.clearTimeout() or window.clearInterval() (they're the same function; don't tell anybody). We'll need to hang on to the IDs setTimeout() and setInterval() give us, and clear them in the mouseup and mouseleave handlers:
let intervalId;
let timeoutId;
function startLongClick (e) {
timeoutId = window.setTimeout(() => {
increaseSlider();
intervalId = window.setInterval(() => {
increaseSlider();
}, 200);
}, 1000);
}
function cancelLongClick () {
window.clearInterval(intervalId);
window.clearTimeout(timeoutId);
}
$('#button').on('mousedown', startLongClick);
$('#button').on('mouseup', cancelLongClick);
$('#button').on('mouseleave', cancelLongClick);
What about the short click?
Now the button's doing what we want it to do, with one exception: a short click doesn't do anything, because we're not using the click handler and the timeout is being cleared before increaseSlider() is ever called. A short click should be registered if a canceling event is fired after the mousedown event but before the timeout fires. Since timeoutId is undefined before the mousedown event and we don't need it once the timeout fires, we can assign undefined to it in the timeout and use it to determine whether we should register a short click:
let intervalId;
let timeoutId;
function startLongClick (e) {
timeoutId = window.setTimeout(() => {
timeoutId = undefined;
increaseSlider();
intervalId = window.setInterval(() => {
increaseSlider();
}, 200);
}, 1000);
}
function cancelLongClick () {
window.clearInterval(intervalId);
if (timeoutId) {
increaseSlider();
window.clearTimeout(timeoutId);
timeoutId = undefined;
}
}
$('#button').on('mousedown', startLongClick);
$('#button').on('mouseup', cancelLongClick);
$('#button').on('mouseleave', cancelLongClick);
We set timeoutId to undefined in the short-click code as well. Otherwise, after short-clicking, an increase would trigger every time you mouse out of the button.
More buttons!
The code works now, but requires two global variables and is hard-coded for a specific button. Let's turn it into a general-purpose jQuery plugin*:
(($) => {
$.fn.repeatingClick = function (callback, delay = 500, interval = 200) {
return this.each(function () {
let intervalId;
let timeoutId;
function startLongClick (e) {
timeoutId = window.setTimeout(() => {
timeoutId = undefined;
callback.call($(this), e);
intervalId = window.setInterval(() => {
callback.call(this, e);
}, interval);
}, delay);
}
function cancelLongClick (e) {
window.clearInterval(intervalId);
if (timeoutId) {
callback.call(this, e);
window.clearTimeout(timeoutId);
timeoutId = undefined;
}
}
$(this).on('mousedown', startLongClick);
$(this).on('mouseup', cancelLongClick);
$(this).on('mouseleave', cancelLongClick);
});
}
})(jQuery);
function modifySlider (e) {
let modifier = Number($(this).data('change'));
$('progress').attr('value', Number($('progress').attr('value')) + modifier);
}
$('button').repeatingClick(modifySlider);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="dec" data-change="-1">−</button>
<progress value="25" max="50"></progress>
<button id="inc" data-change="1">+</button>
What's changed?
Replaced calls to increaseSlider() with a callback parameter and callback.call($(this), e). This way, any function can be used as the callback, and since we used arrow functions in the timeout, we're able to use Function.call with this to access the triggering element in the callback.
Parameterized the delays in the timeout and interval into delay and interval, for more general use.
Stuck the whole thing in a new jQuery function, $.repeatingClick(). Since jQuery objects can represent collections as well as individual elements, we wrap the original code in a call to $.each() to access each element individually. We also return the jQuery object in the usual style.
The rest is specific to this application: two buttons to modify the value of a (<progress>) 'slider', using custom data- attributes for the actual amounts so we can give both the same code.
*I've never written a jQuery plugin before; most of the code surrounding the core logic came straight from jquery-longpress, a jQuery plugin that does almost what OP wants.
Try using intervals instead of manually calculating time. Check this out:
var value = 0
var addval;
var press = false;
$('#button').on('mousedown', function (e) {
press = true;
increaseValue();
return false;
});
$('#button').on('mouseleave', function (e) {
clearInterval(addval);
return false;
});
$('#button').on('mouseenter', function(e) {
if (press)
increaseValue();
});
$('#button').on('mouseup', function (e) {
press = false;
clearInterval(addval);
return false;
});
function increaseValue() {
addval = setInterval(function(){
value++;
$("#counter").text(value);
}, 100);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="button">Press me</button>
<div id="counter">0</div>
You can adjust the speed by changing interval time.
I would like pause on hover when the mouse hovers over the fadelinks div for this script:
$(function(){
$('.fadelinks > :gt(0)').hide();
setInterval(function(){$('.fadelinks > :first-child').fadeOut().next().fadeIn().end().appendTo
('.fadelinks');}, 5000);
});
The html is along the lines of:
<div class="fadelinks">
<div>...</div>
<div>...</div>
</div>
I've tried a few things relating to interval to try and cram pause on hover functionality in there, but with my extremely limited jquery knowledge, everything I've tried breaks the script, leaving it stuck on the last slide or the first slide. Would just like this simple script to pause on mouse-hover and start up again on mouse-exit.
Here's a JSFiddle of the script in its natural state.
Try using .hover() , declaring variable to reference setInterval , using a function to call setInterval
$(function(){
// define `_interval` variable
var _interval;
// cache `.fadelinks` element
var elem = $(".fadelinks");
elem.find("> :gt(0)").hide();
elem.hover(function() {
// "pause" at `hover` of `.fadelinks`
clearInterval(_interval)
}, function() {
// "reset"
interval()
});
var interval = function() {
_interval = setInterval(function(){
elem.find("> :first-child")
.fadeOut().next().fadeIn().end()
.appendTo(elem);
}, 2000)
};
interval()
});
jsfiddle https://jsfiddle.net/ccmgdfog/4/
In your case, there wasn't the need for jQuery. Only with stopInterval you can control it. Altrough there is the jQuery $.stop() function, we wouldn't get the desired result.
I've changed a bit your code:
$(function(){
$('.fadelinks > :gt(0)').hide();
var interval = setInterval(intervalFunc, 2000);
$('.fadelinks').on('mouseenter',function(){
clearInterval(interval);
});
$('.fadelinks').on('mouseout',function(){
interval = setInterval(intervalFunc, 2000);
});
function intervalFunc(){
$('.fadelinks > :first-child').fadeOut().next().fadeIn().end().appendTo('.fadelinks');
}
});
My problem basically is that i want to click on a <button> that appears inside a <div> that is available only for 3 seconds...
The <div> has display:none, so i can make it appear whenever i want, but the <button> is generated by an other js file that i don't have access to. I can not change the original js file (to give me more time, for example) because i'm only interacting with the page using a userscript! (not my own page)
I tried so far locating inside the js code the id or the class of the button generated, but the js is minified... so no luck...
I tried also using the temporary class that is generated during those 3 seconds for that button and click on it... by doing a screenshot typing the class manually and then running a little code in the console during those 3 seconds to click on it... and no luck...
By now i think the problem is my approach to the situation, so i didn't write any code here...
Please give me your thoughts about this...
You can add MutationObserver if you want to react to changes in DOM (in your case to parent element).
Without any code it's hard to provide better answer.
jsFiddle
(function () {
"use strict";
var target = document.getElementById("hiddenElement"),
observer = new MutationObserver(function(mutations) {
mutations.forEach(function(value, index, array) {
console.log(value.type);
console.dir(value.addedNodes); // return node list
console.dir(value.removedNodes);
// put here your logic
});
});
observer.observe(target, {
childList: true,
subtree: true,
characterData: true,
characterDataOldValue: true
});
}());
(function () {
"use strict";
var newEle = document.createElement("input"),
hiddenElement = document.getElementById("hiddenElement");
newEle.type = "button";
newEle.value = "click";
newEle.addEventListener("click", function(e) {
alert("Generated only for 3 second");
});
hiddenElement.appendChild(newEle);
hiddenElement.style.display = "block";
setTimeout(function() {
hiddenElement.style.display = "none";
hiddenElement.removeChild(newEle);
}, 3000);
}());
Throw your button detection code inside a setInterval that fires often enough that it will fire while the button is present:
// note: this is just pseudocode
var interval = setInterval(function() {
// look for button
if(buttonFound) {
clickButton();
clearInterval(interval);
}
}, 300);
i'm having some sort of variable out of scope issue or something. in the function below, i'm creating or clearing a timeout based on whether the mouse is entering or exiting. it seems though, that even once the timeout has been created it's returning undefined on re-entry. not sure what i'm doing wrong here, thanks for your help!
jsFiddle example
JavaScript: (particular issue is within else conditional on line 35
var navLinks = $('nav li.sub');
navLinks.mouseenter(function(){
console.log('hovering on link');
var thiis = $(this),
subList = thiis.find('ul'),
autoClose;
if (!thiis.hasClass('out')){
console.log('isnt out');
/* Link */
thiis
/* Show submenu when entering link */
.addClass('out')
/* Hide submenu when exiting link */
.mouseleave(function(){
autoClose = setTimeout(function(){
thiis.removeClass('out');
}, 1000);
console.log('exiting link: timeout active', autoClose);
});
} else {
console.log ('is out', autoClose);
if (autoClose){
console.log('is out: clear timeout');
clearTimeout(autoClose);
}
}
});
Techno,
The simple answer is just to move var autoClose to an outer scope, but I think you can (and should) do more.
More specifically,
I don't think you want to attach the mouseleave handler inside the mouseenter handler. It can be permanently attached from the outset.
In the mouseenter handler, clearTimeout(autoClose) and thiis.addClass('out') can be executed unconditionally. There's no real economy in testing .hasclass('out').
Try this :
var navLinks = $('nav li.sub');
var autoClose;
navLinks.hover(function(){
var thiis = $(this);
clearTimeout(autoClose);
thiis.addClass('out');
}, function(){
var thiis = $(this);
autoClose = setTimeout(function(){
thiis.removeClass('out');
}, 1000);
});
As pointed in comments to question you have new timeout each time mouse hovers an item. Lets make new timeout variable for every item:
$('nav li:has(ul)').each(function(){
var par = $(this),
sub = $("> ul", this),
closeTO;
par.hover(
function(){
clearTimeout(closeTO);
par.addClass("out");
},
function(){
closeTO = setTimeout(function(){
par.removeClass("out");
}, 1000);
}
);
});
http://jsfiddle.net/ByuG3/1/
You should use a global object to store some vars like reference of timeout and interval.
In example you could declare such an object:
// Declare the context object in the global scope
var myContext = {
"myTimeout" : false
}
And then use the context object in your mouseenter and mouseleave handler functions.
I'm really new to jQuery but familiar with some other languages. I recently bought a quiz type script and I'm trying to add a simple 15 second timer to each question. It's only a fun quiz, so no need to worry about users playing with the javascript to increase time etc.
Basically, if a user does not pick a question within 15 seconds, it will automatically go on to the next question and the timer starts over again.
Answers have the .next tag, and when chosen it moves onto the next question as the code below shows (hopefully).
superContainer.find('.next').click(function () {
$(this).parents('.slide-container').fadeOut(500, function () {
$(this).next().fadeIn(500)
});
return false
});
The problem i have is if i use setInterval, i don't know how i can select the appropriate div again for fade it our and fade in the next one. I've tried the below code and a few similar scrappy idea's but it doesn't work, but maybe it will give a better idea of what I'm after though.
superContainer.find('.next').click(function () {
$active_count = $count;
countInterval = setInterval(function() {
$active_count--;
if($active_count <= 0){
clearInterval(countInterval);
$active_count = $count;
$(this).parents('.slide-container').fadeOut(500, function () {
$(this).next().fadeIn(500)
});
}
$('.question-timer').html($active_count);
}, 1000);
$(this).parents('.slide-container').fadeOut(500, function () {
$(this).next().fadeIn(500)
});
return false
});
I've only been using JQuery a day or two so excuse any obvious mistakes and bad code! Let me know if you need any other code or information
This is moderately tricky for a first jQuery project.
The knack (in this solution) is to factor out a goNext function that can be called in two ways - in response to a click event and in response to a 15 second setTimeout(), not setInterval().
$(function(){
var questionTimeout = null;
function goNext($el) {
clearTimeout(questionTimeout);
var $next = $el.next();
$el.fadeOut(500, function() {
if($next.length > 0) {
$next.fadeIn(500, function() {
questionTimeout = setTimeout(function() {
goNext($next);
}, 15000);
});
}
else {
afterLastQuestion();
}
});
}
function afterLastQuestion(){
alert("last question complete");
$start.show();
}
var $superContainer = $("#superContainer").on('click', '.next', function() {
goNext($(this).closest('.slide-container'));
return false;
});
var $start = $("#start").on('click', function(){
$(this).hide();
$superContainer.find(".slide-container")
.eq(0).clone(true,true)
.prependTo(superContainer)
.find(".next").trigger('click');
return false;
});
});
DEMO
The process is started by clicking a "start" link, causing the first question to be cloned followed by a simulated click on the clone's "next" link. This ensures that the (actual) first question is treated in exactly the same way as all the others.
I also included a afterLastQuestion() function. Modify its action to do whatever is necessary after the last question is answered (or times out).
You could keep the current question in a variable, resetting it on a next click and in the timer, e.g.
var $current;
superContainer.find('.next').click(function (e) {
e.preventDefault();
$(this).parents('.slide-container').fadeOut(500, function () {
$(this).next().fadeIn(500);
$current = $(this).next();
});
});
You'll just need to set it to your first question on initialisation, and remember to reset your timer on a next click
Also, it's usually preferable to use e.preventDefault() rather than return false.