I'm writing some code that will get executed before the DOM loads, basically, using Modernizr to get scripts. Now my issue is that I want to show a loading animation if the DOM loads and the scripts are still loading.
Modernizr is executed in the head. If I put the code to use document.getElementById in the head also, error is thrown because the DOM hasn't loaded. Now I have no idea how to solve this.
Here is the code I have so far:
<head>
<script>
var FileManager = {
IsLoading = false;
LoadRequiredFiles: function (config) {
config = config || {};
this.OnLoading = config.onLoadingCallback;
this.OnComplete = config.onCompleteCallback;
this.IsLoading = true;
if (this.OnLoading) {
this.OnLoading();
}
var self = this;
Modernizr.load([{
load: '/jquery.min.js',
complete: function () {
if (self.OnComplete) {
self.OnComplete();
}
self.IsLoading = true;
}
},
]);
}
};
var globalLoadingId = 'globalLoader';
FileManager.LoadRequiredFiles({
onLoadingCallback: function () {
document.getElementById(globalLoadingId).style.display = 'block';
},
onCompleteCallback: function () {
document.getElementById(globalLoadingId).style.display = 'none';
}
});
</script>
I used to execute this code below the <body> tag, and it worked. Now I moved it into the <head>. So I used to pass 2 callbacks to it. Now I'd rather attach events to it and handle them in the body (assuming thats where the DOM is loaded).
What I'd like to do:
<head>
<script>
FileManager.LoadRequiredFiles();
</script>
</head>
<body>
<script>
//Bind the event, not sure if this is even possible in javascript.
FileManager.OnCompleted += fileManagerCompleted;
fileManagerCompleted()
{
document.getElementById(globalLoadingId).style.display = 'none';
}
if(FileManager.IsLoading)
{
document.getElementById(globalLoadingId).style.display = 'block';
}
</script>
</body>
The page is your canvas for display. You can't show anything before it loads. It sounds more like you want a very small page to load (quickly) where you could display your progress and then your code could dynamically load/display the rest of the page with ajax calls and javascript showing progress as it goes. That's the only way to get out in front of the rest of the page load that I know of.
The only entirely reliable way to run a script that manipulates the DOM is to use the body onload event. (window.onload is popular, but not quite 100% reliable.)
There are some browsers that implement a onDocumentReady event that can be kind-of-sort-of faked in IE, but I don't recommend its use.
Using getElementById will not, by itself, throw an error if used in the head. You might be causing an error because you aren't checking the returned value, which will be null if an element with the specified id wasn't found, e.g.
var el = document.getElementById('foo');
if (el) {
// do somethig with el
} else {
// el wasn't found
}
Your problem is how to display the image only if the scripts are still loading and the page is visible. The simple answer is don't use client-side script loading, do it at the server. :-)
If you want to persist with script loading, add a class to the loading image, say "hideOnLoad". Have a callback from the last script load that sets the rule to "display: none" (just create and add style sheet with that one rule using script).
Now you just include the loading image as the first element in the body with a class of "hideOnLoad", knowing that when scripts have finished loading they will hide the image regardless of whether it (or any other element with the same class) existed at the time or not.
Related
I inherited a project where a page is loaded, then code attached to that page fills in a div with dynamically generated html - it basically fills an existing div with a html string.
This string contains links to images, etc.
I want to tell when all the images, etc have loaded- I cannot seem to get any jQuery standard checks
to work - ie I have tried attaching $(window).load() after the dynamic stuff has been inserted.
I am wondering if I should write $(window).load() dynamically as well, or if there is any other
method- ie $("#thediv").load (doesn't seem to work. I cannot query all the new html for image tags, etc-
too much stuff is being put in.
The $(window).load() doesn't work for dynamic content as far as I know. You can use the .load event for each image separated. Here's an example:
var container = $("<div> ... Stuff ... </div>");
var images = container.find('img');
var imageIdx = 0;
images.load(function(){
imageIdx++;
if (imageIdx == images.length){
callback();
}
});
Where callback() is the function that runs after all images where loaded.
From my comment: window load applies to the initial page load only. Not dynamic loading of content within it. Attach load handlers to each loaded image element and count them.
This is the shortest version I could come up with for you:
// After HTML load finishes
var img = 0;
var imgCount = $("#thediv img").load(function(){
if (++img == imgCount){
// We are done loading all images!
}
}).length;
$(window).ready() only applies to the content within the HTML file and you can only use load to attach an onload event handler to a specific image (not a container), something like this might work for you.
window.ImageLoadHandled = false;
window.ImageLoadCount = 0;
function ImageLoadHandler() {
// guard against calling this function twice
if(window.ImageLoadHandled) return;
window.ImageLoadHandled = true;
// All images have loaded || timeout expired...
}
$("#myAjaxedDiv img").load( function() {
window.ImageLoadCount++;
if( window.ImageLoadCount == $("#myAjaxedDiv img").length ) {
// all images in #myAjaxedDiv have loaded
ImageLoadHandler();
}
});
// if images haven't loaded after 5 seconds, call the code
setTimeout( ImageLoadHandler, 5000 )
The only problem with this is that if an image fails to load for whatever reason, the code will never be hit, which is quite risky. To counteract this I'd recommend creating a setTimeout() method to call your code after a few seconds timeout in-case there is a problem loading images (client or server side) and I've also taken #TrueBlueAussie's correction into account in the edit.
Your alternative is to preload the images with your HTML page
I am trying to fire a script when the contents of a div are altered, specifically when a div receives the next set of results from a js loaded paginator.
I have this:
<script script type="text/javascript" language="javascript">
document.addEventListener("DOMCharacterDataModified", ssdOnloadEvents, false);
function ssdOnloadEvents (evt) {
var jsInitChecktimer = setInterval (checkForJS_Finish, 111);
function checkForJS_Finish () {
if ( document.querySelector ("#tester")
) {
clearInterval (jsInitChecktimer);
//do the actual work
var reqs = document.getElementById('requests');
var reqVal = reqs.get('value');
var buttons = $$('.clicker');
Array.each(buttons, function(va, index){
alert(va.get('value'));
});
}
}
}
</script>
This works well when the doc loads (as the results take a few seconds to arrive) but I need to narrow this down to the actual div contents, so other changes on the page do not fire the events.
I have tried:
var textNode = document.getElementById("sitepage_content_content");
textNode.addEventListener("DOMCharacterDataModified", function(evt) {
alert("Text changed");
}, false);
But the above does not return anything.
Can what I am trying to do be done in this way? If yes where am I going wrong?
Using Social Engine (Zend) framework with MooTools.
I did this in the end with a little cheat :-(
There is a google map loading on the page that sets markers to match the location of the results. So I added my events to the end this code namely: function setMarker() {}.
I will not mark this as the correct answer as it is not really an answer to my question, but rather a solution to my problem, which is localised to the Social engine framework.
I will add a Social engine tag to my original question in the hope it may help someone else in the future.
Thanks guys.
I have a iFrame on my page thats display style is none. I have a javascript function to set the source and then set the display to block. The problem is that the iframe shows up before the content of it is loaded and thus I get a flickering effect. It goes white first and then displays the content. So I need to set the source, and when done loading all content of its source, only set its display style.
CSS & Javascript
.ShowMe{display:block;}
function(url)
{
document.getElementById('myIFrame').src = url;
document.getElementById('myIFrame').className = ShowMe;
}
It's simple as that:
<iframe onload="this.style.opacity=1;" style="opacity:0;border:none;
I would suggest you try the following:
<script type="javascript">
var iframe = document.createElement("myIFrame");
iframe.src = url;
if (navigator.userAgent.indexOf("MSIE") > -1 && !window.opera){
iframe.onreadystatechange = function(){
if (iframe.readyState == "complete"){
//not sure if your code works but it is below for reference
document.getElementById('myIFrame').class = ShowMe;
//or this which will work
//document.getElementById("myIFrame").className = "ShowMe";
}
};
}
else
{
iframe.onload = function(){
//not sure if your code works but it is below for reference
document.getElementById('myIFrame').class = ShowMe;
//or this which will work
//document.getElementById("myIFrame").className = "ShowMe";
};
}
</script>
Based on the code found here.
You could do this within the iframe:
window.onload = function () {
window.frameElement.className = 'ShowMe'; // 'ShowMe' or what ever you have in ShowMe variable.
}
Since you've tagged your question with [jquery], I assume you have executed the code within $(document).ready(). It is fired when the DOM is ready, i.e. it uses native DOMContentLoaded event (if available). window.onload is fired, when all resources on the page are ready.
I've been wondering if there is a way to prevent my functions hiding any current text/formatting.
<body>
<script type="text/javascript">
document.body.style.backgroundColor = "#F5F0EB";
var page = 0
function flip(){
document.write(page);
page++;
}
</script>
<input type=button value="Next" onClick="flip()">
</body>
When I press the button I lose the coloured background and the text appears. Is there a way to make the background stay and the text appear?
Yes, by not using document.write(). MDN explains it clearly:
Writing to a document that has already loaded without calling
document.open() will automatically perform a document.open call.
And about document.open() it says:
If a document exists in the target, this method clears it.
What you should be doing, is manipulate nodes in the DOM. For example, you could change the inner HTML of the body:
document.body.innerHTML += page;
Alternatively, you could create a text node and append it:
var textNode = document.createTextNode(page);
document.body.appendChild(textNode);
In both cases, the current document is not flushed, it is only modified.
This is happening because you're writing non-html to a document which should be html. As indicated by others, it may also be clearing your existing html. Instead of using document.write, you may want to append new elements to your document.
You can do that using the document.createElement function and document.appendChild function.
Here's what a quick Google search brought back:
http://www.dustindiaz.com/add-and-remove-html-elements-dynamically-with-javascript/
You are writing the page to document which is overwriting all over your HTML. Instead, write out the content to a DIV.
This should fix your background color problem as well.
Here is a JS Fiddle with an example.
<script type="text/javascript">
document.body.style.backgroundColor = "#F5F0EB";
var page = 0
function flip(){
document.getElementById("contentgoeshere").innerHTML=page;
page++;
}
</script>
<input type=button value="Next" onClick="flip()">
<div id="contentgoeshere">
</div>
Good luck.
As Mattias Buelens explained, document.write calls the open method, which clears the DOM, simply because -after loading- the document.close method is called automatically. There are a number of alternatives you can use, of course.
Use the innerHTML attribute of the body element, for example.
Using document.createElement and document.body.appendChild is an option, too
But perhaps it's worth taking into consideration that both methods have their downsides: using innerHTML allows you to inject badly formatted markup into the DOM, and could leave you vulnerable to XSS attacks.
Using document.createElement is slower (generally) and often requires more code, which in turn makes your script(s) less maintainable.
You could use something like this:
var flip = (function(tempDiv)
{//create a div once
var page = 0,
targetDiv = document.getElementById('contentgoeshere');//closure variables
//page is now no longer an evil global, and the target element into which
//we will inject new data is referenced, so we don't have to scan the DOM
//on each function call
return function(overridePage)//optional argument, just in case
{//actual flip function is returned here
overridePage = overridePage || page++;//new content
tempDiv.innerHTML = overridePage;//render in div that isn't part of the DOM
//any number of checks can go here, like:
if (tempDiv.getElementsByTagName('script').length > 0)
{
alert('Naughty client, trying to inject your own scripts to my site');
return;
}
targetDiv.innerHTML = tempDiv.innerHTML;
//or, depending on your needs:
targetDiv.innerHTML = tempDiv.innerText;//or the other way 'round
};
}(document.createElement('div')));
A couple of side-notes: as it now stands, this function won't work because the DOM has to be fully loaded for the closure to work. A quick fix would be this:
var flip;//undefined
window.onload = function()
{
flip = (function(tempDiv)
{
var page = 0,
targetDiv = document.getElementById('contentgoeshere');
return function(e)
{
tempDiv.innerHTML = e instanceof Event ? page++ : (e || page++);//ternary + logical or
//your [optional] checks here
targetDiv.innerHTML = tempDiv.innerHTML;
if (e instanceof Event)
{//this part is optional, but look it up if you want to: it's good stuff
e.returnValue = false;
e.cancelBubble = true;
if (e.preventDefault)
{
e.preventDefault();
e.stopPropagation();
}
}
return e;
};
}(document.createElement('div')));
//instead of using the HTML onclick attribute:
document.getElementById('buttonID').onclick = flip;//use as event handler
};
Note that window.onload causes memory leaks on IE<9, check this link for the solution to this issue
I'm fairly new to Javascript in general, and I cobbled together a small script from things found mostly on this site to try to get a small iframe to cycle through a bunch of links, which it does brilliantly. However, I also want it to stop cycling when the user clicks on the iframe or any of its contents.
Here is the code I have so far. There is only one iframe on the HTML page.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8">
<!--
var sites = [
"side/html5.html",
"side/silverlight.html",
"side/wordpress.html",
"side/mysql.html",
"side/php.html",
"side/css3.html"
];
var currentSite = sites.length;
var timerId;
var iframeDoc = $("iframe").contents().get(0);
$(document).ready(function ()
{
var $iframe = $("iframe").attr("src","side/html5.html");
timerId = setInterval(function()
{
(currentSite == 0) ? currentSite = sites.length - 1 : currentSite = currentSite -1;
$iframe.attr("src",sites[currentSite]);
}, 4000);
$(iframeDoc).bind('click', function()
{
clearInterval(timerId);
});
});
//-->
</script>
</head>
<body>
<sidebar>
<iframe name="sideframe" src="side/html5.html"></iframe>
</sidebar> ..etc..
Please help, I am trying to learn Javascript as fast as I can but as far as I can see, it should work, but it really doesn't.
Thanks for any help you can give me, it's really appreciated.
EDIT:
Okay, I've got a new script now, mostly based off of Elias' work, but it doesn't work either. I've pinned it down in Firebug and it's saying that the timerCallback.currentSite value IS updating properly, though I can't find the $iframe's src value to check for it explicitly. As far as I can tell, it is updating the variables properly, it's just not updating the iframe properly. Am I calling/setting the iframe correctly in this code? Also, is the linked in jquery library sufficient for my purposes? I'm a little lost of all these libraries to link to...
<script type="text/javascript" src="http://code.jquery.com/jquery.js"></script>
<script type="text/javascript" charset="utf-8">
<!--
var sites =
[
"side/html5.html",
"side/silverlight.html",
"side/wordpress.html",
"side/mysql.html",
"side/php.html",
"side/css3.html"
];
var $iframe = $("iframe").attr("src","side/html5.html");
function timerCallback()
{
timerCallback.currentSite = (timerCallback.currentSite ? timerCallback.currentSite : sites.length) -1;
$iframe.attr("src",sites[timerCallback.currentSite]);
$($('iframe').contents().get('body')).ready(function()
{
$(this).unbind('click').bind('click',function()
{
var theWindow = (window.location !== window.parent.location ? window.parent : window);
theWindow.clearInterval(theWindow.timerId);
});
});
}
var timerId = setInterval(timerCallback, 4000);
//-->
</script>
If I were you, I'd play it safe. Since you say you're fairly new to JS, it might prove very informative.
function timerCallback()
{
timerCallback.currentSite = (timerCallback.currentSite ? timerCallback.currentSite : sites.length) -1;
$iframe.attr("src",sites[timerCallback.currentSite]);
}
var timerId = setInterval(timerCallback, 4000);
$($('iframe').contents().get('body')).unbind('click').bind('click', function()
{
var theWindow = (window.location !== window.parent.location ? window.parent : window);
theWindow.clearInterval(theWindow.timerId);
});
Now I must admit that this code is not tested, at all. Though I think it provides a couple of things to get you on your way:
1) the interval is set using a callback function, because it's just better for tons of reasons
1b) in that Callback, I took advantage of the fact that functions are objects, and created a static var, that is set to either the length of your sites array (when undefined or 0), in both cases 1 is substracted
2) jQuery's ,get() method returns a DOM element, not a jquery object, that's why I wrapped it in $(), a new jQ obj, giving you the methods you need.
3) since you're manipulating the dom inside the iFrame, it's best to unbind events you want to bind
4) inside the iFrame, you don't have direct access to the parent window, where your interval is.
You might want to read up on how to deal with iFrames, because they can be a bit of a faff from time to time
EDIT:
David Diez is right, easiest way around this is to incorporate the binding in the timeout function:
function timerCallback()
{
timerCallback.currentSide = ...//uncanged
//add this:
$($('iframe').contents().get('body')).ready(function()
{
$(this).unbind('click').bind('click',function()
{
//this needn't change
});
});
}
In theory, this should bind the click event to the body after it has been loaded
Edit2
I've been messing around a bit, you could keep your code, as is. just add a function:
function unsetInterval()
{
window.clearInterval(window.timerId);
}
and add this to your setInterval function:
$('#idOfIframe').load(function()
{
var parentWindow = window.parent;
$('body').on('click',function()
{
parentWindow.clearInterval();
});
});
this will get triggered as soon as the iFrame content is loaded, and bind the click event and unset the timer, like you wanted to
I think your code is not working because of this
var iframeDoc = $("iframe").contents().get(0);
This could be getting the header of the iFrame because you are saving the iframeDoc value to the first child of the iframe, the head tag, actually if you have more than 1 iframe in your window iframeDoc would be undefined because $("iframe") gets all the iframes of your document.
BTW your currentSite value condition is wrong, you asign the same value for both conditions
Now I give you an example:
<iframe id="myFrame" src="http://www.google.com/"></iframe>
and the script:
<script type="text/javascript" src="http://code.jquery.com/jquery.js"></script>
<script type="text/javascript">
var sites = [
"site1",
"site2"
];
var myFrame = document.getElementsByTagName('iframe')[0];
var currentSite = myFrame.getAttribute('src');
var timerId;
var myFrameDoc = window.frames[0].document;
$(document).ready(function()
{
myFrame.setAttribute('src', 'side/html5.html');
timerId = setInterval(function()
{
//WRONGG
(currentSite == 0) ? currentSite = sites.length - 1 : currentSite = currentSite -1;
myFrame.setAttribute('src', sites[currentSite]);
$(myFrame).off("click").on("click", clearInterval(timerId));
}, 4000);
//Won't work because you are trying to get events from an inside of a iframe
//$(myFrameDoc).on("click", clearInterval(timerId));
//This may work
$(myFrameDoc).off("click").on("click", clearInterval(timerId));
});
</script>
When you try to track the events of an iframe you have to be carefull because an iframe contains a totally different document for javascript purprouses so basically you have to get inside the new document, unbind the events you need to use, and bind them again against your functionality, as #Elias points out. but be aware that you are changing constantly the src of your iframe, so if yu really need to do that you will have to separate the code that unbinds and binds again your clearInterval, and for that matter maybe $.on() could work for you.
EDIT: Calling to the iframe should work this way IF the src of the iframe is inside the same domain, with the same port and with the same protocol:
var myFrameDoc = window.frames[0].document;
I Added a new variable because we want to bind and unbind the click event to the iframe's document, not to the iframe, we use for that the window.frames collection property, but modern browsers throw an exception and denies acces if you try to access to a frame and you are not on the same domain with using the same port and the same protocol...
Additionaly the use of $.on() and $.off() instead of $.bind and $.unbind() is because the first ones are the new ones and despite we are not using it here, they are capable of watch constantly the DOM for new elements to bind if added; that could be useful to this case if this code still doesn't work. If that is the case you can still change this:
var myFrameDoc = window.frames[0].window;
and this:
$(myFrameDoc).off("click", "document").on("click", "document", clearInterval(timerId));
This will re-bind the event handler to new documents additions. Not tested but could work.