Breaking loop when user clicks on a new tab - jQuery Tabs - javascript

I am working on some tabbed navigation for my website and I have an issue I'd like to fix.
I've been scrambling my head all day and getting nowhere. Would really appreciate some help.
Here be the code: http://jsfiddle.net/EghAt/
1) Notice when you click Tab 1 and then immediately click Tab 2, Tab 1 continues to loop out all the results.
I would prefer if this stopped looping Tab 1 results and just started looping Tab 2 results.
Is this possible?
How do I achieve this?
Many thanks for any pointers

You can stop the previous animation in this function of yours, by adding the .stop(true, true) you see in this revised function:
function fadeOutItems(ele, delay) {
var $$ = $(ele), $n = $$.next();
// Toggle the active class
$$.toggleClass('active');
// Ensure the next element exists and has the correct nodeType
// of an unordered list aka "UL"
if ($n.length && $n[0].nodeName === 'UL') {
$('li', $n).each(function(i) {
// Determine whether to use a fade effect or a very quick
// sliding effect
delay ? $(this).stop(true, true).delay(i * 400).fadeToggle('slow') : $(this).stop(true, true).slideToggle('fast');
});
}
}
Since you call this on both the currently active tab and the newly active tab, this should stop any animations underway on the currently active tab.
See the jQuery doc on .stop() for details.
In looking at this code further, I believe it does what you literally asked for in your question (it stops the previous tab looping and starts the next tab), but I'm not sure that's actually what you want because it leaves the items in a tab only partially expanded. If that's what you want, then this will do that.
If that's not what you want, then the code will have to be modified a bit further to not only stop the currently running animations, but to put all the items for the old tab into the same state.
As I suspected, you actually want more than you asked for (per your most recent comments). You want the previously items to be hidden, no matter what state they were in previously. You can do that with this code where I changed the slideToggle() to a slideUp(). You can't use any form of toggle if the animation hasn't started yet because toggle will go the wrong way (it just reverses the state). Instead, when hiding you have to use a definitive animation that ends with the item not visible. You can use this code where I used slideUp() but you could pick something different if you wanted:
// A helper function that allows multiple LI elements to be either
// faded in or out or slide toggled up and down
function fadeOutItems(ele, show) {
var $$ = $(ele), $n = $$.next();
// Toggle the active class
$$.toggleClass('active');
// Ensure the next element exists and has the correct nodeType
// of an unordered list aka "UL"
if ($n.length && $n[0].nodeName === 'UL') {
$('li', $n).each(function(i) {
// Determine whether to use a fade effect or a very quick
// sliding effect
show ? $(this).stop(true, true).delay(i * 400).fadeToggle('slow') : $(this).stop(true, true).slideUp('fast');
});
}
}
You can see that in action here: http://jsfiddle.net/jfriend00/rzd3N/.

The problem is here.
$(this).delay(i * 400).fadeToggle('slow')
You are giving a fede effect to each element at once, by increasing delay.
It's not easy to stop it this way. The correct way to do this is to call a function which will only fade an element at a time. Then this function will be executed again at a given time interval (400 in your case), and fade the next element.
This way, passing a variable to the function, for example stopExecuting=true, will stop the effects.
Take a look at setInterval and setTimeout to achieve this.

Related

Issue with jQuery .one() method

