Correct access of dynamic iFrame in IE - javascript

Plain JS - please no jQuery suggestions - it is for a bookmarklet that needs to use as plain JS as possible.
I hope someone KNOWS the answer since I cannot reliable create a fiddle.
This code will run in the scope of the page it is inserted in - it works perfectly in Fx6-9, safari and latest Chromes on Windows XP and OSX - Only IE gives me undefined when I try to access the iFrame
var zContainer = document.getElementById('zContainer');
if (zContainer==null) {
zContainer = document.createElement("div");
zContainer.id="zContainer";
document.body.appendChild(zContainer);
}
var zStuff = {}; // minimise window var footprint
zStuff.html = '<body>Hello</body>';
if (!zFrame) { // did we already have one?
zFrame = document.createElement("iframe");
zFrame.id="zIframeId"; zFrame.name="zIframeName"; zFrame.frameBorder="0";
zIFS = zFrame.style; zIFS.border="0"; zIFS.width="500px"; zIFS.height="500px"; zIFS.backgroundColor="white"; zIFS.display="block";
zContainer.appendChild(zFrame); // append to div
zFrame = window.frames["zIframeName"]; // undefined in IE8 !!!!!
// zFrame = document.getElementById("zIframeId"); // undefined in IE8 !!!!!
zFrame.src="javascript:'<body></body>'"; // initialise body
zFrame.document.write(zStuff.html); // or zFrame.contentDocument.write
zFrame.document.close();
// zFrame.document.body.innerHTML=zStuff.html; // also does not work
// zFrame.src="javascript:'"+zStuff.html+"'"; // alternative method - either one works in Fx/Chrome
}
Thanks for any hints and for not voting this down. I hope the SO community will be as
helpful to me as I have been to it over the last year and a half...
Update - since the code I posted had some remnants of desperation, I changed it to
if (!zFrame) { // did we already have one?
zFrame = document.createElement("iframe");
zFrame.id="zIframeId"; zFrame.name="zIframeName"; zFrame.frameBorder="0";
zIFS = zFrame.style; zIFS.border="0"; zIFS.width="500px"; zIFS.height="500px"; zIFS.backgroundColor="white"; zIFS.display="block";
zContainer.appendChild(zFrame); // append to div
zFrame.src="javascript:'<body></body>'"; // initialise body
zFrame.document.write(zStuff.html); // or zFrame.contentDocument.write
zFrame.document.close();
}
the above now replaces the page I am on with the code in the zStuff.html
instead of replacing only the iFrame content - it also broke in Fx
Now I have to do this in Fx which IE also does not mind but still replaces the window and not the iFrame
if (!zFrame) {
zFrame = document.createElement("iframe");
zFrame.scrolling="no"; zFrame.id="zIframeId"; zFrame.name="zIframeName"; zFrame.frameBorder="0";
zIFS = zFrame.style; zIFS.border="0px none"; zIFS.width="549px"; zIFS.height="510px"; zIFS.backgroundColor="white"; zIFS.display="block";
zDRContainer.appendChild(zFrame);
zFrame.src="javascript:'<body></body>'";
setTimeout(function() {
var zFrame = window.frames["zIframeName"]; // this is needed for the document.write
zFrame.document.write(zStuff.html);
zFrame.document.close();
},100);
}

The hack I suggested in chat:
var a = setInterval( function(){
try{
zFrame.contentWindow.document.write(zStuff.html);
zFrame.contentWindow.document.close();
clearInterval(a);
}
catch(e){}
}, 10 );
Since it is not known when IE allows accessing contentWindow properties, this will keep trying until it is allowed.

Related

Can I use javascript timer in a mobile browser

