开发者

Creating Style Node, adding innerHTML, add to DOM, and IE headaches

I have a two part question.

First, the scenario:

Due to some bizarre issues we've run into in regards to mobile browser support for NOSCRIPT, I'm tasked with coming up with an alternative solution to 'detect' JS. The solution logic is to have two DIVs on the page. One is an error stating you do not have JS and his shown by default. If one has JS, we then want to add a new STYLE block to the HEAD that over-rides the previous CSS and hides the error and instead shows the content.

The sample HTML:

<div id="div1">div 1 (should be shown if JS enabled)</div>
<div id="div2">div 2 (should be hidden if JS enabled)</div>

This is the JS I started with:

  var styleNode = document.createElement('style');
  styleNode.setAttribute("type", "text/css");
  styleNode.innerHTML = "#div1 {display: block;} #div2 {display: none;}";
  headTag.appendChild(styleNode);

But, I was having problems. Some googling resulting in this description of a security issue that IE can have if you try to insert innerHTML into a created element before placing it in the DOM:

http://karma.nucleuscms.org/item/101

So, I modified the script as such:

  var styleNode = document.createElement('style');
  styleNode.setAttribute("type", "text/css");
  var headTag = document.getElementsByTagName("head")[0];
  headTag.appendChild(styleNode);
  var aStyleTags = headTag.getElementsByTagName("style");
  var justAddedStyleTag = aStyleTags[aStyleTags.length-1];
  justAddedStyleTag.innerHTML = "#div1 {display: block;} #div2 {display: none;}";

question 1: is that a valid workaround for the IE issue? Is there a more efficient solution?

question 2: even with the adjustment, the script still does not work in IE. It works fine in Firefox, but in IE 7 I get an "unknown runtime error".

I have a sample of this code up on JSBIN:

http://jsbin.com/ucesi4/4

Anyone know what's going on with IE?

UPDATE:

I stumbled upon this link via google. Note the last comment:

http://msdn.microsoft.com开发者_高级运维/en-us/library/ms533897%28VS.85%29.aspx

That said, you really should put all style rules in the HEAD for strict compliance with XHTML. Doing this can also be a little tricky because you cannot use innerHTML to inject into the HEAD or STYLE element directly. (Both of these tags are READ ONLY.)

Eep! True? Is FireFox just being overly forgiving? Or is this just a very odd IE quirk?

UPDATE 2:

A bit more background on what we're trying to solve here. We're dealing with mobile devices and some of the antiquated devices a) don't support NOSCRIPT and b) have slow JS engines.

Since they don't support NOSCRIPT, we are by default showing an error, then hiding it via JS if they have it, and presenting them with the proper content. Because of the slow JS engines on these, people see the 'flicker' of the DIV's showing/hiding. This was the proposed solution to handle that, as it would load the CSS before the DIVs were even rendered.

Since it appears to be invalid, the solution will be that on these old devices, we'll use this method (as it seems to work, even if not in IE) and then all other proper browsers will do as suggested...we'll just update the DISPLAY CSS property via inline JS after each DIV is loaded in the DOM.

All that said, I'm still curious as to whether this issue is an IE bug, or if IE is actually adhering to the proper standards by making STYLE a read-only element.


In IE you can use style.styleSheet.cssText:

var style = document.createElement('style');
style.type = 'text/css';

if (style.styleSheet) { // IE
    style.styleSheet.cssText = css;
} else {
    style.appendChild(document.createTextNode(css));
}

document.getElementsByTagName('head')[0].appendChild(style);

Try this here: http://jsfiddle.net/QqF77/

See the answer on this question: How to create a <style> tag with Javascript


Don't use innerHTML, use document.createTextNode() and your life will become infinitely better ;)

var styleNode = document.createElement('style');
styleNode.setAttribute("type", "text/css");
var textNode = document.createTextNode("#div1 {display: block;} #div2 {display: none;}");
styleNode.appendChild(textNode);
headTag.appendChild(styleNode);

