October 2008 Archive

Script to add CSS only when JavaScript is available

By Luke Smith on October 29, 2008 7:07 AM

If your site uses JavaScript to hide and show content, styled using display: none and (commonly) display: block, you may have an accessibility problem on your hands. The hidden content can't be viewed by users that have JavaScript turned off unless you've provided adequate navigable hooks to get to it via default html behavior (e.g. <a href="...">).

One way of addressing this is to add a class to the elements you want to hide once the DOM is in a modifiable state, but this can cause the hidden content to appear for some unknown amount of time, then disappear. Less than elegant.

Another approach might be to have a class that hides elements only when relative to a trigger class that is applied to a containing element, such as <body>, ala

<head>
    <style type="text/css">
        .js_on .hide { display: none !important; }
        ... and so on
    </style>
</head>
<body>
    <script type="text/javascript">
        document.body.className = "js_on";
    </script>
    ...
    <div class="hide">This is only hidden when JavaScript is on.</div>

That would prevent the content flicker, but if the body class is modified by another script on the page, your content could suddenly appear.

There are numerous other ways to address the problem, such as using JavaScript in the <head> to add a <link> element pointing to a stylesheet with your hide rules. Etc etc. Many ways to skin this cat.

Last night as I was falling asleep, I concocted this little script that I named script2style.js to address the problem a different way:

(function () {

var d = document,
    scripts = d.getElementsByTagName('script'),
    me      = scripts[scripts.length - 1],
    content = me.innerHTML,
    style   = d.createElement('style');

style.type = 'text/css';

if (style.styleSheet) {
    style.styleSheet.cssText = content;
} else {
    style.appendChild(d.createTextNode(content));
}

me.parentNode.replaceChild(style,me);

})();

Drop that in the <head> like so:

<head>
    ...
    <script src="script2style.js">
        .hide { display: none !important; }
        #other .selectors { ... }
        ...
    </script>
</head>

The script replaces itself with a <style> element with the same content, and since scripts are blocking, the CSS will be in place as the browser loads the remainder of the page.

To be honest, I suspect it's been done before, but it was a fun little exercise.

Here's a demo.

And here's the minified script (255b).

Update

This morning, I added support for pulling @import rules out into separate <link> elements, just for kicks.

Here's a demo for that.

And here's the minified (504b) and uncompressed (966b) script.

Object collection API

By Luke Smith on October 16, 2008 10:30 AM

This has probably been done before, but I decided to spend a few minutes this morning creating a proof of concept for it. The objective is to create a function that accepts multiple objects and returns an API that mimics the objects' APIs, but applies the named method to all of the objects in the group in turn.

So, if you have an object x and an object y, and both have a function foo(), you can call group(x,y).foo().

Here's my 5 minute POC:


var _A = Array.prototype,
    _sl = function (_) { return _A.slice.call(_); },
    _jn = function (_,x) { return _A.join.call(_,x); };

function group() {
    var _ = _sl(arguments),i,len,
        results = [],
        methods = {},m,
        out = {
            results : function () { return results; },
            exec : function (fn) {
                var a = _sl(arguments)
                a.shift();
                results = [];
                for (i=0,len=_.length; i<len; ++i) {
                    results.push(fn.call(null,_[i],a));
                }
                return this;
            }
        };

    for (m in _[0]) {
        if (typeof _[0][m] === 'function') {
            methods[m] = true;
        }
    }
    for (i=1,len=_.length; i<len; ++i) {
        for (m in methods) {
            if (typeof _[i][m] !== 'function') {
                delete methods[m];
            }
        }
    }
    for (m in methods) {
        out[m] = (function (meth) {
                return function () {
                    results = [];
                    for (i=0,len=_.length; i<len; ++i) {
                        results.push(_[i][meth].apply(_[i],arguments));
                    }
                    return this;
                };
            })(m);
    }

    return out;
};

For kicks, I made it chainable, too, so each API method will return this. Results of the method calls are accessible via the results() API method.

You can see it in action here.

I decided to only expose functions and only those that were common amongst all objects passed in. It would have been easy enough to create API methods for all discovered methods and ignore objects that didn't have the requested method or just execute an empty stub. But hey, POC, right? And of course, it doesn't handle asynchronous operations via setTimeout or XHR.

Use str.slice(..)

By Luke Smith on October 10, 2008 10:05 AM

There are three ways to get a substring from a string in JavaScript:

  1. str.substr(startIndex,length)
  2. str.substring(startIndex,endIndexPlusOne)
  3. str.slice(startIndex,endIndexPlusOne)

substr and substring

All browsers treat str.substring(-2) as str.substring(0).

substring(4,2) inputs are automatically reversed when the second param is less than the first, resulting in substring(2,4). So substring(12,-2) is treated as substring(0,12).

IE treats str.substr(-2) as str.substr(0). All others support neg index in substr.

All browsers support str.slice(-2).

slice is the only method that properly handles negative indexes across all browsers. Here's a demo, run in your current browser.

Starting string: var str = "abcdefghijklmnopqrstuvwxyz"

coderesultdesiredcorrect?

Note if you are viewing this in IE, str.substr(-12,5) results in "abcde", whereas in other browsers "opqrs".

So in conclusion, there are too many ways to skin the substring cat. substr has cross-browser compatibility issues and substring doesn't support negative indexes. slice is always safe, so just use slice.

ls.n

LucasSmith.name

Luke and Liam

I'm Luke. I am a front end engineer at Yahoo! on the YUI team.

Mostly I write about code stuff, but occassionally I'll mix in some real life. You've been warned.

Archives

Tags

Feeds

Subscribe to feed Recent entries

Content licensed under Creative Commons

Code licensed under BSD license

©2005 - 2010 Lucas Smith

Powered by Movable Type