I've got a container that includes several icons the user can hover over and be shown a block of text next to it. I'm grabbing the blocks of text from an array and have a randomize function so that they're always shown a different block of text when revisiting the page.
I ran into an issue where every time you hover over an icon, it keeps adding more array elements, because the function gets called each time you hover over the icon. So I decided to use the one() method so the function only runs once, however that's where my real issue is. Using the one() method doesn't show ANY text, and I'm pretty sure it's due to the nested function I have.
You can test this out here: http://www.evanvolmering.com/bootstrap/docs/examples/carousel/eyeswideshut.html
In the banner a video will play, and shortly into it a little icon will appear in the bottom of left of the banner. Hovering over it will show some text. When you hover over it again it adds another array item, and so on. It works, but I don't want it to keep adding array items.
10 seconds later another icon will appear to the top right, which currently has the one() method applied to it. As you can see nothing happens when you hover over it. Not sure where to go from here.
My randomize code (which I got from another StackOverflow answer):
var numRandoms = 14;
function makeUniqueRandom() {
if (!uniqueRandoms.length) {
for (var i = 0; i < numRandoms; i++) {
uniqueRandoms.push(i);
}
}
var index = Math.floor(Math.random() * uniqueRandoms.length);
var val = uniqueRandoms[index];
uniqueRandoms.splice(index, 1);
return val;
}
My code which currently 'works' but keeps adding more array items on hover:
$('img.button1').hover(function(){
$('p.trivia1').fadeIn("slow");
$( 'p.trivia1' ).append(makeUniqueRandom());
},
function(){
$("p.trivia1").stop().fadeOut("slow");
});
My code that uses one() but doesn't do anything on hover:
$('img.button2').one("hover",function(){
$('p.trivia2').fadeIn("slow");
$( 'p.trivia2' ).append(makeUniqueRandom());
},
function(){
$("p.trivia2").stop().fadeOut("slow");
});
Use mouseenter/mouseleave instead of hover
$('img.button1').on('mouseenter',function(){
$('p.trivia1').fadeIn("slow");
$( 'p.trivia1' ).append(makeUniqueRandom());
}).on('mouseleave',function(){
$("p.trivia1").stop().fadeOut("slow");
});

Randomize slides in reveal.js

I have a reveal.js presentation with approximately 300 slides. The purpose of this presentation is to cycle slides in "kiosk mode" on a monitor behind a conference booth.
To create a "kiosk mode" I've got:
Reveal.initialize({
controls: false, // hide the control arrows
progress: false, // hide the progress bar
history: false, // don't add each slide to browser history
loop: true, // loop back to the beginning after last slide
transition: fade, // fade between slides
autoSlide: 5000, // advance automatically after 5000 ms
});
This works very well, but I'd like to randomize the slides. The slides are currently just a list of 300 <section> tags in the index document - they aren't being pulled from anywhere external. Currently random: true isn't a configuration option in reveal.js.
The display order of fragments can be controlled with data-fragment-index. Is it possible to do something like that with sections? Is there a way to trick reveal.js into randomizing my slides?
My preference would be to shuffle them each time around - that is, to show slides 1-300 in random order, and then shuffle them, and show 1-300 again in a different random order. I would also be happy with just jumping to a random slide for each transition, though.
While Reveal itself does not have this functionality built in, it does let you set up event hooks to do actions when all the slides are loaded, this means JQUERY TO THE RESCUE!
You can combine Reveal's "All slides are ready" event with simple javascript to reorder all the sections, here's a simple PoC:
First import jQuery, I did this by adding it directly above the import for js/reveal.min.js:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Then, set up an event listener:
Reveal.addEventListener('ready', function(event) {
// Declare a function to randomize a jQuery list of elements
// see http://stackoverflow.com/a/11766418/472021 for details
$.fn.randomize = function(selector){
(selector ? this.find(selector) : this).parent().each(function(){
$(this).children(selector).sort(function(){
return Math.random() - 0.5;
}).detach().appendTo(this);
});
return this;
};
// call our new method on all sections inside of the main slides element.
$(".slides > section").randomize();
});
I put this right after declaring my Reveal settings and dependencies, but I'm pretty sure you can put it anywhere.
What this does is waits for all javascript, css, etc to load, manually reorders the slides in the DOM, then lets Reveal start off doing its thing. You should be able to combine this with all your other reveal settings since it's not doing anything disruptive to reveal itself.
Regarding the "shuffling them each time around" portion, the easiest way to do this would be to use another event listener, slidechanged. You could use this listener to check if the last slide has just been transitioned to, after which the next time slidechanged is called you could simply refresh the page.
You can do this with something like:
var wasLastPageHit = false;
Reveal.addEventListener('slidechanged', function(event) {
if (wasLastPageHit) {
window.location.reload();
}
if($(event.currentSlide).is(":last-child")) {
// The newly opened slide is the last one, set up a marker
// so the next time this method is called we can refresh.
wasLastPageHit = true;
}
});
As of reveal.js 3.3.0 there is now a built in helper function for randomizing slide order.
If you want the slide order to be random from the start use the shuffle config option:
Reveal.initialize({ shuffle: true });
If you want to manually tell reveal.js when to shuffle there's an API method:
Reveal.shuffle();
To shuffle the presentation after each finished loop you'll need to monitor slide changes to detect when we circle back to the first slide.
Reveal.addEventListener( 'slidechanged', function( event ) {
if( Reveal.isFirstSlide() ) {
// Randomize the order again
Reveal.shuffle();
// Navigate to the first slide according to the new order
Reveal.slide( 0, 0 );
}
} );

