Before posting this I've browsed some existing topics but couldn't get anything helpful, I tried some things to fix this but it failed.
I'm developing a website, and today I wanted to code a basic dark mode switch in Javascript :
<script type="text/javascript">
function switchWhite() {
document.getElementById("body").className = "";
document.getElementById("menubar").className = "navbar navbar-expand-lg navbar-light bg-light";
}
function switchDark() {
document.getElementById("body").className = "dark";
document.getElementById("menubar").className = "navbar navbar-expand-lg navbar-dark bg-dark";
}
function triggerSwitch() {
if ( document.getElementById("body").className.match("dark") ) {
switchWhite();
} else {
switchDark();
}
}
</script>
<button onclick="triggerSwitch()">Switch Mode</button>
This works just fine but there is an auto-refresh on the website, which is triggered every 30 seconds and which refreshes only specific blocs (like menubar) :
<script>
setInterval(function()
{
$(document).ready(function() {
$("#menubar_refresh").load(document.URL + " #menubar");
});
}, 30000);
</script>
Which also works fine, but I cannot mix these two features because once I switch to dark mode theme, after 30 seconds (when the auto-refresh is triggered), it gets back to light mode (the original state of the page).
Though is this normal, so I tried to put the dark mode back right after the bloc refreshes, like this :
<script>
setInterval(function()
{
$(document).ready(function() {
$("#menubar_refresh").load(document.URL + " #menubar");
});
switchDark(); // here
}, 30000);
</script>
It simply doesn't work, the bloc (and only this bloc, not the whole page) still gets back to the original state (light).
I've noticed that it switches to dark mode for a few milliseconds, and gets back to the original state.
I thought that the switchDark(); call is executed at first, even before the whole function finishes, in a way that the dark mode is set and then the bloc is refreshed.
So I tried setting a variable to block the execution of switchDark(); call before everything else finished executing, but the result is the same, which makes me think that my hypothesis is wrong.
Could you please help me to figure out what the problem is here ?
I can add more code snippets if you need them.
Thanks a lot
load() is asynchronous so you need to modify any new content in the complete callback.
Something like:
$("#menubar_refresh").load(document.URL + " #menubar", switchDark);
Or more verbose version:
$("#menubar_refresh").load(document.URL + " #menubar", function(){
// new content now exists
// add logic as to which theme method to call
if(isDark){
switchDark();
}
});
So, I'm in agreement with the answer by #charlietfl. But there is a flaw in your code due to misunderstanding and misinterpretation of javascript's event handler asynchronous nature.
The code in question is this:
<script>
setInterval(function() {
$(document).ready(function() {
$("#menubar_refresh").load(document.URL + " #menubar"); });
switchDark(); // here
},
30000);
</script>
You are using $(document).ready inadvertently as an if statement not as an event handler. Apparently, your code says after 30secs, if the document is ready, refresh the document and switch to a dark theme. But actually, your code means after 30secs, attach the ready state change handler to document. In this handler, asynchronously refresh the page and [immediately] switch to a dark theme.
Now, here lies your problem:
Though there is practically one, there is no 'absolute' guarantee that the document will be ready after 30secs. Consequently, your code setup isn't one that actually executes after 30secs.
There is no practical and absolute guarantee that the page will refresh completely before switchDark() executes. Why? The code for refreshing is asynchronous. Consequently, your switchDark() function almost never executes as you expect it to (though it always executes) since you expect it to use code from the newly loaded page.
A better and more meaningful code setup is to include setInterval inside $(document).ready handler and use a callback with the load() method for any code you want executed after the load() method.
<script>
$(document).ready(function() {
setInterval(function() {
$("#menubar_refresh").load(document.URL + " #menubar", function() {
switchDark(); // here
});
}, 30000);
});
</script>
Related
I have a page that loads some external scripts via defer:
<script src="externalPlugin.js" defer></script>
I then have some code in jquery.ready that calls stuff from that script
$(function() {
$.externalPlugin();
});
So far so good, it works 99.9% of the time.
The problem
If a user navigates away from the page (hits "refresh", or just quickly navigates to another page) while the externalScript is still loading/executing - jQuery.ready still gets called (!) and obviously throws this error: $.externalPlugin is not a function or similar.
How do I fight this?
I tried adding an beforeunload handler that removes the ready binding
window.addEventListener("beforeunload",function() { $(document).unbind("ready"); });
But it does not work.
This is not a big issue (after all the user is navigating AWAY so he doesn't care about any errors), but still that's interesting behavior.
update
Yep, I know that I can wait the definition or even use <script defer onload='whatever()'> but I was wondering if there's a "global" way to fix this universally - by not calling .ready if the page is being unloaded... If not - I'd say this is actually a bug that has to be reported to jQuery team, eh?
If you have to initiate the script on your main page in dom ready, remove the defer attribute of the script :
<script src="externalPlugin.js"></script>
You can either wait the function définition :
function wait(fn, delay)
{
var i = setInterval(function(){
if(fn() && typeof fn() == "function")
{
clearInterval(i);
fn()();
}
}, delay);
}
$(document).ready(function(){
wait(function(){ return $.externalPlugin }, 100);
});
Wait till script loaded
$(function() {
waitPlugin();
});
function waitPlugin(){
if( $.externalPlugin == null ) {
setTimeout(waitPlugin,1000);
return;
}
$.externalPlugin();
}
But I would reorganize the code some way.
In my <body> I have a component that inserts a script that is supposed to run only after all the page has completely loaded:
<script>
$('<script id="smallPlacarScriptdId">\
$(window).load(function() {\
$(".main.right").hide();\
$("#rightzero").show();\
$(".comp.smallPlacard.firstChild").click(function () {\
var clicked = $(this).parent().attr("id");\
$("main.right").hide();\
$("#right"+clicked+"").show();\
});\
})\
<\script>').appendTo("body")
</script>
That's not happening and this script (1) is correctly inserted into the DOM but (2) is not working (not hiding .main.right nor showing #rightzero).
I though that by using this approach I would guarantee that it would be the same as just put this script at the bottom of the <body> but it isn't. In fact if I put it (not dynamically like this) in my page it produces the desired result.
I tried setTimeout() to validate my theory but I'm getting an error in jQuery and I'm lost.
That might be the problem:
<\script>').appendTo("body")
Browser might think you are actually closing your script tag. Change it to
</' + 'script>').appendTo("body")
Check this plunker out: http://plnkr.co/edit/Oc6yrFMdPoW2WV257CBQ?p=preview
Just use this code
<script id="smallPlacarScriptdId">
$(window).load(function() {
$("main.right").hide();
$("#rightzero").show();
$(".comp.smallPlacard.firstChild").click(function () {
var clicked = $(this).parent().attr("id");
$("main.right").hide();
$("#right"+clicked+"").show();
});
})
</script>
Sorry I didn't read you question well enough.
Javascript will allow you to access undeclared variables, so use that to your advantage. Check if a variable is set, undefined is treated as a false so no need for initialization. As soon as you enter the code just set it to true so nothing else will execute.
Hopefully this solves the problem for you, but you really should look at from the server avoiding the javascript, it will bloat the page.
<script>
if (!myScriptHasLoaded)
{
myScriptHasLoaded = true;
$(window).load(function() {
$("main.right").hide();
$("#rightzero").show();
$(".comp.smallPlacard.firstChild").click(function () {
var clicked = $(this).parent().attr("id");
$("main.right").hide();
$("#right"+clicked+"").show();
});
});
}
</script>
My question is in two parts. One overall question and one relating to it with explicit code.
The general question:
Is Javascript read into the memory on load, and by this "installed", or is it read each time I do something? How are handlers installed? Once I .click() on an element, is then the handler in memory, including the function inside it? I once had trouble with a .mouseleave(), every time I left the element, the function was installed again and it was absolute chaos...how does this work with .click() then? Is the function then every time read again or does it stay in memory? The solution to the .mouseleave() was: here.
The specific question:
I just found out, that a .click() function I use here runs on first click once, on the second click twice, etc. ? You can see it, when you open the link, open the console and click on an image. Click on it to close it again, then click on ANY(!!) image and it loads as described. Weird, huh? Also it logs in the console the boolean for .data("installed") as true, although the switch to true comes only later in code?
Hope you can help me.
jQuery code:
$('.pfiles').data("installed", false);
if (!$('.pfiles').data("installed")) {
$('.pfiles img').click(function() {
var scroll = $('body').scrollTop(),
imgThis = $(this).attr('src'),
txtThis = $(this).attr('src').split('/')[2].split('.')[0] + ".txt",
$this = $(this),
bigImgH = $(this).height(),
bigImgW = $(this).width();
$('.progress').show();
console.log($('.pfiles').data("installed"));
$('.pfiles').fadeOut(200, function() {
$('.big').fadeIn(400);
$('.frame').height($('.big').height());
});
$.ajax({
type: 'GET',
url: imgThis,
dataType: 'HTML',
success: function(data) {
$('.big').empty().append("<img src='" + imgThis + "'/>");
if (bigImgW / bigImgH <= 1.3529411176) {
$('.big img').css({'height': '100%'});
} else {
$('.big img').css('width', '100%');
}
$('body').scrollTop(0);
$('.big img').click(function(){
$('.big').fadeOut(400, function() {
$('.pfiles').fadeIn(400);
$('body').scrollTop(scroll);
$('.big').empty();
$('.frame').height($('.incontent').height());
});
});
// progress();
}
});
});
$('.pfiles').data("installed", true);
}
"HTML" code
<?php
//Profile catch
$path = 'img/profile';
$profiles = scandir($path);
natsort($profiles);
$profiles = array_reverse($profiles);
$profiles = array_diff($profiles, array('.', '..', '.DS_Store', 'txt'));
?>
<div class="incontent" style="background:white">
<div class="progress">
<div class="bardiv">
<div class="bar"></div>
<p class="bartext text"></p>
</div>
</div>
<div class="big"></div>
<div class="pfiles">
<?php
foreach ($profiles as $pfiles) {
echo '<img onclick="" src="img/profile/'.$pfiles.'">';
}
?>
</div>
I already tried the same trick with the .data(), but it keeps console.logging on every click (even true!) and it is to no avail to the multiple XHR load... (On the server it is without the .data() switch!)
Javascript code in your page is parsed and run at the time the page loads and remains in memory in the browser while the page is active. As the user interacts with the page, any event handlers that your code might have installed are then called upon demand as those events happen.
Once you install a .click() handler, it remains in place for the lifetime of that DOM element or until you remove the click handler. Until you remove it, the click handler will get called every time the item is clicked on.
You should ONLY install a .click() handler for a given function one time. If you install it multiple times, it will be called multiple times when the item is clicked. If your click handler is being called more and more times each time you click it, then your code is apparently installing another click handler each time you click on it and you would need to modify your code not to do that.
To help more specifically, we'd need to know which click handler you're having an issue with and probably need to see some of the relevant HTML.
You can simplify your event handling code by having only one constant event handler for the .big img rather than constantly creating a new one. You can do that with delegated event handling like this:
$(".big").on("click", "img", function() {
$(".big").fadeOut(400, function() {
$('.pfiles').fadeIn(400);
$('body').scrollTop(scroll);
$('.big').empty();
$('.frame').height($('.incontent').height());
});
});
Put this code before any of the code you have in your question so it is initialized once and only once.
Here's a potential problem. At the very beginning of your block of code, you have this:
$('.pfiles').data("installed", false);
if (!$('.pfiles').data("installed")) {
That means that you will ALWAYS execute the if block, even if you previously set the data to true. You can just remove the first line because the default value for $('.pfiles').data("installed") will be falsey. You don't need to initialize it. Then, when this is called subsequent times, it will respect the fact that you've set it to true later in your code.
I have probably a simple problem to solve but I don't know what's the right approach on that.
I have a div.notification bar on top of my website that is hidden by default. The bar does get an additional class of either success or warning that sets it to display:block; if needed.
So, there are two cases. Either an output message is rendered directly to the .notification bar on page-load (and it gets a class of success or warning right with it) OR the .notifcation bar is sliding down from top and is fed with json data.
Any way, the notification bar should always be visible for 10 seconds and than slide back up.
Therefore I wrote two functions that handle this bar.
var timing = 10000;
function notificationOutput(type, message) {
var note = $('.notification');
note.hide();
note.find('.message').html(message);
note.stop(true,true).slideDown().delay(timing).slideUp();
}
function notificationSlideUp(slideUp) {
var note = $('.notification');
if ( slideUp ) note.delay(timing).slideUp();
else note.stop(true,true).slideUp();
}
So the notificationOutput() function is triggered from various other functions that return json data and render that into the box.
And the notificationSlideUp() function is right now called on every page load because in my header I have this …
<script type="text/javascript">
$(document).ready(function(){
notificationSlideUp(true);
});
</script>
And there is a reason for that! Remember the case where the .notification is directly set to visible on page-load … e.g. when a user logs-in on my platform the .notification bar is immediately visibile and says "Welcome Username, you've successfully logged in".
I call the notifictaionSlideUp() function in my header to make sure the currently visible .notification bar will slideUp() again after 10 seconds.
And here occurs the problem …
Somehow this causes the entire notifcation timing and sliding up and down to be "confused". So there must happen some queuing of the slide-functions and or of the delay() function, because without the note.stop(true,true) in the notificationOutput() function the notification wouldn't slideDown() immediately if it's triggered within the first 10 seconds after the page load. That is because the notificationSlideUp() function has already triggered the slideUp() and the delay() of the object.
And the same happens to the delay. If a .notification is put out within the first 10 seconds the delay timing isn't right because the delay counter already started on page-load.
Any idea how to solve that so that it always works?
Update:
var notificationTimer;
function notificationOutput(type, message) {
var note = $('.notification');
note.hide();
note.find('.message').html(message);
note.stop(true,true).slideDown();
clearTimeout(notificationTimer);
notificationTimer = setTimeout(function() {
note.stop(true,true).slideUp();
}, 10000);
}
function notificationSlideUp(slideUp) {
var note = $('.notification');
if ( slideUp ) {
clearTimeout(notificationTimer);
notificationTimer = setTimeout(function() {
note.stop(true,true).slideUp();
}, 10000);
} else {
note.stop(true,true).slideUp();
}
}
Unfortunately, jQuery's delay() function doesn't offer any method for canceling a delay the way setTimeout() does with clearTimeout(). I'd suggest replacing your delay()s with named setTimeout()s, and writing a condition to clearTimeout() for cases in which you need to cancel/trigger a queued animation right away.
http://api.jquery.com/delay/
The .delay() method is best for delaying between queued jQuery
effects. Because it is limited—it doesn't, for example, offer a way to
cancel the delay—.delay() is not a replacement for JavaScript's native
setTimeout function, which may be more appropriate for certain use
cases.
But you can use the following 'hint' - replace delay with some animation which does not change anything. For example with .animate({opacity:1},timing)
I've got a long running method -- and I want to indicate to the user that an operation is underway. This is NOT and ajax call, so I can't use the common pattern I've used in the past to display, say a spinner, before the ajax event, then hiding it on success for example.
In my case -- I'm not making an ajax call, I'm instead doing some very heavy DOM manipulation.
I had a test example on jsFiddle.net -- would love to learn how to capture the event. At the moment, my "wait-message" div updates at the same exact time when my operation completes which is much too late :(
Complete sample code is here: http://jsfiddle.net/rsturim/97hrs/6/
Javascript (jQuery)
$(document).ready(function() {
$("#link-action").click(function(e) {
$("#wait-message").text("starting ...");
var count = longRunningMethod(1000000000);
$("#result").text(count);
$("#wait-message").text("completed.");
});
var longRunningMethod = function(countUpTo) {
var i = 0;
while (i <= countUpTo) {
i++;
}
return i;
};
});
HTML:
<div id="wait-message">
push button please
</div>
<hr />
<button id="link-action">Run Operation</button>
<hr />
<h1>Results:</h1>
<div id="result"> </div>
Here is a solution. I'm not sure if it works in all browsers, you may want to test it out in several, but I think it does:
$(document).ready(function() {
$("#link-action").click(function(e) {
$("#wait-message").text("starting ...");
// Stuff to do after a render
setTimeout(function(){
var count = longRunningMethod(1000000000);
$("#result").text(count);
$("#wait-message").text("completed.");
}, 0);
});
var longRunningMethod = function(countUpTo) {
var i = 0;
while (i <= countUpTo) {
i++;
}
return i;
};
});
Basically, the browser won't render any changes until a script finishes executing. That allows you to do things like:
Hide all divs with a certain class
Show one of those divs
In a row and the browser will never render the div that is being shown as hidden, so you won't get weird flickers or things moving around on the page.
Using setTimeout like I did, the anonymous click handler will finish executing, the browser will re-render, the the anonymous function in the setTimeout will run (immediately after the render since there is no actual delay).
Use setTimeout or setInterval instead of your while loop; a sub-second delay like 15ms should be enough to prevent your window freezing / UI locking.