Here is my code which might not be the best solution of my problem, and my goal is to check the external script (script.js) every 2 seconds if it's code has changed. If it is changed, then execute it.
function connectLoader(retval) {
console.log('Executing...');
var old = document.getElementById("EPMagic");
if (old !== null) {
old.outerHTML = "";
delete old;
}
var head = document.getElementsByTagName("head")[0];
var script = document.createElement('script');
script.setAttribute('id','EPMagic');
script.setAttribute('type','text/javascript');
script.setAttribute('src','http://server.com/script.js');
head.appendChild(script);
}
setInterval('connectLoader()',2000);
The problem is that /script.js is still executed even if it is not being changed.
The code on /script.js is simply alert('Execute');
Use setAttribue to set the attributes values:
var head = document.getElementsByTagName("head")[0];
var script = document.createElement('script');
script.setAttribute('id','loadScript');
script.setAttribute('type','text/javascript');
script.setAttribute('src','http://server.ip/script.js');
head.appendChild(script);
How to determine when document has loaded(or is loading) after loading external css?
Normal page has loaded and complete at first time(with using document.onreadystatechange or document.readyStage), but after time script will call function to place a new stylesheet CSS into HTML for changing a background or images. During change stylesheet, document has still stage complete. Stage never has been changed after calling function? Why?
Timeline(example):
Visit one page : localhost/index.html
Document has stage loading
Document has stage complete
User was trying to change a theme, at this time stage hasnt been changed yet.
UPDATE: Without jQuery:)
UPDATE:
Example problem with using one image:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<script>
document.onreadystatechange = function(){
console.log(document.readyState);
};
function checkDocumentState(){
console.log(document.readyState);
return setTimeout(function(){
checkDocumentState();
}, 1000);
}
checkDocumentState();
</script>
</head>
<body>
<img src="" onclick="this.setAttribute('src','http://i.imgur.com/uRBtadp.jpg')" style="width:50px; height:50px; background-color:gray; " /> Press empty image and open new image.
</body>
</html>
FOUND ANSWER: How can I tell when a CSS background image has loaded? Is an event fired?
But hopeless .. lack of universality...
CSS is called after DOM elements are populated. This is why in the days of dial up internet, the page would load all funky looking, and then all of a sudden start to develop into the desired page bit by bit. I would suggest using Jquery instead, where you could use the following code to be able to ensure the document is fully loaded and the CSS is already implemented
$document.ready(function() {
//Insert Code here
}
Hope that helps
Answering the question, how to determine the document has loaded after dynamically loading a css file depends upon the different browser vendors out there. There is not a single sure shot way for all the browsers, but lets tackle the problem one by one for each of these browsers.
Preface
var url = "path_to_some_stylesheet.css",
head = document.getElementsByTagName('head')[0];
link = document.createElement('link');
link.type = "text/css";
link.rel = "stylesheet"
link.href = url;
head.appendChild(link);
Once that appending is done:
Internet Explorer : fires readystatechange and load.
Opera : fires load event via onload.
Chrome : Doesnt fire an event but increments document.styesheets.length only after the file has arrived.
Firefox: I was not able to reliably get anything other than mozAfterPaint.
I wrote this code, what i wanted and worked for me:
window.engineLoading = {images_count:0, images_loaded_count:0, fonts_count:0, fonts_loaded_count:0 };
document.querySelector("a").onclick = function(){ // first elemnet a
var before_stylesheets_length = document.styleSheets.length;
var before_fonts_size = document.fonts.size;
document.fonts.onloadingerror = function(a){
window.engineLoading.fonts_loaded_count++;
}
document.fonts.onloading = function(a){
window.engineLoading.fonts_count++;
}
document.fonts.onloadingdone = function(a){
window.engineLoading.fonts_loaded_count++;
}
var head= document.getElementsByTagName('head')[0];
var style= document.createElement('link');
style.rel= 'stylesheet';
style.setAttribute("href","./new_style.css");
style.onload = function(){
for(i=before_stylesheets_length; i<document.styleSheets.length; i++){
var rules = document.styleSheets[i].rules;
for(q=0; q<rules.length; q++){
var styles = rules[q].style;
for(s=0; s<styles.length; s++){
console.log(styles[s]);
if((styles[s] == "background-image" || styles[s] == "background") && styles.backgroundImage.length > 0){
window.engineLoading.images_count++;
var body= document.getElementsByTagName('body')[0];
var image = document.createElement('img');
var url = styles.backgroundImage;
url = url.replace(/^url\(["']?/, '').replace(/["']?\)$/, '');
image.src = url;
image.width = 0;
image.height = 0;
image.setAttribute("class","pace-load-style");
image.onload = function(e){
console.log(e);
window.engineLoading.images_loaded_count++;
};
image.onerror = function(e){
window.engineLoading.images_laoded_count++;
}
body.appendChild(image);
break;
}
}
}
}
};
style.onerror = function(){};
head.appendChild(style);
setTimeout(function(){
checkCurrentState();
}, 1000);
return false;
};
function checkCurrentState(){
if(window.engineLoading.images_count == window.engineLoading.images_loaded_count && window.engineLoading.fonts_count == window.engineLoading.fonts_loaded_count){
console.log("loaded"); return true;
}console.log("still loading...");
return setTimeout(function(){
checkCurrentState();
}, 1000);
};
UPDATE: Scipt has bug on localfile because of empty rule. CSSRules is empty I don't worry about it , and no need fix it.
UPDATE: Mozilla Firefox hasnt reference document.fonts.
I have the below code:
<HTML>
<HEAD>
<SCRIPT>
function myFunction(atlasTrackingURL)
{
var atlasURL = atlasTrackingURL;
if (!atlasURL) return;
//Build a cache busting mechanism
var timestamp = new Date();
var queryString = "?random=" + Math.ceil(Math.random() * 999999999999) +
timestamp.getUTCHours() + timestamp.getUTCMinutes() +
timestamp.getUTCSeconds();
//Build the final URL
atlasURL = atlasURL + queryString;
if (!document.getElementsByTagName || !document.createElement
|| !document.appendChild)
{return false;}
else
{ //Activate the JACTION call
var script = document.createElement("script");
script.type = "text/javascript";
script.src = atlasURL;
document.getElementsByTagName("head")[0].appendChild(script);
return true;
}
}
</SCRIPT>
</HEAD>
<BODY>
Test - Click Me
</BODY>
</HTML>
It works in Internet Explorer every time, but rarely works in Chrome and Firefox. Why would this be?
Can these browsers not handle a function onClick very well? Are there any other options I can take?
I am trying to help a client figure out why one of their tracking tags are not firing off all the time on click in these browsers.
Thanks,
You've got some kind of browser-dependent race condition going on, as Musa pointed out.
Try hiding the link initially, waiting for the document to load, and then adding the onclick attribute and revealing the link with javascript.
So, for example, change the link HTML to something like:
<a id="microsoft-link" href="http://www.microsoft.com" style="display: none;">Test - Click Me</a>
And add in your javascript, below the myFunction(), something like:
document.addEventListener('DOMContentLoaded', function(){
var link_elem = document.getElementById("microsoft-link");
link_elem.onclick = function() {
myFunction('http://view.atdmt.com/jaction/adoakb_PiggybackJSTest_1');
};
link_elem.style.display = 'inherit';
});
jsfiddle -- http://jsfiddle.net/m8VTy/3/
edit: I realized I may be misinterpreting what you're trying to do. What exactly is myFunction() supposed to accomplish ?
To add Google's plusone button on your website the following script tag is to be inserted (for explicit load).
<script type="text/javascript" src="https://apis.google.com/js/plusone.js">
{"parsetags": "explicit"}
</script>
It looks pretty straight forward in HTML. However I wan't to insert the script using a JS file. So I use the following code:
var e = document.createElement('script');
e.src = "https://apis.google.com/js/plusone.js";
e.id = "googplusonescript";
e.innerHTML = '{"parsetags": "explicit"}';
document.getElementsByTagName("head")[0].appendChild(e);
It works pretty awesome in all the browsers except IE because IE doesn't allow us to write the innerHTML of script tags. Anywork arounds anyone ? (I have jquery inserted in the page. So can use jquery too.)
Came across the same issue.
Under IE you should use
script.text = '{"parsetags": "explicit"}';
instead of script.innerHTML
try creating a textNode and appending it to your script tags:
var myText = document.createTextNode('{"parsetags": "explicit"}');
myScriptTag.appendChild(myText);
Try the following:
window['___gcfg'] = { parsetags: 'explicit' };
var ispoloaded;
function showpo() {
var pohd = document.getElementsByTagName('head')[0];
var poscr = document.createElement('script');
poscr.type ='text/javascript';
poscr.async = true;
poscr.text ='{"parsetags": "explicit"}'; //works on IE too
poscr.src = "http://apis.google.com/js/plusone.js";
pohd.appendChild(poscr);
ispoloaded = setInterval(function () {
//check if plusone.js is loaded
if(typeof gapi == 'object') {
//break the checking interval if loaded
clearInterval(ispoloaded);
//render the button, if passed id does not exists it renders all plus one buttons on page
gapi.plusone.go("idthatexists");
}
}, 100); //checks every 0.1 second
}
Is there anyway to listen to the onload event for a <link> element?
F.ex:
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';
link.onload = link.onreadystatechange = function(e) {
console.log(e);
};
This works for <script> elements, but not <link>. Is there another way?
I just need to know when the styles in the external stylesheet has applied to the DOM.
Update:
Would it be an idea to inject a hidden <iframe>, add the <link> to the head and listen for the window.onload event in the iframe? It should trigger when the css is loaded, but it might not guarantee that it's loaded in the top window...
Today, all modern browsers support the onload event on link tags. So I would guard hacks, such as creating an img element and setting the onerror:
if !('onload' in document.createElement('link')) {
imgTag = document.createElement(img);
imgTag.onerror = function() {};
imgTag.src = ...;
}
This should provide a workaround for FF-8 and earlier and old Safari & Chrome versions.
minor update:
As Michael pointed out, there are some browser exceptions for which we always want to apply the hack. In Coffeescript:
isSafari5: ->
!!navigator.userAgent.match(' Safari/') &&
!navigator.userAgent.match(' Chrom') &&
!!navigator.userAgent.match(' Version/5.')
# Webkit: 535.23 and above supports onload on link tags.
isWebkitNoOnloadSupport: ->
[supportedMajor, supportedMinor] = [535, 23]
if (match = navigator.userAgent.match(/\ AppleWebKit\/(\d+)\.(\d+)/))
match.shift()
[major, minor] = [+match[0], +match[1]]
major < supportedMajor || major == supportedMajor && minor < supportedMinor
This is kind of a hack, but if you can edit the CSS, you could add a special style (with no visible effect) that you can listen for using the technique in this post: http://www.west-wind.com/weblog/posts/478985.aspx
You would need an element in the page that has a class or an id that the CSS will affect. When your code detects that its style has changed, the CSS has been loaded.
A hack, as I said :)
The way I did it on Chrome (not tested on other browsers) is to load the CSS using an Image object and catching its onerror event. The thing is that browser does not know is this resource an image or not, so it will try fetching it anyway. However, since it is not an actual image it will trigger onerror handlers.
var css = new Image();
css.onerror = function() {
// method body
}
// Set the url of the CSS. In link case, link.href
// This will make the browser try to fetch the resource.
css.src = url_of_the_css;
Note that if the resource has already been fetched, this fetch request will hit the cache.
E.g. Android browser doesn't support "onload" / "onreadystatechange" events for element: http://pieisgood.org/test/script-link-events/
But it returns:
"onload" in link === true
So, my solution is to detect Android browser from userAgent and then wait for some special css rule in your stylesheet (e.g., reset for "body" margins).
If it's not Android browser and it supports "onload" event- we will use it:
var userAgent = navigator.userAgent,
iChromeBrowser = /CriOS|Chrome/.test(userAgent),
isAndroidBrowser = /Mozilla\/5.0/.test(userAgent) && /Android/.test(userAgent) && /AppleWebKit/.test(userAgent) && !iChromeBrowser;
addCssLink('PATH/NAME.css', function(){
console.log('css is loaded');
});
function addCssLink(href, onload) {
var css = document.createElement("link");
css.setAttribute("rel", "stylesheet");
css.setAttribute("type", "text/css");
css.setAttribute("href", href);
document.head.appendChild(css);
if (onload) {
if (isAndroidBrowser || !("onload" in css)) {
waitForCss({
success: onload
});
} else {
css.onload = onload;
}
}
}
// We will check for css reset for "body" element- if success-> than css is loaded
function waitForCss(params) {
var maxWaitTime = 1000,
stepTime = 50,
alreadyWaitedTime = 0;
function nextStep() {
var startTime = +new Date(),
endTime;
setTimeout(function () {
endTime = +new Date();
alreadyWaitedTime += (endTime - startTime);
if (alreadyWaitedTime >= maxWaitTime) {
params.fail && params.fail();
} else {
// check for style- if no- revoke timer
if (window.getComputedStyle(document.body).marginTop === '0px') {
params.success();
} else {
nextStep();
}
}
}, stepTime);
}
nextStep();
}
Demo: http://codepen.io/malyw/pen/AuCtH
Since you didn't like my hack :) I looked around for some other way and found one by brothercake.
Basically, what is suggested is to get the CSS using AJAX to make the browser cache it and then treat the link load as instantaneous, since the CSS is cached. This will probably not work every single time (since some browsers may have cache turned off, for example), but almost always.
Another way to do this is to check how many style sheets are loaded. For instance:
With "css_filename" the url or filename of the css file, and "callback" a callback function when the css is loaded:
var style_sheets_count=document.styleSheets.length;
var css = document.createElement('link');
css.setAttribute('rel', 'stylesheet');
css.setAttribute('type', 'text/css');
css.setAttribute('href', css_filename);
document.getElementsByTagName('head').item(0).appendChild(css);
include_javascript_wait_for_css(style_sheets_count, callback, new Date().getTime());
function include_javascript_wait_for_css(style_sheets_count, callback, starttime)
/* Wait some time for a style sheet to load. If the time expires or we succeed
* in loading it, call a callback function.
* Enter: style_sheet_count: the original number of style sheets in the
* document. If this changes, we think we finished
* loading the style sheet.
* callback: a function to call when we finish loading.
* starttime: epoch when we started. Used for a timeout. 12/7/11-DWM */
{
var timeout = 10000; // 10 seconds
if (document.styleSheets.length!=style_sheets_count || (new Date().getTime())-starttime>timeout)
callback();
else
window.setTimeout(function(){include_javascript_wait_for_css(style_sheets_count, callback, starttime)}, 50);
}
This trick is borrowed from the xLazyLoader jQuery plugin:
var count = 0;
(function(){
try {
link.sheet.cssRules;
} catch (e) {
if(count++ < 100)
cssTimeout = setTimeout(arguments.callee, 20);
else
console.log('load failed (FF)');
return;
};
if(link.sheet.cssRules && link.sheet.cssRules.length == 0) // fail in chrome?
console.log('load failed (Webkit)');
else
console.log('loaded');
})();
Tested and working locally in FF (3.6.3) and Chrome (linux - 6.0.408.1 dev)
Demo here (note that this won't work for cross-site css loading, as is done in the demo, under FF)
You either need a specific element which style you know, or if you control the CSS file, you can insert a dummy element for this purpose. This code will exactly make your callback run when the css file's content is applied to the DOM.
// dummy element in the html
<div id="cssloaded"></div>
// dummy element in the css
#cssloaded { height:1px; }
// event handler function
function cssOnload(id, callback) {
setTimeout(function listener(){
var el = document.getElementById(id),
comp = el.currentStyle || getComputedStyle(el, null);
if ( comp.height === "1px" )
callback();
else
setTimeout(listener, 50);
}, 50)
}
// attach an onload handler
cssOnload("cssloaded", function(){
alert("ok");
});
If you use this code in the bottom of the document, you can move the el and comp variables outside of the timer in order to get the element once. But if you want to attach the handler somewhere up in the document (like the head), you should leave the code as is.
Note: tested on FF 3+, IE 5.5+, Chrome
The xLazyLoader plugin fails since the cssRules properties are hidden for stylesheets that belong to other domains (breaks the same origin policy). So what you have to do is compare the ownerNode and owningElements.
Here is a thorough explanation of what todo:
http://yearofmoo.com/2011/03/cross-browser-stylesheet-preloading/
// this work in IE 10, 11 and Safari/Chrome/Firefox/Edge
// if you want to use Promise in an non-es6 browser, add an ES6 poly-fill (or rewrite to use a callback)
let fetchStyle = function(url) {
return new Promise((resolve, reject) => {
let link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.onload = resolve;
link.href = url;
let headScript = document.querySelector('script');
headScript.parentNode.insertBefore(link, headScript);
});
};
This is a cross-browser solution
// Your css loader
var d = document,
css = d.head.appendChild(d.createElement('link'))
css.rel = 'stylesheet';
css.type = 'text/css';
css.href = "https://unpkg.com/tachyons#4.10.0/css/tachyons.css"
// Add this
if (typeof s.onload != 'undefined') s.onload = myFun;
} else {
var img = d.createElement("img");
img.onerror = function() {
myFun();
d.body.removeChild(img);
}
d.body.appendChild(img);
img.src = src;
}
function myFun() {
/* ..... PUT YOUR CODE HERE ..... */
}
The answer is based on this link that say:
What happens behind the scenes is that the browser tries to load the
CSS in the img element and, because a stylesheet is not a type of
image, the img element throws the onerror event and executes our
function. Thankfully, browsers load the entire CSS file before
determining its not an image and firing the onerror event.
In modern browsers you can do css.onload and add that code as a fallback to cover old browsers back to 2011 when only Opera and Internet Explorer supported the onload event and onreadystatechange respectively.
Note: I have answered here too and it is my duplicate and deserves to be punished for my honesteness :P