The following code is working correctly for all browsers including Safari on Mac, with the exception of Safari on the iPhone.
I have a timer Object that could potentially be running that is defined like so:
//delay background change until animation is finished
lastTimer = setTimeout(function () {
$('#' + targetDiv).removeClass('open');
}, 150);
Later, I need to check if the timer is running, and if so cancel it. Here is the code I am using:
if (lastTimer != null) { clearTimeout(lastTimer); }
This is where IOS Safari generates the JavaScript Error:
"ReferenceError: Can't find variable: lastTimer".
Any ideas on why the check for null is not preventing the error, like it seems to with the other browsers?
Here is the full code for the two pertaining functions in answer to the reply below: (edited with solution)
// Class for handling the animations for the drop down menus
var dropDownMenu = {
lastTimer: null,
openMenu: function (targetDiv) {
if (targetDiv != null) {
var targetHeight = $('#' + targetDiv).height();
$('#' + targetDiv).stop(true); //stop an previous animations and clear queue
if (this.lastTimer != null) { clearTimeout(this.lastTimer); } //stop possible pending timer to prevent background change
console.log("testing b");
$('#mainNavigation #dropDownMenu ul').removeClass('open'); // make sure all closed menus show corrent bgd
$('#' + targetDiv).animate({
bottom: -(targetHeight + 30)
}, 200, 'swing');
$('#' + targetDiv).addClass('open');
}
},
closeMenu: function (targetDiv) {
if (targetDiv != null) {
$('#' + targetDiv).stop(true); //stop an previous animations and clear queue
$('#' + targetDiv).animate({
bottom: 0
}, 200, 'swing');
//delay background change until animation is finished
this.lastTimer = setTimeout(function () {
$('#' + targetDiv).removeClass('open');
}, 150);
}
}
}
When the error happens in iOS the execution stops and my test console.log immediately after does not execute.
I want to chime in on this to explain. Mobile Safari is less forgiving when checking for undefined using the simple check,
if variable
When you come across situations like this then use,
if typeof variable === "undefined"
Attaching the variable to "this" is one solution here but it's just taking advantage of the fact that definedVariable.undefinedProperty returns undefined, whereas referencing an undefined variable directly will cause the reference error in some runtime environments.
I would advise not getting into the habit of attaching to "this" if it's not necessary.
There's one other potential issue that I think needs to be stated. Safari on iOS* can be very aggressive on caching and not reloading a clean copy of your code.
e.g. if you executed one version of your code without the variable defined, then fixed the code but the error continues to show on reloading (even if you close the browser/reboot the phone)
To solve this, tap and hold (aka Long Press) the reload icon in the address bar and a menu pops up with 2 options Request Desktop Site and Reload Without Content Blockers. Choosing either one of these causes a Real reload of all of the content... which resolves the issue with any cached buggy code.
*Not just Safari. Chrome on iOS (which is built on Apple's version of WebKit) can exhibit the same issue.
Your issue seems to be that on IOS, the openMenu is being invoked first.
This means that you're trying to get the value of an undeclared variable, resulting in the ReferenceError.
Oddly, you can assign to an undeclared variable, which implicitly makes it global. So if closeMenu is called first, then the assignment happens first, making the variable implicitly declared.
The proper solution is to always declare variables before using them.
var lastTimer;
But as it turned out, you preferred to use a property on the current object instead of a variable. As such, the solution was to access the property in the methods...
this.lastTimer
This will never throw a ReferenceError, even if the property is not declared.
You have a global variable in your code. Not declaring var makes it global.
Try changing:
if (lastTimer != null)
to
if (typeof lastTimer !=="undefined" && lastTimer)
if you are sticking with it being global.
In my case Safari 13 didnt work with ES6 const hence i needed to replace it with var
Related
Why would the difference between these two lines of code create a bug that cause jquery to loop endlessly in iOS(Safari and Chrome)? The loop did not occur in any other browser.
if ($('[name="loadingTime"]') != undefined) {...
vs
if ($('.loadingTime') != undefined) {...
When we targeted by class and not name attribute the loop bug went away. Any ideas or explanations?
Upon further investigation the bug was discovered in another part of the code. Here's what happened:
loadInterval: function() {
var settimer = $('[name="loadingTime]').val();
var interval = setInterval(function() {
if (settimer == 0) {
window.clearInterval(interval);
$('[id^="interstitial-container"]').remove();
};
settimer--;
if (settimer >= 0) {
$('.ncount').text(settimer);
}
}, 1000);
}
in
var settimer = $('[name="loadingTime]').val();
we missed a closing quote after loadingTime! which the js returned as undefined and iOS didn't handle it gracefully so var settimer wasn't set to zero so whenever that function loadInterval was called it was undefined and we checked whether we needed to load based on undefined or not. in our case it wasn't and continued to load over and over always getting an undefined response but without an error. I think...
Is there any tool (preferably extension/add-on to any browser) that allows you to see all the value changes of the desired JS variable in real time?
Previously I did something like this (in pure JS):
var someVariable;
var previousValueOfSomeVariable;
var f = function() {
if (previousValueOfSomeVariable != someVariable) {
console.log(Date.now(), someVariable);
previousValueOfSomeVariable = someVariable;
}
}
var TO = setInterval(f, 100);
It did the trick, but was, of course, inefficient (in reality the function was bigger, while it required object-copy function if variable was an object and further checks)...
UPDATE
I'm aware of console tools, but I'd like to see the history of changes, like:
someVariable
0ms: undefined;
10ms: 5;
40ms: 'someothervalue';
150ms: null
etc.
(Milliseconds are given for example purposes, not necessarily required). Maybe it can be done within the DevTools console, but I don't know how.
Chrome dev tools has functionality for this.
insert the line
debugger;
right after the variable you're interested in. When your page executes and dev tools is open it will pause there and you can inspect the console.log with the value it had at that moment.
For example - say you have an onClick handler and want to see what information is passed in the event:
html:
<input onClicked={myFunc} />
JS
function myFunc(event){
console.log(event)
}
This will log the event to the console, but if you try to drill down chrome evaluates the obj when you click on it and since its long gone, its mostly null:
However if you use debugger, chrome pauses execution when it hits that and you can dig into the real event:
JS:
function myFunc(event){
console.log(event);
debugger;
}
Lets you drill down into the object as it was at the time you hit the debugger line
More info in the chrome dev tools site:
https://developers.google.com/web/tools/chrome-devtools/javascript/breakpoints
The different DevTools (tested in Chrome DevTools, Firefox DevTools and Firebug) don't offer a way to see the value changes in real time. You always have to refresh their display manually.
Firefox offers an Object.prototype.watch() function (for other browsers there is a shim), which does what you want.
Example:
test = 0;
setInterval(() => test++, 1000);
window.watch("test", (id, oldValue, newValue) => {
console.log(new Date().toLocaleTimeString(), newValue);
return newValue;
});
This will output something like this:
09:51:03 1
09:51:04 2
09:51:05 3
09:51:06 4
09:51:07 5
Note: This function only allows to watch single properties of an object, so, in order to watch all object properties you need to loop over them and call watch() for each one.
Ah yes object.watch . It isn't used very often though! Here is a more detailed post of what I think you're looking for Listening for variable changes in JavaScript or jQuery
I'm working on a responsive site with a specific set of jQuery functions for the desktop layout and mobile layout. They interfere with each other if they're both active at the same time.
By checking window.width, I'm able to deliver only the correct set of functions on page load, and I'd like to do the same on window.resize.
I've set up a stripped down Fiddle of where I'm at here: http://jsfiddle.net/b9XEj/
Two problems exist right now:
Either desktopFunctions or mobileFunctions will continuously fire on page resize, whether they have already been loaded or not.
If the window is resized beyond one breakpoint and then returned to the previous size, the incorrect set of functions will already have been loaded, interfering with the current set.
The window.resize function should behave in the following way:
Check if the correct set of functions currently active for the viewport size
If yes, return.
If no, fire correct set of functions and remove incorrect set of functions if they exist.
In the Fiddle example above, you would always see a single line, displaying either "Mobile Functions are active" or "Desktop Functions are active".
I'm a bit lost at this point, but I have tried using
if ($.isFunction(window.mobileFunctions))
to check if functions already exist, but I can't seem to get it working without breaking the overall function. Here's a fiddle for that code: http://jsfiddle.net/nA8TB/
Thinking ahead, this attempt also wouldn't take into account whether the incorrect set of functions exists already. So, I'm really hoping there's a way I can deal with this in a simpler way and solve both problems.
Any help would be much appreciated!
Following conquers 2 of the problems. The resize fires many times a second, so using a timeout will fix it firing your code constantly. It also adds a check to see if the same size is in effect, and return if it is
$(document).ready(function() {
var windowType;
var $wind = $(window);
var desktopFunctions = function() {
$('body').append('<p>Desktop functions are active</p>');
}
var mobileFunctions = function() {
$('body').append('<p>Mobile Functions are active</p>');
}
var mobileCheck = function() {
var window_w = $wind.width();
var currType = window_w < 940 ? 'mobile' :'desktop';
if (windowType == currType) {
$('body').append('<p>No Type Change, Width= '+window_w+'</p>');
return;
} else {
windowType = currType;
}
if (windowType == 'mobile') {
mobileFunctions();
} else {
desktopFunctions();
}
}
mobileCheck();
var resizeTimer;
$wind.resize(function() {
if (resizeTimer) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(mobileCheck, 300)
});
});
DEMO: http://jsfiddle.net/b9XEj/1/
Without seeing some real world differences between your 2 sets of functions it is hard to provide gudance on how to stop them conflicting. One possibility is checking the windowType in your functions
You can prevent the continuous firing by adding a delay mobileCheck. Use a setTimeout along with a checkPending boolean value.
var checkPending = false;
$(window).resize(function(){
if (checkPending === false) {
checkPending = true;
setTimeout(mobileCheck, 1000);
}
});
See here: http://jsfiddle.net/2Q3pT/
Edit
As far as the second requirement, you could use this pattern to create or use the existing one:
mobileFunctions = mobileFunctions || function() {
// mobile functions active
};
See: http://jsfiddle.net/2Q3pT/2/
I made this javascript method that I altered from an existing script that I found online that should rotate showing an indefinite number of '.testimonial' divs. The script works fine in chrome and firefox, but doesn't compile in internet explorer, unless you use f12 to start the debugging of the script. Is there a better way to write this script? I have looked online for ideas but haven't been able to find anything. I imagine that the issue is with the console.log(testimonialCount); statement, but am unsure of a better way to write it. Any help would be greatly appreciated. Thanks.
//rotate testimonials script
jQuery('.testimonial').hide();
var testimonialCount = $('.testimonial').length;
console.log(testimonialCount );
var currentItem = 0;
var timeout;
timeout = window.setTimeout((function(){switchDiv();}));
switchDiv = function() {
if (currentItem == testimonialCount - 1) {
jQuery('.testimonial').eq(testimonialCount - 1).hide();
currentItem = 0;
jQuery('.testimonial').eq(0).fadeIn();
timeout = window.setTimeout((function(){switchDiv();}),7000);
}
else {
jQuery('.testimonial').eq(currentItem).hide();
currentItem = currentItem + 1;
jQuery('.testimonial').eq(currentItem).fadeIn();
timeout = window.setTimeout((function(){switchDiv();}),7000);
}
}
When the IE developer tools are not open, there is no console object.
Therefore, calling console.log() throws an error.
To prevent that, you can check if ('console' in window) and make your own dummy console (or just don't log anything) if it isn't.
A few notes:
Add this to the very top of your script:
window.console = console || { 'log' : function(){} };
This defines window.console in case it isn't. Doesn't do anything except avoid errors.
Next take out the parenthesis around your timeout functions:
timeout = window.setTimeout(function(){ switchDiv(); },7000);
... or just simplify further:
timeout = window.setTimeout(switchDiv,7000);
I am being picky here but:
currentItem = currentItem + 1; is the same as currentItem++;
Another picky thing window.setTimeout is the same as setTimeout.
I'm relatively new to javascript so please hold it against me.
I have a bit of code which should give the user a little time to reach the submenu from the base-menu.
My problem is that the code keeps executing in a weird order.
Here is the code:
function onFocusOut() {
var tester = 0;
setTimeout(function(){menuReset(tester)},1000);
}
function menuReset(tester) {
var hoverCheck = function (event) {
alert("#navBase a has focus"); //is fired, but to late...
var tester = event.data.varTester;
var tester = 1;
};
jQuery('#navBase').on('mousemove', 'a', { varTester: tester }, hoverCheck);
jQuery('#navBase').off('mousemove', 'a', { varTester: tester }, hoverCheck);
alert(tester); //This keeps firing first, before the alert in hoverCheck
if(tester == 1){
alert("tester = 1");
return;
}
else {
jQuery('#navBase ul').hide();
jQuery('#navBase').css({'width': ''});
jQuery('#navBaseAnchor').css({
'width': '', 'color': '',
'font-size': '',
'border-bottom-style': '',
'border-bottom-width': '',
'border-bottom-color': ''});
tester = 0;
}
}
Now I keep getting the alert that "tester" is 0, before the hoverCheck function is executed (which should set "tester" to 1) and fires the alert within that function.
Could someone tell me what I'm doing wrong?
I am also fairly new to JS, but should you also be watching out for variable scope errors too?
You have declared tester locally in onFocusOut() and in menuReset(tester), and then called it as a global var outside?
From answers.oreilly.com
LOCAL - Are those that are specific to a function and only work on it.
GLOBAL - Are those that are not defined within a function and may also serve to functions unless the function has not required that
variable.
Nevermind people...
I found a way around it all.
Currently i'm setting a .focus() to the anchor involved on mouseOver. (and of course blur() on mouseleave)
Then it's real easy to check the currently focussed element using document.activeElement.
So problem solved, altough in a bit different way.
alert(tester) is the first line of code that is executing something you notice as a user. The two function calls jQuery().on() and jQuery().off() are only attaching event handlers. If you want to see a "1" in the alert, you have to quickly move your mouse before hoverCheck is executed. But probably you cannot move your hand faster than JavaScript reaching the next line, which is the alert() with tester equals "0".
A little bit different approach would be to set a Javascript timeout() to make the submenu disappear after a certain amount of time if a certain condition isn't met.
Check out this JSFiddle example
Best of luck!