JS variables getting "stuck" on new page with turbolinks - javascript

First, let me apologize for the complexity of this question, I can't really break it down more simply as the complexity might be part of the cause. But this logic makes the active link for the page light up and flicker.
Using turbolinks, on the first page everything works fine, but when we switch to another page, things start going haywire. The link that previously had the class of active from the previous page will still be saying its passed in argument of active is true, even though it no longer has a class of active.
With turbolinks disabled, everything works as expected.
function flicker(elem, active) {
var delay = Math.random() * (2000) + 130;
console.log($(elem).data('name') + ": " + active + ': ' + $(elem).hasClass('active'));
if (active == true) {
// We need specific timers for each link so each can operate with its own
// separate "thread" and be toggled independently of the others.
// Using window['foo'] to handle dynamic variable names.
window['timer-' + $(elem).data('name')] = setTimeout(function() {
window.setTimeout(function() {
$(elem).addClass('powered');
}, 20) // Controls length of flicker.
flicker(elem, true);
$(elem).removeClass('powered');
}, delay);
} else {
clearTimeout(window['timer-' + $(elem).data('name')]);
}
};
$(document).on('turbolinks:load', function() {
// Initiate the current, active link.
$('.navbar a.active').each(function() {
window['timer-' + $(this).data('name')]; // Instantiating dynamic variable.
flicker($(this), $(this).hasClass('active'));
});
});
Stay with me. So where I have the console log on line 3, on the first page, that will output as expected. For example:
nav-1: true: true
will repeatedly get output with each flicker, indicating that the first link active argument from line 1 is true and that it has a class of true.
But if I click on the second link to go to the next page, then console will repeatedly log:
nav-1: true: true
nav-2: true: true
Even though, in this case, nav-1 does not have a class of active.

Turns out, I was able to fix this just by resetting all flickering effects on page load.
$('.navbar a').each(function() {
flicker($(this), false);
});
Works with turbolinks now.

Related

How to prevent jQuery function from being initialized more than once (Drupal behaviours involved)?

My goal:
To enable a user to load a template (which contains preset jquery, html and css) to allow them to click one of five dots to trigger an animation on an image.
My issue:
When I load more than one of these templates to my page, my animation value (margin-left in this case) applies double the number of times that there is an instance of this template on the page. If my template is loaded twice, the margin-left sets to a value, jumps to the correct value, then back before finally setting on the correct value. This means that if I was to add 10 instances to the page, it would take 20 times as long to get that last value.
Before testing I thought that my code would be ok, as due to the context and .once()function, I believed it would only fire once.
All html and CSS are functioning as expected, it's just the jQuery is an issue.
My code:
(function ($) {
Drupal.behaviors.click_the_dots = {
attach: function (context, settings) {
$('.wrapper_class', context).once('click_the_dots', function () {
// Prevent other buttons from being clickable until the
// previous animation is complete.
var animationDone = false;
function clickDots(dotNum) {
$('.dot_class_num_' + dotNum).click(function () {
// Setup context, to keep animations to the container in which the dots exist.
var findElem = $(this).parent().parent().parent().find('.inner_wrapper');
// Prevent other buttons from being clickable until the
// previous animation is complete.
if (animationDone === false) {
animationDone = true;
// Find the visible image.
var animatingImage = findElem.find('.dot_class_num_active');
// Find the image that will be animating in.
var thisImageAnim = findElem.find('.dot_num_img_src_' + dotNum);
if (animatingImage.is(thisImageAnim)) {
// Can't click on the same dot again, until another dot is clicked.
animationDone = false;
return;
} else {
// Animate out the already visible image.
// Remove the visible image class as it's going to be hidden.
findElem.find('.dot_class_num_active').removeClass('dot_class_num_active');
// Animate it to hide to the left.
animatingImage.animate({
marginLeft: '-10%',
opacity: 0
}, 280, 'easeInOutQuad');
// Animate in the image associated with the dot click
// Set the image css to be further right in order to animate to left at 0.
thisImageAnim.css('margin-left', '10%').delay(200).animate({
marginLeft: '0',
opacity: 1
}, 300, 'easeInOutQuad', function () {
// Set the now visible image to the visible image.
thisImageAnim.addClass('dot_class_num_active');
}).promise().done(function () {
// Now allow the other dots to be clicked.
animationDone = false;
});
}
}
});
}
// For each of the five dots.
for (i = 1; i <= 5; i++) {
clickDots(i);
}
});}};})(jQuery);
I would like to add as many instances of this jQuery as required, but only have the function be looped through once. I'm not sure how to check if this has already been done, or how to ensure that once it has been done at least once, it shouldn't happen again.
:)
I figured out what my issue was - I was attaching the behaviour to the wrapper class of my content, i.e. $('.wrapper_class', context).once... - this meant that it attached the behaviour to each instance of this class, of which there could be many.
What I did was attach the behaviour to a higher parent element, of which I knew there would be only one instance. It attaches just once and the code works perfectly.
Thanks to the commenters above who helped me realise my issue!

