Function triggers unexpectedly - javascript

I have a function which fades out a div, loads replacement HTML in, and then fades the div back in. This function is being called (correctly) when I click on the navigation bar at the top to load content into #main div. It is also called on the "about" page to load different team profiles in.
The bug occurs when changing off the default team profile. When clicking to view another profile, the function repeats every "#main" change that has happened before clicking on the profile.
The website is https://symbiohsis.github.io/. The visible bug can be reproduced by clicking "About", then clicking on another profile, e.g "B". The profile flashes but is not selected. Selecting profiles after the first on works fine.
The fade in/out & load function:
/* ajax load into $(sel) from newView (e.g. about.html) */
function loadView(sel, newView, checkOrCb, cb) {
// one of these events will be called when animations end
var animationEnd = "webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend";
// cache element to change
var elem = $(sel);
// if there is a check and callback, do the check
if (cb) {
// if the check fails, exit
if (!checkOrCb(elem, newView))
return 1;
}
// animate out
elem.addClass("animated fadeOut");
// when finished load new page and animate in
elem.one(animationEnd, function() {
// remove fadeOut animation (while keeping elem hidden)
elem.css("opacity", 0).removeClass("fadeOut");
// load new page, then fadeIn
elem.load(newView, function(text, response, ev) {
elem.addClass("fadeIn");
// remove opacity style and animations
elem.one(animationEnd, function() {
elem.css("opacity", "").removeClass("animated fadeIn");
});
// do the callback if one exists
if (cb) {
cb(elem, newView, text, response, ev);
}
else if (checkOrCb) {
checkOrCb(elem, newView, text, response, ev);
}
});
});
}
The navigation bar listeners:
$(".nav_entry").on("click", function() {
loadView("#main",
`${$(this).attr("data-link")}.html`,
function(dummy, newPage) {
return getCurrentPage() != newPage.replace(".html", "");
},
function(dummy, newPage) {
window.location.hash = newPage.replace(".html", "");
});
});
The about listeners:
$(".about_icon").on("click", function() {
var target = $(this);
loadView("#about_info", `about/${this.innerText.toLowerCase()}.md`, function() {
return !target.hasClass("about_selected");
}, function() {
$(".about_selected").removeClass("about_selected");
target.addClass("about_selected");
});
});
// set 2900 team profile as default
$("#about_info").load("about/2900.md");
$(".about_icon:contains(2900)").addClass("about_selected");
How can I fix the bug please? If anyone has any tips on JavaScript conventions that I've missed feel free to add them to your answer/comment :)