How does this animation work?

I'm working with cookies to run or not run a jQuery animation someone else built:
$(function () {
$('div.transitional').click(function () {
$('div.intro').removeClass('hidden');
$('div.final').off('click');
});
ShowDiv($("div.transitional.hidden")[0]);
});
function ShowDiv(target) {
target = $(target);
target.removeClass('hidden');
target.delay(500).animate({
opacity: 1.0
}, 300, 'easeInExpo', function () {
ShowDiv($("div.transitional.hidden")[0]);
})
}
I have the cookie part working, but I'm confused about the anonymous function and the "ShowDiv" function.
What is each part doing?
Functionally, the animation makes visible a series of pictures, then the whole site. I want to skip the animation and just make the whole site visible (if cookies='visited'.) I'd like to do this without rewriting the animation script.
Here's a link: http://claytonsalem.com/bottlecap.
What happens now is if you have the cookie the animation doesn't run and everything is hidden.
That script only fades in elements, one after the other. If you want to skip that, use something like this in the anonymous function (which is also known as a DOM ready handler) :
$(function() {
$('div.transitional').click(function() {
$('div.intro').removeClass('hidden');
$('div.final').off('click');
});
if(cookies === "visited") //Assuming you already have the variable set.
ShowDiv($("div.transitional.hidden")[0]);
else
$("div.transitional.hidden").css('opacity', 1).removeClass('hidden')
});
I will focus on how it works:
$("div.transitional.hidden")
This would select ALL elements with div.transitional.hidden, placing them in a list.
By placing [0] in the selector, we are picking ONLY the first element in this list.
Then, when the script begins to run, this element is modified by target.removeClass('hidden'), which removes the hidden class.
When the scripts ends, it calls the $("div.transitional.hidden")[0] selector again, but this time it will not include the previously selected element (because it no longer has the hidden class).
That's why the script show images one after the other: it removes the hidden class and selects the next remaining element.
You might refer to Karl's answer on how to show your whole site.

Trigger event on animation complete (no control over animation)

I've a scenario that requires me to detect animation stop of a periodically animated element and trigger a function. I've no control over the element's animation. The animation can be dynamic so I can't use clever setTimeout.
Long Story
The simplified form of the problem is that I'm using a third party jQuery sliding banners plugin that uses some obfuscated JavaScript to slide banners in and out. I'm in need of figuring out a hook on slideComplete sort of event, but all I have is an element id. Take this jsfiddle as an example and imagine that the javascript has been obfuscated. I need to trigger a function when the red box reaches the extremes and stops.
I'm aware of the :animated pseudo selector but I think it will need me to constantly poll the required element. I've gone through this, this, and this, but no avail. I've checked jquery promise but I couldn't figure out to use that in this scenario. This SO question is closest to my requirements but it has no answers.
P.S. Some more information that might be helpful:
The element isn't created by JavaScript, it is present on page load.
I've control over when to apply the plugin (that makes it periodically sliding banner) on the element
Most of the slideshow plugins I have used use changing classes at the end of the animation... You could extend the "addClass" method of jQuery to allow you to capture the class change as long as the plugin you use is using that method like it should:
(function($){
$.each(["addClass","removeClass"],function(i,methodname){
var oldmethod = $.fn[methodname];
$.fn[methodname] = function(){
oldmethod.apply( this, arguments );
this.trigger(methodname+"change");
return this;
}
});
})(jQuery);
I threw together a fiddle here
Even with obfuscated code you should be able to use this method to check how they are sending in the arguments to animate (I use the "options" object when I send arguments to animate usually) and wrap their callback function in an anonymous function that triggers an event...
like this fiddle
Here is the relevant block of script:
(function($){
$.each(["animate"],function(i,methodname){
var oldmethod = $.fn[methodname];
$.fn[methodname] = function(){
var args=arguments;
that=this;
var oldcall=args[2];
args[2]=function(){
oldcall();
console.log("slideFinish");
}
oldmethod.apply( this, args );
return this;
}
});
})(jQuery);
Well since you didn't give any indication as to what kind of animation is being done, I'm going to assume that its a horizontal/vertical translation, although I think this could be applied to other effects as well. Because I don't know how the animation is being accomplished, a setInterval evaluation would be the only way I can guess at how to do this.
var prevPos = 0;
var isAnimating = setInterval(function(){
if($(YOUROBJECT).css('top') == prevPos){
//logic here
}
else{
prevPos = $(YOUROBJECT).css('top');
}
},500);
That will evaluate the vertical position of the object every .5 seconds, and if the current vertical position is equal to the one taken .5 seconds ago, it will assume that animation has stopped and you can execute some code.
edit --
just noticed your jsfiddle had a horizontal translation, so the code for your jsfiddle is here http://jsfiddle.net/wZbNA/3/