Hashchange history breaks iScroll

I have created the following application using iScroll: http://preview.na-software.co.uk/Demo/FutureLearning4/#/section-0
As the user flicks left and right or clicks the arrows in the bottom corners, the application moves the content sections it updates the history by changing the hash so that the user can move back and forth to other sections and bookmark them etc.
However! If you access a hash like: http://preview.na-software.co.uk/Demo/FutureLearning4/#/section-2 and then navigate a few sections and then use the back buttons two issues happen:
1.) It scrolls to the first screen (even though currentSection is correct, and iScroll has been told the correct section).
2.) If you click the back or forward button multiple times, you stop the animation and cause it to become confused and stick in between two sections.
Looking into the code, and seeing that the correct indexes and elements are being passed to iScroll on hashchange, and console logging out the offsets, I've discovered the issue is cause because the offsets are incorrectly set... however just doing refresh() won't fix the issue, as it will then reset the position.
Can anyone see where the problem is or see a way to fix this?
I should note that this bug ONLY happens if you come into the application on a URL that isn't section 0 and then scroll around the application. This is because the offsets will be created correctly by your interactions. But if you come into a URL like section 3, then the offsets will be incorrect and so the hashchanges don't work correctly, if that makes sense.
The hashchange method looks like:
// handle hashchange events
$(window).hashchange( function(){
// read the hash to find out what the new section number is
var nums = location.href.match(/(section)-\d+/g).map(
function(x){ return +x.replace(/\D/g,"") }
);
// set currentSection
currentSection = nums[0];
// if the hashchange was called by user scrolling
if(hashCalledByScroll){
// no need to anything as they have already updated hash and scrolled
hashCalledByScroll = false;
} else {
// find the section to scrollTo
sectionToScrollTo = $('#horizontal > .sections > .section').eq(currentSection).attr('id');
// tell iscroll to scroll to the section
horizontal.scrollToElement( '#' + sectionToScrollTo, null, null, true );
}
// hide the menu on hashchange
hideMenu();
});
Testing your site, I noticed the following: Whenever I access the site via section-3 and then enter the url for section-2, the navigation would instead send me to section-0.
I believe this is the same behaviour as you are experiencing in 1).
So I investigated and came to the following analysis:
In the function horizontal.scrollToElement( '#' + sectionToScrollTo, null, null, true )
iScroll retrieves the utils.offset(el) [iScroll.js#772] for the given el-ement. This offset tells it, where the element to scroll to is.
iScroll goes through the element and all of its offsetParents to add up their offsets. This is where things are breaking: <div class="sections"> has a negative offset to its parent, which imho it should not have.
This, in turn, messes up the scrollTo-coordinates.
To see what I am talking about: document.querySelector('.sections').offsetLeft
This has all just been analysis. My approach to fix this would be to avoid scrollToElement() and instead use scrollTo():
...
} else {
// find the section to scrollTo
sectionToScrollTo = $('#horizontal > .sections > .section').eq(currentSection).attr('id');
// tell iscroll to scroll to the section
var posLeft = -$('#' + sectionToScrollTo)[0].offsetLeft;
var posTop = -$('#' + sectionToScrollTo)[0].offsetTop;
horizontal.scrollTo(posLeft, posTop, 1000);
}
// hide the menu on hashchange
hideMenu();
});
Thus, just calculate the location of the section you want to go to yourself.
About 2) I am not sure if there is much one can do about it. Jumping around quickly breaks a lot of carousels. Maybe a delayed callback to scrollEnd, verifying the validity of the current state.
Another thing I noticed is that you can accidentally stop the transition. Try to click, hold and release the cursor midway a transition - you need to be quick.
Hope this helps.
Found not best solution and it doesn't solve main problem, but it works.
$(window).hashchange(function () {
if (hashCalledByScroll) {
hashCalledByScroll = false;
} else {
var hpage = window.location.hash;
var hpage = hpage.replace('#/section-', ''); //get number of target page
var cpage = currentSection; //number of current page
var count = parseInt(hpage) - parseInt(cpage); //difference
while (count > 0) { //if difference positive: go forward count-times
horizontal.next();
count--;
}
while (count < 0) { //if difference negative: go backward count-times
horizontal.prev();
count++;
}
}
hideMenu();
});
FIDDLE

