Javascript, local copying of a variable's value... struggling to achieve it - javascript

I have what i thought was a simple javascript / jquery function (fade out of one div, fade into another... loop until it reaches a maximum and then start back from the begining. The problem i have though is that to fadein the next div i need to increment the global counter. Doing this increments double increments it because i'm assuming the local variable i've created maintains the same reference to the global variable.
The code sample below should explain a little easier. Can anyone spot what i'm doing wrong?
var current_index = 1;
$(document).ready(function() {
$(function() {
setInterval("selectNextStep()", 3000);
});
});
function selectNextStep() {
$("#step_"+current_index).fadeOut('slow', function() {
var next = current_index;
next = next + 1;
$("#step_"+next).fadeIn('slow', function() {
if (current_index == 4) current_index = 1;
else current_index ++;
});
});
}

I think you're ending up with race conditions due to the interval trying to fade things in and the callbacks trying to fade things out. For this setup it makes more sense to let the fade callbacks start the next round.
Also using a 0-based index makes the math easier.
var current_index = 0; // zero indexes makes math easier
$(document).ready(function () {
$(function () {
// use timeout instead of interval, the fading callbacks will
// keep the process going
setTimeout(selectNextStep, 3000);
});
});
function selectNextStep() {
// +1 to adapt index to element id
$("#step_" + (current_index + 1)).fadeOut('slow', function () {
var next = current_index + 1;
// keeps index in range of 0-3
next = next % 4; // assuming you have 4 elements?
current_index = (current_index + 1) % 4;
// callback will start the next iteration
$("#step_" + (next + 1)).fadeIn('slow', function () {
setTimeout(selectNextStep, 3000);
});
});
}
demo: http://jsbin.com/exufu

I do not see any double increment the way your code is..
the problem is that the next variable goes beyond the 4 value that seems to be the limit, and trying to fadein an element that does not exist. so the code that resets the currentIndex never executes..
try adding if (next > 4 ) next = 1; after increasing the next variable
Example at http://jsfiddle.net/5zeUF/

isn't $(function() {}); the same as $(document).ready(function(){}), so you are initializing selectNextStep twice (hence the double increment)?

Try this. Simplifies things a little. Increments (and resets if needed) the current_index before the next fadeIn().
Example: http://jsfiddle.net/r7BFR/
var current_index = 1;
function selectNextStep() {
$("#step_" + current_index).fadeOut('slow', function() {
current_index++;
if (current_index > 4) current_index = 1;
$("#step_" + current_index).fadeIn('slow');
});
}
$(document).ready(function() {
setInterval(selectNextStep, 3000);
});
EDIT: Added example, and fixed my misspelling (camelCase) of current_index.
Here's an alternate way of doing the increment:
current_index = (current_index % 4) + 1;

Try this, slightly different approach but does what you need it to do I believe, also you can add more steps without modifying the script and doesn't pollute the global namespace (window)
[HTML]
​<div class="step defaultStep">One</div>
<div class="step">Two</div>
<div class="step">Three</div>
<div class="step">Four</div>
<div class="step">Five</div>
[CSS]
.step { display: none; }
.defaultStep { display: block; }​
[JS]
$( function() {
var steps = $( ".step" );
var interval = setInterval( function( ) {
var current = $( ".step" ).filter( ":visible" ), next;
if( current.next( ).length !== 0 ) {
next = current.next( );
} else {
next = steps.eq(0);
}
current.fadeOut( "slow", function( ) {
next.fadeIn( "slow" );
} );
}, 3000);
} );

Maybe you also want to have a look at the cycle plugin for jquery. There you can actually achieve such nice transitions. I think with a little work this would ease up everything.
http://jquery.malsup.com/cycle/
Regarding your code snippet. I think you can enhance it a little in this way:
$(document).ready(function() {
var current_index = 0;
window.setInterval(function() {
$("#step_"+ current_index).fadeOut('slow', function() {
$("#step_" + (current_index + 1)).fadeIn('slow', function() {
current_index = (current_index + 1) % 4;
});
});
}, 3000);
});
This should do the exact same work. As the interval function closes over the current_index variable it should be valid inside the function. Sorry, if you're not a fan of all these closures but I rather preferr passing the function I want to execute directly to the setInterval function, than defining it anywhere else.
P.S. Be aware that the changes I introduced imply that your #step_ IDs start at 0.

Related

Is there a way of making a javascript loop to repeatedly press a button?

I am very new to javascript so bear with me if I make mistakes, I am designing a website that allows the user to change the background with a button press, this all works very well and it's great but I want the images to be able to cycle with a time delay. In essence, I want the user to be able to click a button to change the background but I also want a javascript loop running in the background clicking the same button after a set period of time, is this possible?
The code to change the background is as follows:
$(document).ready(function () {
var i = 0;
$("#n").click(function () {
i++;
if (i > 16) { i = 1; };
$('body').css('background-image', 'url(img/' + i + '.jpg)');
});
$("#p").click(function () {
i--;
if (i <= 0) { i = 16; };
$('body').css('background-image', 'url(img/' + i + '.jpg)');
});
});
And the code for the buttons used is:
<button id="n" class="btn">Nxt</button>
<button id="p" class="btn">Prv</button>
You can use setInterval for looping -
setInterval(() => {
console.log('this will repeat every 500 milliseconds');
}, 500);
My recommendation would be not to simulate a click but just call the same function that the click does.
So let's make a function that can be called:
function increment() {
i++;
if ( i > 16 ){ i = 1; };
$('body').css('background-image', 'url(img/' + i + '.jpg)');
}
Then we can use that for the regular click that you already have:
$("#n").click(increment)
And we can also define a repeating call to this function:
setInterval(increment, 1000)
setInterval is pretty neat for non time sensitive tasks like this. Look at the link provided to see how it works, how to cancel it if you want to stop it...
#Ritesh Ganjewala wrote a nice little snippet in her answer that will show you what setInterval does.
#trincot also has an answer that, while a little more complicated to understand, allows you to have less repeating code by using the same function to increment and decrement your counter. Which is something you should strive for when programming something. This is called the DRY principle.
By design, you can't create user inputs with JavaScript. This makes sense if you think about it, certain browser features like requesting permissions can only be done if the user performs an action. You wouldn't want sites agreeing to grant permissions without your input.
So in your case, the best thing to do would be to have the event listener (for user input) and the setTimeout or requestAnimationFrame (for automatic cycling) call the same function.
document.getElementById('n').addEventListener('click', nextColor);
document.getElementById('p').addEventListener('click', prevColor);
var i = 0;
function nextColor() {
i++;
if ( i > 16 ){ i = 1; };
document.body.style.backgroundImage = `url(img/${i}.jpg)`;
}
function prevColor() {
i--;
if ( i <= 0 ){ i = 16; };
document.body.style.backgroundImage = `url(img/${i}.jpg)`;
}
setInterval(nextColor, 5000);
Don't attempt to send a click event to a button. Instead isolate the function that such a click executes, and then use setTimeout to execute that function. You may also want to reset an ongoing timeout when the user clicks again.
Here is how that could look:
$(document).ready(function(){
var i = 0;
var delay = 1000; // number of milliseconds
var timer = setTimeout(next, delay); // <-- set the time out
function next(step=1) { // optional argument that should be 1 or -1
i = (i + 16 + step) % 16; // Use modulo arithmetic to rotate in the range [0..15]
$('body').css('background-image', 'url(img/' + (i+1) + '.jpg)');
clearTimeout(timer); // interrupt the ongoing time out ...
timer = setTimeout(next, delay); // <-- and start it again
}
$("#n").click(next);
$("#p").click(next.bind(null, -1)); // bind the argument to be -1
});
try this setInterval, this will allow you to run a function with a fixed time delay.
function doThis() {
console.log('2 seconds over ...');
}
var xyz = setInterval(doThis, 2000);
and if you need to stop this,
clearInterval(xyz);
Yes, It is possible...
On click of the button on which you are changing the background color you can also initiate a timeout which will call a function after the specified time period (callback function) which in your case will change the image.and setinterval will do the same repeatedly after a specified interval.
$(document).ready(function(){
var i = 0;
function increment() {
i++;
if ( i > 16 ){ i = 1; };
$('body').css('background-image', 'url(img/' + i + '.jpg)');
}
function decrement() {
i--;
if ( i <= 0 ){ i = 16; };
$('body').css('background-image', 'url(img/' + i + '.jpg)');
}
$("#p").click(function(){
increment();
setInterval(increment, 1000);
}
$("#n").click(function(){
decrement();
setInterval(decrement, 1000);
}
}
Ok so ive figured something out from a couple of people who answered, what ive got now is this:
<script>
setInterval(() => {
$("#n").click();
}, 7000);
</script>
This has the effect of clicking the button i want every 7 seconds and its perfect, thanks for everyones help!

Clearing setInterval() Issue

I have a setInterval on a function X that runs every 500ms. In this function X, I call another function Y that essentially binds an event on some divs. However, I would like to unbind these events the next time the function X is called (to start "fresh"). My code doesn't seem to work:
setInterval(this.board.updateBoard, 500); //called from another constructor
This then initiates the functions below:
Board.prototype.updateBoard = function() {
//I attempt to unbind ALL my divs
var divs = this.$el.find("div");
for(var i = 0; i < divs.length; i++) {
$(divs[i]).unbind(); //Apparently this doesn't work?
}
//...some code here...
//find appropriate $div's (multiple of them), and then calls this.beginWalking() below on each of those
//loop here
this.beginWalking($div, direction + "0", direction + "1");
//end of loop
}
//alternate between classes to give appearance of walking
Board.prototype.beginWalking = function ($div, dir0, dir1) {
return setInterval(function () {
if ($div.hasClass(dir0)) {
$div.removeClass(dir0);
$div.addClass(dir1);
} else {
$div.removeClass(dir1);
$div.addClass(dir0);
}
}.bind(this), 80);
};
Basically, updateBoard is called every 500ms. Each time it's called, beginWalking is called to set another interval on a div. The purpose of this other interval, which functions correctly, is to add and remove a class every 80ms. I just can't seem to unbind everything before the next updateBoard is called.
Any suggestions appreciated!
use clearInterval()
edit: $(selector).toggleClass(dir0) might also be helpful
// In other file, use a global (no var) if you need to read it from another file:
updaterGlobal = setInterval(this.board.updateBoard, 500);
// store interval references for clearing:
var updaterLocals = [];
Board.prototype.updateBoard = function() {
//I attempt to unbind ALL my divs
var divs = this.$el.find("div");
// Stop existing div timers:
while(updaterLocals.length > 0){
clearInterval(updaterLocals[0]);
updaterLocals.shift(); // remove the first timer
}
//...some code here...
//loop here to call the below on several $div's
this.beginWalking($div, direction + "0", direction + "1");
//end of loop
}
//alternate between classes to give appearance of walking
Board.prototype.beginWalking = function ($div, dir0, dir1) {
var interval = setInterval(function () {
if ($div.hasClass(dir0)) {
$div.removeClass(dir0);
$div.addClass(dir1);
} else {
$div.removeClass(dir1);
$div.addClass(dir0);
}
}.bind(this), 80);
// Save the timer:
updaterLocals.push(interval);
return;
};

Pausing Recursive Function Call

I have a function of which I'm supposed to pause on mouseenter and pause on mouseleave but the problem is that the function is recursive. You pass in a parent and index and it will recursively loop through each inner div displaying and hiding. The function looks like this:
var delay = 1000;
function cycle(variable, j){
var jmax = jQuery(variable + " div").length;
jQuery(variable + " div:eq(" + j + ")")
.css('display', 'block')
.animate({opacity: 1}, 600)
.animate({opacity: 1}, delay)
.animate({opacity: 0}, 800, function(){
if(j+1 === jmax){
j=0;
}else{
j++;
}
jQuery(this).css('display', 'none').animate({opacity: 0}, 10);
cycle(variable, j);
});
}
I've tried setting a timeout and clearing it but it doesn't seem to do anything (it seems to ignore the timeout entirely), I've tried using stop() and calling the function again on mouseout but that seemed to repeat the function call (I was seeing duplicates) and it stopped mid animation which didn't work. I tried adding in a default variable at one point (var pause = false || true;) but I also couldn't get it to work as expected (though I feel the solution relies on that variable). I'm open to all suggestions but there are some rules of engagement:
Rules: There can't be any major changes in how this function works as many things rely on it, it's something I do not have control over. Assume the function call looks like this jQuery('#divList', 0) and holds a bunch of div elements as children.
The timeout function is the last solution I tried which looks like:
jQuery('#divList').on('mouseenter', function(){
setTimeout(cycle, 100000);
})
.on('mouseleave', function(){
window.clearTimeout();
});
Perhaps something like this? I simplified the animation just to make the example simpler, but you should be able to adapt it to your needs.
First, we have a function that's responsible for animating a set of elements. Every function call returns a new function that allows to toggle the animation (transition between pause and resume).
function startCycleAnimation(els) {
var index = 0,
$els = $(els),
$animatedEl;
animate($nextEl());
return pauseCycleAnimation;
function animate($el, startOpacity) {
$el.css('opacity', startOpacity || 1)
.animate({ opacity: 0 }, 800, function () {
animate($nextEl());
});
}
function $nextEl() {
index = index % $els.length;
return $animatedEl = $els.slice(index++, index);
}
function pauseCycleAnimation() {
$animatedEl.stop(true);
return resumeCycleAnimation;
}
function resumeCycleAnimation() {
animate($animatedEl, $animatedEl.css('opacity'));
return pauseCycleAnimation;
}
}
Then we can kick-start everything with something like:
$(function () {
var $animationContainer = $('#animation-container'),
toggleAnimation = startCycleAnimation($animationContainer.children('div'));
$animationContainer.mouseenter(pauseOrResume).mouseleave(pauseOrResume);
function pauseOrResume() {
toggleAnimation = toggleAnimation();
}
});
Example HTML
<body>
<div id="animation-container">
<div>Div 1</div>
<div>Div 2</div>
<div>Div 3</div>
</div>
</body>
If you want something more generic, it seems there's a plugin that overrides animate and allows to pause/resume animations in a generic way.
You will need to put a flag that each cycle checks before it determines if it is going to run. Then you can just change that flag when the mouse events are triggered. If you need to pick up where you left off when you unpause, consider saving the last value of j
function cycle(variable, j){
if (window.paused) {
window.last_j = j;
return;
}
...
Then when you want to pause, just set window.paused = true . To resume, change it back to false and call cycle again:
cycle(variable, last_j);

build slider using jquery & javascript

I built slider using jquery but it is very stupid. You can see it:
http://jsfiddle.net/Bf2Mv/
i think the problem is here:
$(".img img").fadeOut().attr("src", images[count % images.length]).fadeIn();
$(".text").fadeOut().html(text[textcount % text.length]).fadeIn();
how to fix the effects?
thanks a lot!
You probably wanted something like this:
JSFiddle: http://jsfiddle.net/TrueBlueAussie/Bf2Mv/7/
count = 0,
text = [
"first img desc",
"2nd img desc",
"3rd img desc"],
imageCount = images.length,
rand = 6000;
function slide() {
changeImage(1)
rand = 6000;
}
function changeImage(delta) {
count += delta;
if (count < 0) count = imageCount - 1;
count %= imageCount;
$(".img img").fadeOut(function () {
$(this).attr("src", images[count]).fadeIn()
});
$(".text").fadeOut(function () {
$(this).html(text[count]).fadeIn();
});
}
(function loop() {
setTimeout(function () {
slide();
loop();
}, rand);
}());
$("#next").click(function () {
changeImage(1);
});
$("#prev").click(function () {
changeImage(-1);
});
Notes:
You only need a single counter if the images and text arrays are the same length.
You did not need to have modulus operators everywhere if the indexes are managed correctly (between 0 and length-1)
You need to correctly wrap around from 0 to length-1 and back the other way.
You need to change the images after they fadeout (hence the new callbacks in fadeout)
I refactored the change code, so it just takes a direction delta value. That way the same code can be reused by the timer, the next and the prev options.
The problem was on how the fadein/fadeout was defined. You need to use the jQuery fadein/fadeout finished parameter function.
$(".img img").stop().fadeOut('fast', function() {
var that = $(this);
that.attr("src", images[count]).fadeIn('fast', function() {
if (!LOOP_INTERVAL) {
startLoop(); // Continue the loop.
}
});
});
There were also some other issues on the indexes. Here's the solution: http://jsfiddle.net/Bf2Mv/45/
I hope this is what you meant. If you have questions about the answer, feel free to ask!

Allowing for maximum number of opened accordion sections using jQuery

I've looked all over the internet and I can't seem to find a good way to do this.
I've got an accordion menu that I've built primarily using addClass/removeClass and css. It has special functionality, the accordion tabs open after a delay on mouseover and they open and close on click. I can currently open all of them at once, but I'd like to limit this to 2 or 3 with the earliest selected panel closing after I hit that limit. So I'd either need to make the classes numbered and switch them on every action, or perhaps apply a variable that keeps track of the order in which the panels were selected and switch them.
Below is the code I have so far. I've only been able to get as far as keeping count of how many tabs there currently are open. Does anyone have an idea as to what the best way to approach this is?
var timer;
var counter = 0;
$('li.has-dropdown').mouseenter(function() {
dd_item = $(this);
if(!$(this).hasClass('expand-tab')){
timer = setTimeout ( function () {
$(dd_item).addClass('expand-tab');
counter++;
}, 200);
};
}).mouseleave(function(){
clearTimeout(timer);
console.log(counter);
}).click(function() {
if ($(this).hasClass('expand-tab')){
$(this).removeClass('expand-tab');
counter--;
console.log(counter);
}else{
$(this).addClass('expand-tab');
console.log(counter);
}
});
Add a incrementting data-index to each opened tab.
count the tabs on the end of the hover effect, if they are to many, sort them by the index, and hide the lowest/oldest.
var timer;
var index = 1;
$('li.has-dropdown').mouseenter(function() {
dd_item = $(this);
if(!$(this).hasClass('expand-tab')){
timer = setTimeout ( function () {
$(dd_item).addClass('expand-tab');
$(dd_item).attr('data-index', index++);
counter++;
}, 200);
};
}).mouseleave(function(){
clearTimeout(timer);
console.log(counter);
}).click(function() {
$(this).taggleClass('expand-tab'); // see jQuery toggleClass();
$(this).attr('data-index', index++);//this will add index on closed tabs also.. but it does not matter at the end.
});
if($('.expand-tab').length> 3){
//custom inline sorting function.
var expanded_tabs = $('.expand-tab').sort(function (a, b) {
return (parseInt( $(a).attr('data-index')) < parseInt( $(b).attr('data-index')) ? -1 : 1 ;
});
//time out .. effect etc.
expanded_tabs[0].removeClass('expand-tab');
}
P.S I don't like havving Hover and Click in the same place ... try to separate the events and call a unified collapseIfToMany function in on each event
This is a corrected version. I decided to use a variable for the maximum panels opened, this way you don't have to dig if you decide you want to change it, or if you add more to the code.
var timer;
var index = 1;
var maxOpen = 2;
$('li.has-dropdown').mouseenter(function() {
dd_item = $(this);
if(!$(this).hasClass('expand-tab')){
timer = setTimeout ( function () {
$(dd_item).addClass('expand-tab');
$(dd_item).attr('data-index', index++);
collapseIfTooMany();
}, 200);
};
}).mouseleave(function(){
clearTimeout(timer);
}).click(function() {
$(this).toggleClass('expand-tab'); // see jQuery toggleClass();
$(this).attr('data-index', index++);//this will add index on closed tabs also.. but it does not matter at the end.
});
function collapseIfTooMany(){
if($('.expand-tab').length > maxOpen){
//custom inline sorting function.
var expanded_tabs = $('.expand-tab').sort(function (a, b) {
return (parseInt( $(a).attr('data-index')) < parseInt( $(b).attr('data-index'))) ? -1 : 1 ;
});
//time out .. effect etc.
$(expanded_tabs[0]).removeClass('expand-tab');
}
};

Categories

Resources