ScriptOnly - The Opposite of a NOSCRIPT

Despite all of the advances in client-side scripting, the wonderful JavaScript libraries like Prototype and Scriptaculous, and the ease of writing AJAXy code in ASP.NET, there’s still one aspect of modern web development that can be a complete pain in the butt: accessibility for users without JavaScript. If you’re lucky – perhaps you’re developing an Intranet application, or the like – a simple <noscript>Error: This application requires JavaScript enabled</noscript> is all it takes. But other times, you need to take that extra step and make it work for those with and without JavaScript enabled.

There’s a lot of ways that this can be accomplished, but one of the more popular ways is with the SCRIPT/NOSCRIPT combo...

<script type="text/javascript">
    document.write('Only users with JavaScript will see me.');    
</script>
<noscript>
    Only users without JavaScript will see me.
</noscript>

While this works fine in a lot of scenarios, it can get especially tricky when you want to put server-side controls on the SCRIPT side of things. A lot of developers resort to something like this...

<div id="javaScriptOnly" style="display:none">
    Only users with JavaScript will see me.
    <asp:LinkButton runat="server" ... />
</div>
<div id="noJavaScript" style="display:block">
    Only users without JavaScript will see me.
    <asp:Button runat="server" ... />
</div>
<script type="text/javascript">
    document.getElementById('javaScriptOnly').style.display = 'block';
    document.getElementById('noJavaScript').style.display = 'none';
</script>

... and of course, things quickly get much uglier once you do this in the real world.

One solution that I use is a simple, custom-control called ScriptOnly. It works just like this...

<inedo:ScriptOnly runat="server">
    Only users with JavaScript will see me.
    <asp:LinkButton runat="server" onClick="doSomething" ... />
</inedo:ScriptOnly>
<noscript>
    Only users without JavaScript will see me.
    <asp:Button runat="server" onClick="doSomething" ... />
</noscript>

JavaScript users see a LinkButton, while non-JavaScript users see a plain old submit button. What’s neat about this technique is that you can put any type of content - server-controls, html, script tags, etc - and that content will only be displayed for JavaScript users. In essense, it works like a reverse NOSCRIPT tag.

Behind the scenes, ScriptOnly is a very simple control...

[ParseChildren(false)]
public class ScriptOnly : Control
{
    protected override void Render(HtmlTextWriter writer)
    {
        //Render contents to a StringWriter
        StringWriter renderedContents = new StringWriter();
        base.Render(new HtmlTextWriter(renderedContents));

        //write out the contents, line by line
        writer.WriteLine("<script type=\"text/javascript\">");
        StringReader sr = new StringReader(renderedContents.ToString());
        while (sr.Peek() >= 0)
        {
            // This could be optimized to write on one line; but
            // I've found this makes it easier to debug when
            // looking at a page's source
            writer.WriteLine(
                "document.writeln('{0}');",
                jsEscapeText(sr.ReadLine()).Trim());
        }
        writer.WriteLine("</script>");
    }

    private string jsEscapeText(string value)
    {
        if (string.IsNullOrEmpty(value)) return value;

        // This, too, could be optimzied to replace character
        // by character; but this gives you an idea of
        // what to escape out
        return value
            /*  \ --> \\ */
            .Replace("\\", "\\\\")
            /*  ' --> \' */
            .Replace("'", "\\'")
            /*  " --> \" */
            .Replace("\"", "\\\"")
            /*  (newline) --> \n */
            .Replace("\n", "\\n")
            /*  (creturn) --> \r */
            .Replace("\r", "\\r")
            /* </script> string */
            .Replace("</script>", "</scri'+'pt>");
    }
}

When "pre-reistered" in your web.config, it works just as well as the NOSCRIPT tag.

5 Comments

  • Another way to do this would be to implement a control adapter for your custom controls that determines if JavaScript is enabled (or desired) on the browser and changes the HTML rendering accordingly. I think that your approach is easier to understand for people familiar with NOSCRIPT though.

  • The problem is, there is no way to determine (ServerSide) whether or not JavaScript is enabled.

    There are certainly guesses - looking at the browser ID string, an entry page that (effectively) uses JavaScript to set Session data, etc - but, if your goal is to develop for accessibility, none of the hacks are a good fit.

  • Is it really necessary to detect Javascript these days?
    Don't know the statistics, but only thing I could imagine posing a problem would be mobile devices.. But, please, educate me :)

  • Who doesn't have JavaScript?

    FireFox + NoScript
    IE in Restricted Sites

    Now, all these people can turn it back on, but will they?

  • Ann:

    to "hide" the email address on your page botttoms, why not have a graphic of whatever constant text (support@foobar.com) is to display? This solution prevents snooping the code for plain-text email addresses, and doesn't require javascript... or am i being too simple-minded here?

Comments have been disabled for this content.