append content, scrollTo it, hide old content

I would like to implement the following animation effect;
http://jsfiddle.net/YKmu2/37/
This is the core code;
$("#c1").on( "click", function(event) {
event.preventDefault();
new_site = 'content1';
if(site == new_site) { $('[name="' + new_site + '"]').show(); }
else {
$('[name="' + new_site + '"]').insertAfter('[name="' + site + '"]');
$('[name="' + new_site + '"]').show();
$.scrollTo('[name="' + new_site + '"]', 1000);
}
setTimeout(function() {
$('[name="' + site + '"]').hide();
$.scrollTo('[name="' + new_site + '"]');
site = new_site;
}, 1005);
});
When you click on one of the links, the content associated with this link is appended to the current content and a scroll animation is executed using the scrollTo plugin.
After the scroll animation is finished, the old content is removed/hidden.
That works all fine as long as the user is clicking on a link and is waiting until everything is finished.
However when you start to click on multiple links while the animation is not finished a lot of weird things are happening.
Does someone have an idea how to fix this / make it reliable and error-proven?
Or does somebody know a jquery plugin that already implements such an effect?
Thank you!
This is a relatively common problem of animation queueing and only triggering the action if there are no animations already happening, however in this case you need to be checking if the page is scrolling or not, which is a little more abstract in a sense.
UPDATED BELOW TO FIX SCROLLING ISSUE:
I've updated your fiddle here: http://jsfiddle.net/andyface/77fr6/ to demonstrate checking to see if the page has scrolled to the top before triggering a change in content.
Current working fiddle: http://jsfiddle.net/andyface/77fr6/1/
I've also taken some liberties with making your code a little easier to manage, which I hope is actually helpful. Essentially I've made things work based on classes instead of individual IDs, so you only have to maintain one bit of code and if you need to expand it, you don't have to add more blocks of code, just a few extra lines to get the ID, however this could be made to do it all cleverly enough to not even need that.
The main changes I've made are as follows:
The links now have a class of link and content a class of content.
The IDs of the links are then used to extrapolate the content they are meant to be triggering.
I've also removed the timeout and used the scrollTo() callback feature as this will be more reliable.
Finally I added a check to see if the page had scrolled to the top before allowing the action to trigger
Hope this helps, but let me know if you need anything more explaining
UPDATE:
Seeing I missed the fact that by limiting the action by scroll position this would stop them working if the user scrolled down, I've changed how the action blocking works.
$(document).ready(function() {
// HIDE CONTENT
$('.content').hide();
var site = '',
scrolling = false;
$(".link").on( "click", function(event) {
// THIS IS JUST USED FOR TESTING TO SEE WHAT THE scrollTop() POSTION IS WHEN CONTENT IS SCROLLED - REMOVE console.log() WHEN IN PRODUCTION
console.log($(window).scrollTop());
event.preventDefault();
// ONLY RUN THE SCRIPT IF THE PAGE HAS SCROLLED TO IT'S FINAL POSITION
if( ! scrolling)
{
var new_site = null;
switch($(this).attr('id')) {
case 'c1':
new_site = 'content1';
break;
case 'c2':
new_site = 'content2';
break;
case 'c3':
new_site = 'content3';
break;
}
if(site != new_site) {
// KEEP A TRACK OF IF THE PAGE IS SCROLLING OR NOT
scrolling = true;
$('#' + new_site).insertAfter('#' + site);
$('#' + new_site).show();
$.scrollTo('#' + new_site, 1000, function() {
// CALLBACK TRIGGERED WHEN SCROLLING HAS FINISHED
$('#' + site).hide();
$.scrollTo('#' + new_site);
site = new_site;
// TELL THE PAGE IT'S OK TO DO STUFF COS WE'RE NOT SCROLLING IT ANYMORE
scrolling = false;
});
}
}
});
});
http://jsfiddle.net/andyface/77fr6/1/

