Replacing the <noscript> tag

What the <noscript> tag is

When JavaScript was first introduced into the Netscape 2 browser, it was a very different language to what it is today. The most significant difference was that it had very limited access to actually interact with a web page. The only significant part of the page content that JavaScript could update after the page was loaded were form fields. Any other interactions after the page had loaded required the use of dialogs such as alert(). The document.write() call could be used to write content into the page while it was loading for that single browser that supported JavaScript. The <noscript> tag was introduced at the same time as JavaScript to allow all those browsers other than Netscape 2 to be able to output some alternative text in place of that which netscape was producing with the document.write statement.

<noscript><p>This is not Netscape 2.</p></noscript>
<script language="javascript">
document.write('<p>This is Netscape 2.</p>');
</script>

When Internet Explorer 3 introduced jScript as Microsoft's equivalent of JavaScript they used the same same HTML tags for the same purpose and the above example code could be reasonably updated to indicate that the browser either was or wasn't one of Netscape 2 or 3 or Internet Explorer 3.

Note: the language attribute was the way that JavaScript was originally identified on the script tag. Both JavaScript and jScript could recognise this language specification and run the script. Most of the other language values introduced since then are server side languages and are used for scripts intended to be run on the server. For example versions of PHP prior to PHP 7 could have the PHP code wrapped in <script language="php"></script>.

Why the tag doesn't work any more

The very limited access to the web page from JavaScript made it impossible to do anything of any great significance using JavaScript/jScript. A more advanced Document Object Model to allow access to the HTML was needed. Netscape 4 introduced the layer DOM as an attempt to provide this needed functionality in JavaScript and IE4 introduced the all DOM to do the equivalent for jScript. These only really lasted as long as those specific browsers did though as subsequent browsers all supported at least some of the many DOM commands that JavaScript uses today to provide full access to everything in the web page.

The introduction of JavaScript commands supported by some browsers and not other eralier ones meant that browsers could no longer be divided into two groups. The introduction of proper DOM support means that we now have three groups of browsers.

The <noscript> </noscript> arrangement only provides for two of these alternatives and so we need to use JavaScript itself to distinguish between the last two of these. Fortunately, JavaScript right from the very beginning has supported feature sensing where we can test if a given JavaScript (or jScript) command is supported and adjust the subsequent processing to be performed based on whether the browser we are running the script in does or doesn't support that command.

The introduction of further commands into JavaScript adding to the ways we can access the DOM and adding new ways of performing processing means that today we have a lot more than just three different variants to cover. To the above three we can add at least the following:

With these six (or if you want to include other significant changes to JavaScript, more than six) different alternatives to distinguish between we still need to make the distinction between all but that very first option from within JavaScript itself.

Of course in practice we don't need to distinguish between all of these different alternatives, we just need to distinguish two groups - those that support the commands we are using and those that don't. Unless our dividing line is between the first of the above groups and all the rest this means that we will need to use JavaScript to distinguish the dividing line. As the <noscript> tag will then only include some of the browsers we want to treat as not supporting JavaScript this means that we need to handle most of our noscript situations from JavaScript and so handling all of them from JavaScript and not using the noscript tag at all will avoid duplication of that content.

The different alternatives even blur together to some extent as we can add polyfills to add support for specific commands to older browsers that don't support the native version and even use transpilers to convert modern JavaScript code into an older equivalent supported by more browsers. When we do this we have either moved browsers into a newer variant of JavaScript with respect to the particular commands we have added via polyfills or moved our code into an older variant of JavaScript by transpiling it back into an earlier JavaScript version that more browsers understand.

There isn't really a list of JavaScript versions where you have specific versions of JavaScript but rather a range of different versions that are either supported or not supported by a particular browser depending on the specific commands used within the JavaScript itself that the code expects to be native commands supplied by the browser.

Benefits of replacing it with JavaScript and CSS

The Document Object Model provides us with ways to add, change and remove content from the web page. If we create our page initially so it looks the way that we want those without JavaScript (or an insufficient level of JavaScript) to see it then we can use DOM commands to change the content of the HTML for when an appropriate level of JavaScript is supported for our script to be able to run. Of course this means we can't distinguish between browsers that have JavaScript but no DOM from those without JavaScript but then we will want to put those in the same group anyway as just about all JavaScript today uses DOM commands. To do this means that for every single spot in the page content where we want the page to read differently when our JavaScript runs we will need to check if the appropriate JavaScript commands are supported and then update that part of the page accordingly. If we need lots of such tests then our code could get very messy.

There is an easier way. At about the same time as the DOM was first introduced, CSS was reintroduced to the web only this time being attached to web pages instead of browsers and using a new syntax. This means that we can use CSS to hide content that we don't want to have visible when particular conditions apply. Instead of having to actually remove all of the parts of the page we only want visible when our script can't run we can just add a class to all of them to hide them instead.

By using JavaScript in place of the <noscript> tag we can replace the generic "is JavaScript supported or not" that the noscript tag can handle with a more specific "are the commands used in our JavaScript supported or not". The noscript situation then applies if the browser is unable to run our script even if JavaScript is available and able to run older scripts.