EDIT:

Since this solution doesn't seem to work for you, I'd abandon it. Instead go for a solution where styles are already defined and where you just disabled/enable styles via javascript if available.

You can do it this way:

<head>
<style>
.jsenabled #div2, #div1 { display: none;}
.jsenabled #div1, #div2 { display: block;}
</style>
<script>
//i know you don't use jQuery, but the solution should still be valid as a concept
//bind to DOM-ready, then set the class jsenabled on the body tag
$(function() {
 $(document.body).addClass('jsenabled');
});
</script>
</head>
<body>
<div id="div1">div 1 (should be shown if JS enabled)</div>
<div id="div2">div 2 (should be hidden if JS enabled)</div>
</body>

EDIT 2:

If it has to be done prior to DOM ready, you could do something kinda ugly like this:

<head>
<style>
#div2, .show { display: block;}
#div1, .hide { display: none;}
</style>
</head>
<body>
<div class="hide">
<script>document.write('</div><div id="div1">');</script>
   div 1 (should be shown if JS enabled)
</div>
<script>document.write('<div class="hide">');</script>
<div id="div2">div 2 (should be hidden if JS enabled)</div>
<script>document.write('</div>');</script>
</body>

Or to keep things simple, you could just do

<head>
<script>document.write('<style>#div1 {display: block;} #div2 {display: none;}</style>');
</head>
<body>
<div id="div1">div 1 (should be shown if JS enabled)</div>
<div id="div2">div 2 (should be hidden if JS enabled)</div>
</body>


Best practice is to put all the <link for CSS into the <head, and most <script at tne very end of the <body (except short fragments of <script that should execute ASAP should go in the <head). Both the standard and browsers allow you to do silly things, but just because it's acceptable doesn't mean it's a good idea.

Rather than go through all the rigamarole of creating a Style node and of manipulating the DOM (which just provide more opportunities for something to go wrong), I recommend a much simpler solution. Hard code as much as you can into your document (and style sheet?) in advance once (rather than creating things on the fly every time). Here's a rough example:

<body ...
...
<div id="warning" style="display: block;">
JS is required for this site, but isn't available ...
</div>
<div id="message" style="display: none;">
JS is available
</div>
...

<script ...
var warningEl = document.getElementById('warning');
warningEl.style.display = 'none';
var messageEl = document.getElementById('message');
messageEl.style.display = 'block';
</script ...

This should work reasonably well most of the time (it will certainly work in more browsers than what you tried to do initially). However, any attempt to change the DOM in any way before the page is initially displayed to the user is not guaranteed to work (in other words neither your way nor the example above will always work in all cases). The earlier you run your Javascript, the more likely DOM operations (including getElementById) may fail. And the later you run your Javascript, the more likely the user will notice a perceptible "flicker" of their display. So you're presented with a tradeoff choice between wide compatibility and noticeable flicker.

You can wait until the DOM is guaranteed to be fully ready before you run your Javascript ("ready" in jQuery, or addEventListener 'domcontextloaded', or even addEventListener 'load'). This is guaranteed to function correctly in all cases. But it will flicker (perhaps quite badly) in many cases.

The only way I know (but I hope somebody else knows more:-) to avoid entirely the possibility of flicker is to put in the <head a fragment of Javascript that changes window.location but does nothing else. (No references to the DOM, which are usually made obvious by the word "document" somewhere in the code.) The net effect if JS is available will be an immediate reload of a different page before much of anything is shown to the user. The initial page can contain your warning, and the new page the real stuff with no warning.

Even this method has some disadvantages though: First, the double download takes extra bandwidth and delays user visibility a little. This won't matter on typical desktops. But on handhelds it may not be acceptable. And second, it will raise havoc with SEO. The second page -that's supposed to be invisible and only accessed from the first page- may show up independently in webdexes so users can easily access it directly (you may be able to "fix" this with clever use of "canonical" and/or "meta...robots"). And the SERP of the initial page may fall precipitously when its only content is the warning message.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