jQuery click event with class selector only fires once

I'm using classed links to change FlowPlayer content. Here is a working version: http://jsfiddle.net/r9fAj/
In my actual page using the same code the first link clicked works fine. The second one does not fire the click function at all. Even if I comment out everything but the console.log()...
$('.playerLink').click( function() {
audioPlayer.unload();
initAudioPlayer();
$('#player').css('display', 'block');
$('#player').animate({"height":"50px"}, 1000);
var newClip = {'url':$(this).attr('ajax-data'),'autoplay':true};
audioPlayer.play(newClip);
console.log('playing ' + $(this).attr('ajax-data'));
});
HTML like so
Listen
Listen
<a id="flowplayer" href="/audio/episodes/09_27_2013_Happy_Hour_88509726.mp3"></a>
And the player initialized like so:
var audioPlayer;
var initAudioPlayer = function () {
$f("flowplayer", "/player/flowplayer-3.2.16.swf", {
plugins: {
controls: {
fullscreen: false,
autoHide: false,
}
},
clip: {
autoPlay: false,
url: "",
}
});
audioPlayer = $f();
};
initAudioPlayer();
Since the jsFiddle works over and over I assume something else in my page is preventing the second click() from working but the console has no errors for me.
So my question is, short of posting the whole site's code how do I pursue debugging this?
So it sounds like your .click() event handler is only being fired for the first link you click and not for additional clicks. For general debugging, you could take your page that is not working and gradually comment out / remove other part of the JS and HTML until you are able to make it work correctly. Or start with the minimal amount that is working (the fiddle) and gradually add in the rest to see when it stops working.
So this is the first site I have done where content is delivered via AJAX and internal links are caught by
$("a:not([href^='http://'])").click( function(e) {
var url = $(this).attr("href");
var title = ($(this).attr("title")) ? ': ' + $(this).attr("title") : '';
e.preventDefault();
if(url!=window.location){
window.history.pushState({path:url},title,url);
$('#contentMain').load(url);
document.title = "It's New Orleans" + title;
}
});
For some reason it does work once to click a link with the class but the second time gets preventDefault()ed.
Listen
The fix was adding [href^='#'] to not() e.g.
$("a:not([href^='http://'],[href^='#'])").click( function(e) {

Javascript looping issue

I know this is something easy that is just eluding me.
Anyway, I have a simple function that loops through a series of six images and text and hides and shows them based on which one is visible.
Issue I'm having is that when it gets to the last image, it should just start over with the first but instead it goes back to the middle image.
<script type="text/javascript">
setInterval('testAnimation()', 5 * 1000);
show = 0;
function testAnimation() {
$("#headerImage" + show).fadeOut();
$("#headerText" + show).fadeOut();
if (show == 5) {
show = 0;
}
else {
show++;
}
$("#headerImage" + show).fadeIn();
$("#headerText" + show).fadeIn();
}
</script>
So it should go like this:
Hide: 0 Show: 1
Hide: 1 Show: 2
Hide: 2 Show: 3
Hide: 3 Show: 4
Hide: 4 Show: 5
Hide: 5 Show: 0
But what happens after 4, 5 is 3, 4. Then it goes to 5, 0.
Any ideas as to why?
You can see the behavior here:
http://www.findyourgeek.com/index-copy.php
Summary: Issue was a global variable name conflict for the global variable named show between two different functions (that had nothing to do with one another) in the same page.
Seems to work fine here: http://jsfiddle.net/jfriend00/pahZx/. There must be an issue with your HTML or other code. Please show your HTML and rest of the page if you want further help.
I see you've now included a link to your actual implementation.
Edit:
OK, I can now see the issue in your actual site. I can even see it happening in the Chrome debugger. It seems to only happen the first iteration through. I don't know for sure what the issue could be, but my first guess is a global variable conflict with the variable show. I would suggest these changes:
Change the name of your show global variable to something a lot more unique. Perhaps there is a conflict somewhere with something else using that global name (I can come up with no other explanation for why the value would be changed from 5 to 3 between invocations of the function.
Add var in front of the declaration of that variable.
Get rid of the text in the setInterval call and refer to the function directly
This is what your code would look like:
<script type="text/javascript">
setInterval(testAnimation, 5 * 1000);
var mySlideshowCntr = 0;
function testAnimation()
{
$("#headerImage" + mySlideshowCntr).fadeOut();
$("#headerText" + mySlideshowCntr).fadeOut();
if (mySlideshowCntr == 5)
{ mySlideshowCntr = 0; }
else
{ mySlideshowCntr++; }
$("#headerImage" + mySlideshowCntr).fadeIn();
$("#headerText" + mySlideshowCntr).fadeIn();
}
</script>
I do see some code in your page that I think is setting a global named show:
// ( main ) index.php
otherSpotlightTimer = 0
function refreshOtherSpotlight()
{
if (!$("#login").is(":visible"))
{
if (otherSpotlightTimer<=3)
{
otherSpotlightTimer++;
$("#otherSpotlight"+otherSpotlightTimer).hide();
switch (otherSpotlightTimer)
{
case 1:show=2;break;
case 2:show=3;break;
case 3:show=1;break;
}
$("#otherSpotlight"+show).fadeIn();
}
if (otherSpotlightTimer > 3)
{
$('.qtip').each(function(){$(this).qtip("api").destroy()});
$.post("/scripts/refreshotherspotlight.php",
{ fromsubmit : "true" },
function(data)
{
if (data != "")
{
$("#otherSpotlight1").hide();
$("#otherSpotlight1").html(data);
$("#otherSpotlight1").fadeIn("slow");
//height = $("#otherSpotlight").height();
//$("#otherSpotlight").css('min-height', height+'px');
}
}
);
}
attachqTips();
}
}
Edit2:
I can verify with a breakpoint that this other code is, indeed, setting the global variable show and that is causing a problem. If you change your global variable name to something unique, it should solve the problem. This is a classic lesson in global namespacing.
I have updated the code. here is the solution: http://jsfiddle.net/wMK6H/2/
just to summarize
<div id="headerImage1" class="image">1</div>
<div id="headerImage2" class="image" style="display:none">2</div>
<div id="headerImage3" class="image" style="display:none">3</div>
<div id="headerImage4" class="image" style="display:none">4</div>
<div id="headerImage5" class="image" style="display:none">5</div>
<div id="headerImage6" class="image" style="display:none">6</div>
the js code:
setInterval(testAnimation, 5000);
show = 1;
function testAnimation()
{
$(".image").fadeOut();
if (show == 5)
{ show = 0; }
else
{ show++; }
$("#headerImage" + show).fadeIn();
$("#headerText" + show).fadeIn();
}
I have a very similar JSFiddle to one previously posted but completely independent. The point being that the code you've provided seems to work fine. I checked out your link too....
I noticed two issues. After the last headerImage/headerText (the 6th one) the animation seems to take twice as long but does eventually rotate to the correct image. So its working but seems to take a break at the end of the sequence.
There is a ghosting of the text in the headline. Instead of using fadeOut, use hide. You have 4 animations in your function. Default time for each is 400ms so you'd think that 1600ms would finish before your next interval elapses however, maybe not?
Perhaps there is an issue with the queue where something isn't finishing correctly? Maybe things are getting screwed up in the jquery animation queue because one thing is finishing before another starts so the show++ hasn't executed.
Not sure what you're experiencing. This works fine for me: http://jsfiddle.net/wMK6H/3/
Except in my example everything is shown to begin with so you will have to let it do an initial run-through all the numbers first before you see the full effect.

Categories