How we can replace it

The simplest way to replace the <noscript> tag with something that will hide all of the noscript content when a specific level of JavaScript is available is actually to use two classes. We place one of these classes on all of the content that we want to hide when our required level of JavaScript is not supported and we add the other class when JavaScript is supported.

Here is the CSS and JavaScript we need to do this:

<style type="text/css">
.js .noscript {display:none;}
</style>
<script type="text/javascript">
document.getElementsByTagName('html')[0].className += ' js';
</script>

With this code in place we can now simply apply class="noscript" to any block level tag we want to have treated as if it were a noscript tag. So we can have <p class="noscript"></p> in place of <noscript><p></p></noscript>. We also gain the ability to apply the same functionality to inline tags - something you can't do with the <noscript> tag because that is a block level tag. So we can do <span class="noscript"></span>. Already we have a more powerful replacement for the <noscript> tag and we have yet to move beyond distinguishing whether the browser can run any JavaScript or not.

This is one of the very few situations where we place a line of JavaScript in the <head> tag rather than just before the </body> tag. The reason is that we want this line of JavaScript to run before the page starts loading so it needs to be at the top of the page. As it is just one line of JavaScript we can include int directly in the HTML so as to not require an extra file lookup and download just for that one line.

We add our extra class for when JavaScript is enabled to the <html> tag because that's the only tag already existing that is wrapped around the page content to which our CSS is to be applied. There are several different ways to reference the <html> tag and depending on which you choose to use you are already starting to distinguish the level of JavaScript your page will treat as having JavaScript available. With the code as shown any browser that doesn't support getElementsByTagName will be unable to run that line of code and so the js class will not be added and so the noscript tags will remain visible. Note that we use += to append the class name (with a leading space) rather than just using = as this means that if we already have other classes attached to the <html> that they will not be overwritten.

Note that in this example I have used the deprecated "text/javascript" MIME type for the type attribute on the script tag. This is required if you need to allow older browsers that only support jScript to run the script as well as browsers that understand JavaScript. You need this MIME type specified either in the script tag or on the server as the default MIME type for scripts if you want your script to be able to run in IE8 or earlier.

Being more selective

Replacing the <noscript> tag with a couple of classes and a line or two of JavaScript goes way beyond just distinguishing browsers that can run JavaScript from those that can't. By adding a couple of if statements into our JavaScript we can actually set up the script so that the browser will only run it if a specific JavaScript command is supported and have the JavaScript not run at all if that command isn't supported. With a slightly modified version of our JavaScript/CSS solution we can set the point at which we consider the browser to support JavaScript at any level we like. Our <noscript> replacement can be very specific to the exact requirements for being able to run our own specific script and to treat browsers that don't understand all of the commands used in our script as not supporting JavaScript since they don't support our JavaScript.

Testing for the browser supporting all of the commands our script uses is obviously impractical, it is also unnecessary. If you look at how well all of the different commands used in your script are supported by the different browsers then you will find that some have far better browser support than others. What you need to find are those commands in your code that are least likely to be supported. By testing for one or two of the least supported commands in your code you can teat all of the browsers that don't support those commands as not supporting JavaScript.

For example. Let's assume that the least supported commands we are using in our code are some of the array methods introduced in ECMAScript 5. We can treat all browsers that don't support these methods as not supporting JavaScript by testing for one of these methods being supported by the browser. if that one is supported then we can reasonably assume that the others are as well. Where there are several methods you are using where there are browsers that support one command but not another but where the supported command is not always the same then you may need to check for both being supported in the if statement so as to only allow the script to run in the browsers that support both.

<!doctype html>
<html>
<head>
<style>
.js .noscript {display:none;}
</style>
<script type="application/javascript">
if (Array.prototype.forEach) document.getElementsByTagName('html')[0].className += ' js';
</script>
</head>
<body>
<p>JavaScript is <span class="noscript">not </span>available<span class="noscript"> or you are using a browser that doesn't understand forEach</span>.</p>
<p class="noscript">This paragraph appears when forEach is not supported (including when JavaScript is turned off).</p>
<script type="application/javascript">
(function() {
if (!Array.prototype.forEach) return;
// all the actual JavaScript for the page goes here
}());
</script>
</body>
</html>

Here we have added an if statement to the front of our one line of JavaScript in the head of the page so that the extra class only gets added to identify that JavaScript is supported if the browser supports the forEach method on arrays. We also add a similar test to the top of our actual JavaScript at the bottom of the page so that our script doesn't even try to run if that command isn't supported. Effectively we have coded our page so that if the browser doesn't support Array.forEach then the page functions exactly as if JavaScript were not supported at all (note though that where the method we are testing for is attached to the prototype we need to specify that in our test or it will not correctly identify that the command is supported). We have successfully redefined what we mean when we say that the browser doesn't support JavaScript to require a specific JavaScript command to be supported for the browser to be considered to support JavaScript.

 

This article written by Stephen Chapman, Felgall Pty Ltd.

go to top

FaceBook Follow
Twitter Follow
Donate