I am using IE in a mobile browser. I add a javascript function to a button that when the User clicks it says 'hello'.
This works.
I then add a timer.
On a desktop browser it works.
it does not work on my mobile browser.
This is my code. (note I Had just tried placing an alert('hi'); in the swapImages() and that did not work either.
var div = document.getElementById('divImage');
var imgCached = document.getElementById('imgCached');
document.execCommand("BackgroundImageCache", false, true);
function OnImgLoaded() {
img1.src = imgCached.src;
}
var interval = 30;
var _timer;
var _index = 0;
function test() {
_timer = setInterval('swapImages()', interval);
}
function swapImages() {
imgCached.onload = OnImgLoaded();
imgCached.src = 'my server url~/' + '0000' + _index + '.jpg';
_index = _index + 1;
if (_index == 10) {
_index = 0;
clearTimeout(_timer);
}
}
UPDATE!!
I had been runningit on Chrome desktop and not IE. I am mow testing it in IE desktop. I get the same erro so now I can debug.
The error is this line:
img1.src = imgCached.src;
It tells me:
Unable to get property 'src' of undefined or null reference
I have changed the code to:
var imgLive = document.getElementById('imgLive'); (I have renamed the img control)
function OnImgLoaded() {
imgLive.src = imgCached.src;
}
I get the same error.
I look in Source and the control is correctly named..
Thanks
i'm not sure that the following line is valid in your mobile phone:
imgCached.src = 'http://127.0.0.1/Cloud/test/' ...
the timer executes successfully, but the image doesn't get the proper src since the device doesn't run a web server on it (unless you configured one).
and to answer your topic question, yes- you can use javascript timers in mobile browsers just like desktop browsers.
hope that helped.
First of all: Do you ever call the test function, that starts the timer?
Second: Maybe it's really document.execCommand("BackgroundImageCache", false, true), that fails - it may not be enabled in the mobile version of IE that you are using. You can check if it's enabled using the queryCommandEnabled function, see more here: http://msdn.microsoft.com/en-us/library/ie/ms536676(v=vs.85).aspx

setAttribute iFrame name and Chrome

Here is a simple setAttribute for an iFrame to append a name.
Issue is that the below code works great in all browsers except Chrome.
<iframe id="frame" frameborder="0" src="http://website.ca/"></iframe>
Parent javascript:
(function() {
var newFrameName = "hello";
document.getElementById("frame").setAttribute("name", newFrameName);
})();
in the iFrame:
var iframeName = window.name;
alert (iframeName );
Alert calls "Hello" in all browsers, Chrome calls "frame" -- which is the ID of the iFrame.
looking at source (through the elements inspector), in Chrome, I see the correct name for the iFrame: name="Hello" ... but the alert calls the id.
why would that be? I'm i missing anything?
Creating iframe by createElement fixes the issue:
function createFrame() {
frame= document.createElement("iframe");
frame.setAttribute("src", "https://website.ca/");
frame.setAttribute("name", "Hello");
frame.setAttribute("id", "frame");
frame.frameBorder = 0;
frame.style.width = 100 + "%";
frame.style.height = 2300 + "px";
document.getElementById("iframeHolder").appendChild(frame);
}
createFrame();
<div id="iframeHolder"> </div>
Bit of a hack, but it works for this situation.
6 and a half years later I ran into the same problem.
It seems that Chrome sets the window.name property once and doesn't update it when the enclosing iFrame's name (either property or attribute) is updated.
Setting the window.name directly does work however.
So, changing the OP's example from:
(function() {
var newFrameName = "hello";
document.getElementById("frame").setAttribute("name", newFrameName);
})();
into
(function() {
var newFrameName = "hello";
document.getElementById("frame").name = newFrameName; // keep for other browsers
document.getElementById("frame").contentWindow.name = newFrameName;
})();
works...

Jquery attr('src') undefined in IE 8 (works in FF)

This has gotten so far,that I will sum up what we found out:
Inside the event handler the attribute src cannot be read in IE8 (FF works fine), neither with jQuery nor with usual javascript
The only way to get the data was to get it outside the handler, write it to an array and read it afterwards from the inside of the handler
But there was still no possibility to write to src (neither jQuery nor javascript worked - only for IE 8)
I've got it working by writing the img elemts themselves to the document, but the reason behind this problem is no solved
The snippet we have is used twice.
The old code
<script type="text/javascript">
jQuery(document).ready(function() {
//...
//view entry
jQuery('.blogentry').live('click',function(){
// Get contents
blogtext = jQuery(this).children('.blogtext').html();
blogauthor = jQuery(this).children('.onlyblogauthor').html();
blogtitle = jQuery(this).children('.blogtitle').html();
profileimage = jQuery(this).children('.profileimage').html();
imgleft = jQuery(this).children('.Image_left').attr('src');
imgcenter = jQuery(this).children('.Image_center').attr('src');
imgright = jQuery(this).children('.Image_right').attr('src');
// Write contents
jQuery('#bild_left').attr('src', imgleft);
jQuery('#bild_center').attr('src', imgcenter);
jQuery('#bild_right').attr('src', imgright);
jQuery('.person').attr('src', profileimage);
jQuery('#g_fb_name').html(blogauthor);
jQuery('#g_titel').html(blogtitle);
jQuery('#g_text').html(blogtext);
//...
});
//...
// Change entry
jQuery('.blogentry').each(function(){
entryindex = jQuery(this).attr('rel');
if (entry == entryindex)
{
// The following works fine (so 'children' works fine):
blogtext = jQuery(this).children('.blogtext').html();
blogauthor = jQuery(this).children('.onlyblogauthor').html();
blogtitle = jQuery(this).children('.blogtitle').html();
profileimage = jQuery(this).children('.profileimage').html();
// This does not work - only in IE 8, works in Firefox
imgleft = jQuery(this).children('.Image_left').attr('src');
imgcenter = jQuery(this).children('.Image_center').attr('src');
imgright = jQuery(this).children('.Image_right').attr('src');
//alert: 'undefined'
alert(jQuery(this).children('.Image_center').attr('src'));
//...
}
}
//...
});
</script>
The new code
Please see my own posted answer for the new code.
UPDATE:
This does not work if called inside of the click event!!!
jQuery('.Image_left').each(function(){
alert(jQuery(this).attr('src'));
});
SOLUTION TO GET THE IMAGE DATA:
relcounter = 1;
imgleft_array = new Array();
jQuery('.Image_left').each(function(){
imgleft_array[relcounter] = jQuery(this).attr('src');
relcounter++;
});
relcounter = 1;
imgcenter_array = new Array();
jQuery('.Image_center').each(function(){
imgcenter_array[relcounter] = jQuery(this).attr('src');
relcounter++;
});
relcounter = 1;
imgright_array = new Array();
jQuery('.Image_right').each(function(){
imgright_array[relcounter] = jQuery(this).attr('src');
relcounter++;
});
//... inside the eventhandler (entryindex = 'rel' of blogentry):
imgleft = imgleft_array[entryindex];
imgcenter = imgcenter_array[entryindex];
imgright = imgright_array[entryindex];
This works because it is not called inside the event handler and the sources are saved beforehand
BUT! I still cannot write the data, which is my aim:
jQuery('#bild_left').attr('src', imgleft);
jQuery('#bild_center').attr('src', imgcenter);
jQuery('#bild_right').attr('src', imgright);
UPDATE!!!
This is just crazy, I tried to write the data via usual javascript. This also works in FF, but no in IE8. Here really is some serious problem witt the attribute src:
document.getElementById('bild_left').src = imgleft;
document.getElementById('bild_center').src = imgcenter;
document.getElementById('bild_right').src = imgright;
alert(document.getElementById('bild_left').src);
This works in FF, but not in IE8, the attribute src remains undefined after writing! This seems to be not a jQuery problem at all!
children looks for immediate child elements only where as find looks for all the elements within it until its last child element down the dom tree. If you are saying find is working that means the element you are looking is not its immediate children.
Try to alert this jQuery(this).children('#Image_center').length see what you get.
FYI. Even when any element is not found jQuery will return an emtpy object it will never be null. So alert an emtpy object will always give you [object Object]. You should alwasy check for the length property of the jQuery object.
Try this
alert(jQuery(this).find('#Image_center').length);//To check whether element is found or not.
Bing Bang Boom,
imgright = jQuery(".Image_right",this).attr('src');
And why don't you easily use one working?
alert(jQuery(this).children('#Image_center').attr('src'));
change children to find
alert(jQuery(this).find('#Image_center').attr('src'));
It is probably the easiest solution, and when it work, why wouldn't you use it?
the problem is not in the attr('src') but in something else. The following snippet works in IE8:
<img id="xxx" src="yrdd">
<script type="text/javascript">
alert($('#xxx').attr('src'));
</script>
But if you for example change the the text/javascript to application/javascript - this code will work in FF but will not work in IE8
This has gotten so far,that I will sum up what we found out:
Inside the event handler the attribute src cannot be read in IE8 (FF works fine), neither with jQuery nor with usual javascript
The only way to get the data was to get it outside the handler, write it to an array and read it afterwards from the inside of the handler
But there was still no possibility to write to src (neither jQuery nor javascript worked - only for IE 8)
I've got it working by writing the img elemts themselves to the document, but the reason behind this problem is no solved
The new code
relcounter = 1;
imgleft_array = new Array();
jQuery('.Image_left').each(function(){
imgleft_array[relcounter] = jQuery(this).attr('src');
relcounter++;
});
relcounter = 1;
imgcenter_array = new Array();
jQuery('.Image_center').each(function(){
imgcenter_array[relcounter] = jQuery(this).attr('src');
relcounter++;
});
relcounter = 1;
imgright_array = new Array();
jQuery('.Image_right').each(function(){
imgright_array[relcounter] = jQuery(this).attr('src');
relcounter++;
});
//view entry
jQuery('.blogentry').live('click',function(){
// Get contents
entryindex = jQuery(this).attr('rel');
blogtext = jQuery(this).children('.blogtext').html();
blogauthor = jQuery(this).children('.onlyblogauthor').html();
blogtitle = jQuery(this).children('.blogtitle').html();
profileimage = jQuery(this).children('.profileimage').html();
imgleft = imgleft_array[entryindex];
imgcenter = imgcenter_array[entryindex];
imgright = imgright_array[entryindex];
// Write contents
jQuery('#entryimages').html('');
jQuery('#entryimages').html('<img class="rotate" width="132" height="138" id="bild_left" src="'+imgleft+'" /><img class="rotateright" width="154" height="162" id="bild_center" src="'+imgcenter+'" /><img class="rotate" width="132" height="138" id="bild_right" src="'+imgright+'" />');
jQuery('.person').attr('src', profileimage);
jQuery('#g_fb_name').html(blogauthor);
jQuery('#g_titel').html(blogtitle);
jQuery('#g_text').html(blogtext);
});
So I am just not using .attr('src') in the event handler....
Try to make a delay:
jQuery(document).ready(function() {
setTimeout(function () {
jQuery('.blogentry').each(function(){
// your code...
});
}, 100); // if doesn't work, try to set a higher value
});
UPDATE
Hope, this code will work.
$('.blogentry img').each(function(){
alert( $(this).attr('src') );
});
UPDATE
I'm not sure, but maybe IE can't read classes with uppercase first letter...
Try to change ".Image_center" to ".image_center"
UPDATE
Check your code again. You definitely have some error. Try this jsfiddle in IE8, attr('src') is showed correctly. http://jsfiddle.net/qzFU8/
$(document).ready(function () {
$("#imgReload").click(function () {
$('#<%=imgCaptcha.ClientID %>').removeAttr("src");
$('#<%=imgCaptcha.ClientID %>').attr("src", "Captcha.ashx");
});
});

IE8 tinyMCE .NET insert image

Solution: The problem below was caused by a Divx javascript that overwrote a core javascript function. Thanks to aeno for discovering this and shame on the Divx coder who did that!
Problem: Clicking the insert image button in the tinymce toolbar does nothing in IE8.
Description: Bear with me here. I don't think the issue is with tinymce and it's probably the fault of IE8, but I need help from someone wiser than me in solving the final piece of the puzzle to figure out who is responsible for this...
So basically I'm using tinyMCE with Visual Studio 2010 and I get the problem as described above. So I switch to the tinyMCE source code to debug this. The problem seems to happen in this piece of the code in the inlinepopups/editor_plugin_src.js, line 358:
_addAll : function(te, ne) {
var i, n, t = this, dom = tinymce.DOM;
if (is(ne, 'string'))
te.appendChild(dom.doc.createTextNode(ne));
else if (ne.length) {
te = te.appendChild(dom.create(ne[0], ne[1]));
for (i=2; i<ne.length; i++)
t._addAll(te, ne[i]);
}
},
the exact line of code being,
te = te.appendChild(dom.create(ne[0], ne[1]));
In IE8 te becomes null because te.appendChild returns nothing.
To give some background on the the code, te is a DOM.doc.body object and ne seems to be a json object containing the structure of the inline popup object that needs to be created.
So back to the code.. this works with all other browsers no problem. So I step into the function appendChild and I'm brought to some "JScript - script block [dynamic]" file that does the unthinkable. It overrides the doc.body.appendChild function... You can see it below,
code cut out
...
var appendChildOriginal = doc.body.appendChild;
doc.body.appendChild = function(element)
{
appendChildOriginal(element);
var tag = element.tagName.toLowerCase();
if ("video" == tag)
{
ProcessVideoElement(element);
}
}
...
code cut out
Here we can obviously see what went wrong. Of course te.appendChild returns nothing... it has NO RETURN STATEMENT!
So the final piece to this puzzle is wtf is this dynamic script block? I can't for the love of god figure out where this script block is coming from (VS2010 is not helping). My deepest suspicions are that this is IE8 in built? Can anyone shed some light on this? Below I'm providing a little bit more of this mysterious script block in case anyone can figure out where it's from. I can promise you something right now, it doesn't belong to any of the scripts in our project since we've done a search and we turn up with nothing.
var doc;
var objectTag = "embed";
// detect browser type here
var isInternetExplorer = (-1 != navigator.userAgent.indexOf("MSIE"));
var isMozillaFirefox = (-1 != navigator.userAgent.indexOf("Firefox"));
var isGoogleChrome = (-1 != navigator.userAgent.indexOf("Chrome"));
var isAppleSafari = (-1 != navigator.userAgent.indexOf("Safari"));
// universal cross-browser loader
if (isInternetExplorer)
{
// use <object> tag for Internet Explorer
objectTag = "object";
// just execute script
ReplaceVideoElements();
}
else if (isMozillaFirefox)
{
// listen for the 'DOMContentLoaded' event and then execute script
function OnDOMContentLoadedHandled(e)
{
ReplaceVideoElements();
}
window.addEventListener("DOMContentLoaded", OnDOMContentLoadedHandled, false);
}
else if (isGoogleChrome)
{
// just execute script
ReplaceVideoElements();
}
else if (isAppleSafari)
{
// listen for the 'DOMContentLoaded' event and then execute script
function OnDOMContentLoadedHandled(e)
{
ReplaceVideoElements();
}
window.addEventListener("DOMContentLoaded", OnDOMContentLoadedHandled, false);
}
function MessageHandler(event)
{
//window.addEventListener("load", OnLoad, false);
}
// replacing script
function ReplaceVideoElements()
{
if (isMozillaFirefox)
{
doc = window.content.document;
}
else
{
doc = document;
}
// set up DOM events for Google Chrome & Mozilla Firefox
if (isMozillaFirefox || isGoogleChrome || isAppleSafari)
{
doc.addEventListener("DOMNodeInserted", onDOMNodeInserted, false);
doc.addEventListener("DOMNodeInsertedIntoDocument", onDOMNodeInsertedIntoDocument, false);
}
// HACK : override appendChild, replaceChild, insertBefore for IE, since it doesn't support DOM events
if (isInternetExplorer)
{
var appendChildOriginal = doc.body.appendChild;
doc.body.appendChild = function(element)
{
appendChildOriginal(element);
var tag = element.tagName.toLowerCase();
if ("video" == tag)
{
ProcessVideoElement(element);
}
}
var replaceChildOriginal = doc.body.replaceChild;
doc.body.replaceChild = function(element, reference)
{
replaceChildOriginal(element, reference);
var tag = element.tagName.toLowerCase();
if ("video" == tag)
{
ProcessVideoElement(element);
}
}
var insertBeforeOriginal = doc.body.insertBefore;
doc.body.insertBefore = function(element, reference)
{
insertBeforeOriginal(element, reference);
var tag = element.tagName.toLowerCase();
if ("video" == tag)
{
ProcessVideoElement(element);
}
}
}
...
code cut out
HI,
I'm dealing with the exact same problem occuring when opening a prettyPhoto gallery...
I have no idea where this "script block" is coming from, but it definitely causes the error.
So, does anyone know anything on this suspicious script block?
Thanks,
aeno
edit:
A little more googling shed some light onto it: The mentioned script block comes from the DivX plugin that's installed in InternetExplorer. Deactivating the DivX plugin suddenly solved the problem and prettyPhoto opens quite smooth :)
Now I have to figure out whether the DivX developers have bug tracker...

link element onload

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

Categories

Resources