This StackOverflow post was what answered my question / fixed the bug.
Q: The transition ends ... but it works 2 times in Google Chrome.
A: This is because Chrome will fire on both thewebkitTransitionEnd and transitionend events.
I use Visual Event to see what (and how many) event listeners were attached to each object. That showed me that there was a lot of end-transition listeners hanging around on #main. I Googled "jquery one not working" and the first result was the answer which is quoted above.
The solution is to have your own alreadyFired variable to make sure it only fires once.
var animationEndFired = false;
elem.addClass("fadeIn");
// remove opacity style and animations
elem.one(animationEnd, function() {
// if only fire once
if (animationEndFired)
return;
animationEndFired = true;

Related

Why this .js script stops working and is causing a browser freeze?

I've downloaded this Drupal 8 template and the site is at www.plotujeme.sk. It has an responsive navigation with this .js script:
function sidebar_menu() {
var windowsize = jQuerywindow.width(),
jQuerynav = jQuery("nav"),
slide = {
clear: function () {
jQuerybody.removeClass('toggled');
jQuery('.overlay').hide();
jQuery('.easy-sidebar-toggle').prependTo("header");
//jQuery('#search').prependTo("body");
jQuery('.navbar.easy-sidebar').removeClass('toggled');
jQuery('#navbar').removeAttr("style");
},
start: function () {
jQuery('.overlay').show();
jQuerybody.addClass('toggled');
jQueryhtml.addClass('easy-sidebar-active');
jQuerynav.addClass('easy-sidebar');
jQuery('.easy-sidebar-toggle').prependTo(".easy-sidebar");
//jQuery('#search').prependTo("#navbar");
jQuery('#navbar').height(jQuerywindow.height()).css({
"padding-top": "60px"
});
},
remove: function () {
jQuerynav.removeClass('easy-sidebar');
}
};
if (windowsize < 1003) {
jQuerynav.addClass('easy-sidebar');
jQuery('.easy-sidebar-toggle').on("click", function (e) {
e.preventDefault();
if (jQuerybody.hasClass('toggled')) {
slide.clear();
} else {
slide.start();
}
});
/*
jQueryhtml.on('swiperight', function () {
slide.start();
});
jQueryhtml.on('swipeleft', function () {
slide.clear();
}); */
} else {
slide.clear();
slide.remove();
}
}
and:
jQuery(document).ready(function () {
"use strict";
sidebar_menu();
jQuery(window).resize(function () {
sidebar_menu();
});
});
Problem is, that if I open responsive navigation by clicking on hamburger button, it works several times and then it stops working, the page and a browser freezes or is unresponsive for a long time. I also noticed that (even in template preview) sometimes it does not work at all and nothing happens after clicking hamburger icon. When I resize window multiple times sometimes it works sometimes not.
Do you see any error in the script that could possibly cause this problem?
Update: I also tried to use jQuery('.easy-sidebar-toggle').off("click"); just before jQuery('.easy-sidebar-toggle').on("click", function() {...}); but got the same results.
jQuery(window).resize(function () {
sidebar_menu();
});
As a result, whenever sidebar_menu function changes the window size, this function is called again and again, like a recursion, hence the freezing
I think the reason might be the following lines in the resize handler:
jQuerynav.addClass('easy-sidebar');
jQuery('.easy-sidebar-toggle').on("click", ...
They are run every time the window is resized by even one pixel, so a few dozen times a second if you drag the window border. Not sure about the first line, whether it adds the class over and over, but the second line certainly adds an event handler multiple times and fills up the stack. That's the reason your browser freezes. It just can't process the hundreds of registered events.
Just a guess, though.

jQuery AUTOMATIC scroll (no button click) on document ready

I am creating a chat, everything works perfectly, it scrolls down when i click the "Send" button, but I want it to scroll all the way down when the document is ready. I have done this by adding the scrolling function to setInterval, but the problem with that is that the user basically cant scroll up to see previous chat messages because he gets scrolled down every 0.1 seconds. My code is:
$(function () {
//$("#messages").scrollTop($("#messages").prop("scrollHeight")); Doesnt work at all
function updateChat(){
$("#messages").load('chat/ajaxLoad.php');
//$("#messages").scrollTop($("#messages").prop("scrollHeight")); This works but the user cannot scroll up anymore
}
setInterval(function () {
updateChat();
}, 100);
$("#post").submit(function(){
$.post("chat/ajaxPost.php", $('#post').serialize(), function (data) {
$("#messages").append('<div>'+data+'</div>');
$("#messages").scrollTop($("#messages").prop("scrollHeight")); // This works but only when the user presses the send button
$("#text").val("");
});
return false;
});
});
Add this to your code.
var chat = $("#messages").html();
setInterval(function () {
updateChat();
if(chat !== $("#messages").html()){
$("#messages").scrollTop($("#messages").prop("scrollHeight"));
chat = $("#messages").html();
}
}, 2000);
I think this should work (didnt test), but there are some better ways you can optimise this like not saving the whole .html() into a variable.
The idea here is that it checks if the content is changed every 2 seconds. If it is, it scrolls down.
I see what's your problem and I have 2 ideas for you :
You scroll down only when a new message is post, for example with an Ajax request you could check if number of messages is > in compare with the last 0.1s, if yes you scroll if not you ignore.
You scroll down every 1-2s only if the scroll is at the maximum bottom position. If the scroll is not at the maximum you do not scroll. I feel this solution is better.
You need to seperate the actions on your application,
also you missed many checks that can make the application work properly and will
make it easy to maintain.
How i suggestion the code will look:
$(function () {
function updateMessages(){
var messages_before_update = $("#messages").html();
$("#messages").load('chat/ajaxLoad.php');
var message_after_update = $("#messages").html();
if(messages_before_update !== message_after_update){
scrollToBottom();
}
}
function scrollToBottom(){
var scroll_height = $("#messages").prop("scrollHeight");
var scroll_top = $("#messages").scrollTop();
if(scroll_height !== scroll_top){
$("#messages").scrollTop($("#messages").prop("scrollHeight"));
}
}
function addMessage(message){
$("#messages").append('<div>' + message + '</div>');
}
setInterval(updateMessages, 100);
$("#post").submit(function () {
$.post("chat/ajaxPost.php", $('#post').serialize(), function (data) {
addMessage(data);
scrollToBottom();
$("#text").val("");
});
return false;
});
});

How do I animate each individual HTML tag to load in a sequence via jQuery?

I am polishing a jQuery script to make a nice page-changing effect in my project. Here is my script...
$(document).ready(function() {
// Start-Up Page Load (Cover, ToC, etc.)
$('#content').load('pages/page1.htm');
// Navigating Pages
$(document).on('click', 'a', function(e) {
if ($(this).attr('target') == '_blank') {
return true;
}
else {
e.preventDefault();
}
var ahref = $(this).attr('href');
var $c = $('#content');
var $cc = $('#content_container');
$c.fadeTo('fast', 0.0, function() {
$cc.animate({height: 'hide'}, 500);
$c.load(ahref + '#content/', function(){
$cc.animate({height: 'show'}, 500, function(){
var x = $($c.children()[1]).children();
var waittime = 0;
for (item in x) {
item.delay(waittime).fadeTo('slow', 1.0);
waittime += 500;
}
// Failsafe that loads the entire page contents, in case the element-by-element animation does not work.
// $c.fadeTo('slow', 1.0);
});
});
});
return false;
});
});
What I need to do is go through each and every specific tag in the remote content. I do this first by setting the var waittime to a number to help adjust the delay times because the elements tended to load at the same time in earlier tries, then (supposed to) loop through each HTML element and fade each one in, one at a time.
I have used the .each() and .children() methods and got bad results on page load. Through trial and error, I found that this line:
$($c.children()[1]).children()
Fetches each top-level HTML tag as an object. But when I tried running through that code using either item or $(item), the console reads an error.
I don't think it gets text-nested elements like <span>. But if it did, doing so will interrupt the flow of the animation, so I don't know how jQuery can nitpick that out (though I noticed it doesn't, because it's reading the top-level). In other words, it should do the whole <p>, <span> inside it included, at the same time.
Taking the above into account, I noticed it did everything at the first level. It should be able to do whole elements nested inside whole elements individually, such as the <dt> and <dd> inside a <dl>.
How do these HTML objects being fetched work? For some reason the console is telling me things like .fadeTo() is not a function.
I used setTimeout with each to animate elements in sequence. my code is like below:
var panels = $(".panel");
panels.each(function(i) {
var $panel = $(this);
setTimeout(function () {
$panel.css("display", "block")
.addClass('fadeInDown animated')
.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
$panel.removeClass('fadeInDown animated');
});
}, i * 200);
});
you can use CSS3 animations and add classes to elements.

Interested in tracking the events that get fired while user browses a site

I was wondering if it's possible to do as follows:
In my site I am using a lot of jQuery plugins that fire different events that I don't know about.
Is there a way - a program, a browser add-on, or something else - that I can browse the site and get a list of the exact javascript events that were fired with every click?
For example, I have a jQuery plugin that when I right click on any element a custom contextMenu shows and then when I click on one of the options other things come up. I need to know exactly what Javascript basic events were fired:
$('input:submit, button:submit').rightClick(function (e) {
$(this).contextMenu('contextMenuInput', {
'Capture This': {
click: function (element) { // element is the jquery obj clicked on when context menu launched
doSomething();
},
klass: "kgo" // a custom css class for this menu item (usable for styling)
},
'Create List': {
click: function (element) {
},
klass: "kfilter kdisabled"
},
'Collect Data': {
click: function (element) {
},
klass: "kcapture kdisabled"
}
},
{ disable_native_context_menu: true }
);
});
Does anyone have any idea?
You can use the following code to show events currently bound ....
here is an example of using this code : http://jsfiddle.net/manseuk/CNjs3/
(function($) {
$.eventReport = function(selector, root) {
var s = [];
$(selector || '*', root).andSelf().each(function() {
var e = $.data(this, 'events');
if(!e) return;
s.push(this.tagName);
if(this.id) s.push('#', this.id);
if(this.className) s.push('.', this.className);
for(var p in e) s.push('\n', p);
s.push('\n\n');
});
return s.join('');
}
$.fn.eventReport = function(selector) {
return $.eventReport(selector, this);
}
})(jQuery);
Use it like this ->
// all events
alert($.eventReport());
// just events on inputs
alert($.eventReport('input'));
// just events assigned to this element
alert($.eventReport('#myelement'));
// events assigned to inputs in this element
alert($.eventReport('input', '#myelement'));
alert($('#myelement').eventReport('input')); // same result
// just events assigned to this element's children
alert($('#myelement').eventReport());
alert($.eventReport('*', '#myelement'); // same result
Updated as per comments
If you want to see what is bound to these events this is an excellent tool -> http://www.sprymedia.co.uk/article/Visual+Event
It's not quite what your looking for, but with firebug, you can log events for a given DOM element.
You can do this by right clicking on the element in the html tab and clicking log events:
The event log:
You may also find the firebug extension "EventBug" useful:
http://getfirebug.com/wiki/index.php/Firebug_Extensions#Eventbug
http://www.softwareishard.com/blog/firebug/eventbug-alpha-released/

jQuery - Disable Click until all chained animations are complete

Question
The solution below is intended to slide down the groupDiv displaying div1 and enough space for div2 to slide in. It's all achieved by chaining the animations on the #Link.Click() element.
It seems to bug out, though, when the link is clicked rapidly. Is there a way to prevent this? By perhaps disabling the Click function until the chained animations are complete? I currently have checks in place, but they don't seem to be doing the job :(
Here's the code i'm using:
Custom animate functions.
//Slide up or down and fade in or out
jQuery.fn.fadeThenSlideToggle = function(speed, easing, callback) {
if (this.is(":hidden")) {
visibilityCheck("show", counter--);
return this.slideDown({duration: 500, easing: "easeInOutCirc"}).animate({opacity: 1},700, "easeInOutCirc", callback);
} else {
visibilityCheck("hide", counter++);
return this.fadeTo(450, 0, "easeInOutCirc").slideUp({duration: 500, easing: "easeInOutCirc", complete: callback});
}
};
//Slide off page, or into overflow so it appears hidden.
jQuery.fn.slideLeftToggle = function(speed, easing, callback) {
if (this.css('marginLeft') == "-595px") {
return this.animate({marginLeft: "0"}, speed, easing, callback);
} else {
return this.animate({marginLeft: "-595px"}, speed, easing, callback);
}
};
In the dom ready, i have this:
$('#Link').toggle(
function() {
if (!$("#div2 .tab").is(':animated')) {
$("#GroupDiv").fadeThenSlideToggle(700, "easeInOutCirc", function() {$('#div2 .tab').slideLeftToggle();});
}
},
function(){
if (!$("#groupDiv").is(':animated')) {
$('#div2 .tab').slideLeftToggle(function() {$("#groupDiv").fadeThenSlideToggle(700, "easeInOutCirc", callback);} );
}
}
);
HTML structure is this:
<div id="groupDiv">
<div id="div1">
<div class="tab"></div>
</div>
<div id="div2">
<div class="tab"></div>
</div>
</div>
The issue is your first animating the div#GroupDiv so your initial check if (!$("#div2 .tab").is(':animated')) will be false until the groupDiv has finished animated and the callback is fired.
You could maybe try
if (!$("#div2 .tab").is(':animated') && !$("#GroupDiv").is(':animated'))
however I doubt this will cover really quick clicking. The safest is to unbind the event using
$(this).unbind('toggle').unbind('click');
as the first line inside the if and you can then do away with the animated check. The downside to this is you will have to rebind using the callback you are passing through to your custom animation functions.
You can easily disable your links while animation is running
$('a').click(function () {
if ($(':animated').length) {
return false;
}
});
You can of course replace the $('a') selector to match only some of the links.
Animating something that can be clicked repeatedly is something to look out for because it is prone for errors. I take it that you Problem is that animations queue up and are executed even when you have stopped clicking. The way I solved it was to use the stop() function on an Element.
Syntax: jQuery(selector).stop(clearQueue,gotoEnd) //both parameters are boolean
More Info
When I click on a button, I first stop the animation and clear the Queue, then i proceed to define the new animation on it. gotoEnd can stay false (default value) but you can try tochange it to true if you want, you might like the result.
Usage Example: jQuery('button#clickMe').stop(true).animate({left:+=10}).
you can put this first thing inside the click event
$(element).css({ "pointer-events":"none"});
, and this in the callback function of the animation
$(element).css({ "pointer-events":"auto"});
you can unbind... but this should work too:
if (!$("#div2 .tab").is(':animated') && !$("#GroupDiv").is(':animated')) return;
I have recently made an AJAX jQuery plugin, featuring plenty of animation. The workaround to the AJAX animation bug that I have found is as follows.
$(options.linkSelector).click(function(e){
if ($("#yourNav").hasClass("disabled")) {
return false;
} else {
e.preventDefault();
$("#yourNav").addClass("disabled")
// Prepare DOM for new content
$(content).attr('id', 'content-old');
$('<div/>', {id: 'ajMultiLeft'}).css({'top': '100%'}).insertAfter('#content-old');
// Load new content
$(content).load(linkSrc+ ' ' +options.content+ ' > *', function() {
// Remove old content
$(content).animate({top: '100%'}, 1000, function(){
$(content-old).remove();
$("#yourNav").removeClass("disabled")
});
setBase();
}
What this does is makes the click event for each link respond to nothing whilst the parent div has a class of disabled. The disabled class is set by the function upon initial click and removed via a callback on the final animation.

Categories

Resources