How can I reveal content and maintain its visibility when mousing to a child element?

I'm asking a question very similar to this one—dare I say identical?
An example is currently in the bottom navigation on this page.
I'm looking to display the name and link of the next and previous page when a user hovers over their respective icons. I'm pretty sure my solution will entail binding or timers, neither of which I'm seeming to understand very well at the moment.
Currently, I have:
$(document).ready(function() {
var dropdown = $('span.hide_me');
var navigator = $('a.paginate_link');
dropdown.hide();
$(navigator).hover(function(){
$(this).siblings(dropdown).fadeIn();
}, function(){
setTimeout(function(){
dropdown.fadeOut();
}, 3000);
});
});
with its respective HTML (some ExpressionEngine code included—apologies):
<p class="older_entry">Older<span class="hide_me">Older entry:
<br />
{title}</span></p>
{/exp:weblog:next_entry}
<p class="blog_home">Blog Main<span class="hide_me">Back to the blog</span></p>
{exp:weblog:prev_entry weblog="blog"}
<p class="newer_entry">Newer<span class="hide_me">Newer entry:
<br />
{title}</span></p>
This is behaving pretty strangely at the moment. Sometimes it waits three seconds, sometimes it waits one second, sometimes it doesn't fade out altogether.
Essentially, I'm looking to fade in 'span.hide_me' on hover of the icons ('a.paginate_link'), and I'd like it to remain visible when users mouse over the span.
Think anyone could help walk me through this process and understand exactly how the timers and clearing of the timers is working?
Thanks so much, Stack Overflow. You guys have been incredible as I walk down this road of learning to make the internet.
If you just want to get it working, you can try to use a tooltip plugin like this one.
If you want to understand how this should be done: first, get rid of the timeout, and make it work without it. The difference (from the user's point of view) is very small, and it simplifies stuff (developing and debugging). After you get it working like you want, put the timeout back in.
Now, the problem is you don't really want to hide the shown element on the navigator mouse-out event. You want to hide it in its own mouse out event. So I think you can just pass the first argument to the navigator hover function, and add another hover to dropdowns, that will have an empty function as a first argument, and the hiding code in its second argument.
EDIT (according to your response to stefpet's answer)
I understand that you DO want the dropdown to disappear if the mouse moves out of the navigator, UNLESS its moved to the dropdown itself. This complicates a little, but here is how it can be done: on both types of items mouse-out event, you set a timer that calls a function that hides the dropdown. lets say the timer is 1 second. on both kind of item mouse-in even, you clear this timer (see the w3school page on timing for syntax, etc). plus, in the navigator's mouse-in you have to show the dropdown.
Another issue with the timer in your code is that it will always execute when mouse-out. Due to the 3 seconds delay you might very well trigger it again when mouse-over but since the timer still exist it will fade out despite you actually have the mouse over the element.
Moving the mouse back and forth quickly will trigger multiple timers.
Try to get it to work without the timer first, then (if really needed) add the additional complexity with the delay (which you must keep track of and remove/reset depending on state).
Here was the final working code, for anyone who comes across this again. Feel free to let me know if I could have improved it in any ways:
$(document).ready(function() {
var dropdown = $('span.hide_me');
var navigator = $('a.paginate_link');
dropdown.hide();
$(navigator).hover(function(){
clearTimeout(emptyTimer);
$(this).siblings(dropdown).fadeIn();
}, function(){
emptyTimer = setTimeout(function(){
dropdown.fadeOut();
}, 500);
});
$(dropdown).hover(function(){
clearTimeout(emptyTimer);
}, function(){
emptyTimer = setTimeout(function(){
dropdown.fadeOut();
}, 500);
});
});

Categories