Developer’s Guide

1. Setup and Configuration

Before you can use CSSUtilities you'll need to setup and configure it, and have a working knowledge of the different ways to get data from it. This part of the documentation will take you through that:

  1. Setting it up
  2. Getting data
    1. Asynchronous with manual init
    2. Asynchronous with auto-init
    3. Synchronous with manual init
    4. Synchronous with auto-init
  3. Configuration options

Setting it up

There are two script includes to put at the end of the <head> section — the main CSSUtilities library, plus an additional Selectors API:

<script type="text/javascript" src="CSSUtilities.js"></script>
<script type="text/javascript" src="Selector.js"></script>

The additional Selectors API provides functionality to browsers that don't support the querySelectorAll method (which is Opera 9, Firefox 1.5–3.0, Konqueror, IE6 and IE7), so if you don't include the additional library then CSSUtilities won't work in those browsers either. The library that's bundled by default is LlamaLab's Selector.js, because in my testing it gave the most consistently accurate information with a very small code size (Dean Edward's base2 and Robert Nyman's DOMAssistant gave equally good results, but they're larger script files, and CSSUtilities is not exactly tiny by itself!). But you can use a different Selectors API if you want; this might make sense if you were already including the codebase for another library anyway.

The library scripts go in the <head> section so you can initialize as soon as possible during page load; and they go at the end of the <head> so that all the page's stylesheets come before it. But as we'll see, most of the actual scripting you do with the library will need to go at the end of the <body> — or be deferred until page-load — because such scripting will generally also refer to elements in the DOM.

Getting data

The script has two primary execution modes (asynchronous or synchronous), and two implementation modes ("browser" mode or "author" mode). These modes and their interaction have specific implications for how you use the library:

In "browser" mode

The script compiles all the data it needs from the document.styleSheets collection, therefore no network requests are made and the difference in performance impact between synchronous and asynchronous execution is neglible.

In "author" mode

The script needs to re-parse every style sheet it encounters as plain text, and therefore multiple network requests might be made and the difference between synchronous and asynchronous execution is significant.

If you choose synchronous execution then you should initialize the library as soon as possible during page load — with the script at the end of the <head> section and the initialization command immediately after that. This will allow the initialization time to be absorbed into the overall page loading time, and so reduce the apparent impact overall.

If you choose asynchronous execution then users needn't notice the impact, because all the network requests happen in the background; but of course you still need to be aware that the initialization process will take time, and that all subsequent uses of the data methods must defer to that (via callback function, as the following design patterns will illustrate).

So with that in mind, there are four primary design patterns you can choose from (two for each execution mode):

Pattern A/1: Asynchronous with manual init (demo)

With this pattern you call the initialization method asynchronously, passing a callback for when it's complete. Within the callback you can then use the data methods synchronously like normal:

<head xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">

    ... style sheets ...

    <script type="text/javascript" src="CSSUtilities.js"></script>
    <script type="text/javascript" src="Selector.js"></script>
</head>
<body>

    ... page content ...

    <script type="text/javascript">
    
        //configure
        CSSUtilities.define('async', true);

        //initialize with callback
        CSSUtilities.init(function()
        {
            //synchronously call data method
            var rules = CSSUtilities.getCSSRules('#foo');
        
            //work with the returned data
            alert(rules);
        });

    </script>
</body>

Pattern A/2: Asynchronous with auto-init (demo)

With this pattern you call the data methods asynchronously without first initializing, and each of them has a completion callback of its own, which is fired once auto-init (if necessary) and the data-retrieval is complete. Within each callback the response data is passed in as an argument:

<head xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">

    ... style sheets ...

    <script type="text/javascript" src="CSSUtilities.js"></script>
    <script type="text/javascript" src="Selector.js"></script>
</head>
<body>

    ... page content ...

    <script type="text/javascript">
    
        //configure
        CSSUtilities.define('async', true);

        //asynchronously call data method with callback
        CSSUtilities.getCSSRules('#foo', function(rules)
        {
            //work with the response data
            alert(rules);
        });

    </script>
</body>

Every data method has its own completion callback, and it's always the last argument.

Pattern S/1: Synchronous with manual init (demo)

With this pattern you call the intialization method synchronously, and then use the data methods synchronously once that's complete; since all the calls are separate you can initialize early on, and thereby reduce the impact of synchronous network use:

<head xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">

    ... style sheets ...

    <script type="text/javascript" src="CSSUtilities.js"></script>
    <script type="text/javascript" src="Selector.js"></script>
    <script type="text/javascript">
    
        //configure
        CSSUtilities.define('async', false);

        //initialize
        CSSUtilities.init();

    </script>
</head>
<body>

    ... page content ...

    <script type="text/javascript">
    
        //synchronously call data method
        var rules = CSSUtilities.getCSSRules('#foo');
        
        //work with the returned data
        alert(rules);

    </script>
</body>

Pattern S/2: Synchronous with auto-init (demo)

With this pattern you call the data methods synchronously, without first initializing, and this triggers a synchronous auto-init (if necessary) before retrieving and returning the data; using this approach will probably cause an obvious pause the first time you do it:

<head xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">

    ... style sheets ...

    <script type="text/javascript" src="CSSUtilities.js"></script>
    <script type="text/javascript" src="Selector.js"></script>

</head>
<body>

    ... page content ...

    <script type="text/javascript">
    
        //configure
        CSSUtilities.define('async', false);

        //synchronously call data method
        var rules = CSSUtilities.getCSSRules('#foo');
        
        //work with the returned data
        alert(rules);

    </script>
</body>

Configuration options

CSSUtilities has a small number of global configuration options, all of which are defined before initialising the library (the design patterns shown above also illustrate where this happens).

Options are defined using the define method, which takes two required arguments, for the name (always a String) and the value (some String some Boolean):

CSSUtilities.define('mode', 'author');
CSSUtilities.define('async', true);
CSSUtilities.define('attributes', false);

(For one of the settings there's an optional third argument; for details see :: external Selectors API settings.)

The define() method includes comprehensive validation and error reporting, and any invalid definitions will throw a fatal error that prevents any further use of the library; for a list of possible errors see :: Error messages.

Here's a reference of all the available options, their meanings and possible values:

"mode"

CSSUtilities.define("mode", "browser");
implementation mode [REQUIRED String]

A String that specifies one of two internal parser implementations — either "browser" or "author". The default value if undefined is "browser".

This variable fundamentally changes how the library gets its CSS data, and therefore what data it returns.

In "browser" mode the library uses the document.styleSheets collection to get its data, which means that what we get is subject to each browser's implementation: it only includes what the browser understands and supports, and the properties will be normalized in various ways (for example, among other things: Firefox normalizes hex colors to RGB, Opera normalizes colors to 6-digit hex, Internet Explorer splits-up margin shorthands into their component longhand properties).

In "author" mode, the library loads and parses every stylesheet as plain text, which means that each browser should return the same data, whether or not it actually supports everything listed, and all the properties will be returned exactly as they were specified in the stylesheet — with no normalization of units or value-types, and everything listed in the same syntax it was defined with (except for properties defined in HTML style attributes — these properties cannot be returned independently of browser normalization).

So as a consequence of each approach, "author" mode has to make a number of Ajax requests in addition to the normal page load, where "browser" mode already has the data it needs. This difference may in turn affect your choice of asynchronous or synchronous execution mode; for more about that see :: Getting data.

Which mode you choose will obviously depend on what you need, and there are no hard or fast rules about which is better. But broadly speaking, "author" mode may be suitable for testing and development tools, while "browser" mode may be more appropriate for real-world applications.

You should also note that only "browser" mode has full access to dynamically-generated styles. In "author" mode, the library won't see any rules created using addRule or insertRule; but all browsers except IE will see rules created as text-nodes inside a <style> element, and all browsers will see properties added to .style (subject to browser support for the individual properties). Naturally in all cases, only styles created before initialization will be picked-up, though you can of course re-initialize at any time.

Some of the data methods are affected by your choice of implementation, for example, where particular options are only relevant in one mode or the other; such details are documented wherever applicable.

"async"

CSSUtilities.define("async", false);
asynchronous execution mode [REQUIRED Boolean]

A Boolean that defines whether the library's initialization should be asynchronous (true) or synchronous (false). The default value if undefined is false (synchronous execution).

The difference in grammar is simply that with synchronous execution you can query the library using all synchronous function calls, whereas with asynchronous execution you have to use callbacks for some.

The difference in practical terms though is that the execution mode primarily determines how Ajax requests are made — correspondingly synchronous or asynchronous — which in turn has an impact on usability, because synchronous requests lock-up the browser thread until they're complete (the impact depending on when you initialize and how many requests are made, factors which are affected by your choice of implementation mode).

This may sound a little convoluted at first, but once you get used to the design patterns I hope you'll appreciate the flexibility they afford; for details please see :: Getting data.

"page"

CSSUtilities.define("page", document);
context document [REQUIRED Document]

A Document that defines the document object (DOM) you wish to inspect — containing the test elements and stylesheets you're interested in. The default value if undefined is document.

Generally you won't need to change this, but it's there so that you can configure the library to work with virtualized or remote documents, such as the responseXML of an Ajax request, or the contentDocument of an <iframe>.

If you do specify a different document, you will often need to specify a matching base URI for stylesheet paths as well.

If you're also defining "api" then you must define this option first.

"base"

CSSUtilities.define("base", document.location.href);
base URI for stylesheet paths [REQUIRED String]

A String that defines a URI base for qualifying stylesheet paths. The default value if undefined is the URL of the document specified in the page variable, which in turn defaults to document, and so the default value for base is document.location.href.

Most of the time you won't need to change this, but it's there so that the DOM you're inspecting can be at a different location than the stylesheets, or virtualized entirely such that it has no addressable location.

For example, in some versions of CodeBurner the DOM inspection works by loading a remote page using Ajax and then writing the returned HTML into an <iframe>, in order to create a document object for testing. But then of course, the document's location no longer matches the URL of the original page, so any relative paths in <link href> stylesheet addresses could no longer be resolved. So we set the "base" variable to match the remote page's original URL, and then all the stylesheet URLs can be resolved against that!

The value of base must be an absolute URL. If you specify a folder root then you must include the trailing slash.

If you're also defining "api" then you must define this option first.

"attributes"

CSSUtilities.define("attributes", true);
parse HTML style attributes [REQUIRED Boolean]

A Boolean that defines whether to include the data from HTML style attributes. The default value if undefined is true.

Any such CSS properties defined for an element will be organised into a single rule object, with meta-data that reflects their origin — such as higher specificity, and a selector with the special value "" (empty string).

Other things being equal, there's no reason to change this option — disabling it might mean that some data sets are incomplete, missing any properties defined this way. However if you know that the page you're checking doesn't use any style attributes, then you can disable this option and give the script one less thing to check.

Please also note that CSS properties defined in style attributes cannot bypass browser normalization in "author" mode (as opposed to properties defined in external stylehseets, which are returned independently of normalization in this mode); such inconsistency might also be a reason for disabling this option, and/or avoiding the use of HTML style attributes on applicable pages (assuming you have any such choice or inclination!).

"watch"

CSSUtilities.define("watch", false);
watch for stylesheet switching [REQUIRED Boolean|Object]

A Boolean that defines whether or not the library should monitor for stylesheet switching activity. The default value if undefined is false.

If this is set to true, the script will continually check the disabled state of every listed stylesheet; as soon as any of them changes, the library automatically re-initializes to refresh its data cache. Automatic re-initialization will also re-fire any stored initialization callback.

If you set this to false then the timer will not run, so if any state changes do occur then the library will no longer have accurate data. In that situation it would be up to you to manually re-initialize as necessary (for example, as part of a stylesheet switching script).

In "author" mode (only) you can also set this to null, which tells the library to ignore the disabled state of all stylesheets, and parse them as though they were all enabled; and no further monitoring will take place. This is a reference setting and should be used with care, because it means that the data methods may subsequently return rules and properties which are marked as active when really they're not (because they're inside a stylesheet that's disabled).

→ Important Browser Note:

In Safari and other Webkit browsers, a stylesheet element's disabled property will always return false until it's been explicitly set. This is a browser issue which affects the script in "author" mode, and means that alternate stylesheets will appear to be normal stylesheets, enabled by default, and the watch mechanism won't work until at least one switch has happened.

This issue is easily fixed, but the script can't do so automatically, since it may potentially change the page's default appearance or behavior.

So if you have this problem (ie. if you're running in "author" mode, and the watch setting is not null, and you have alternate stylesheets on the page), then you should implement the fix yourself; like this:

for(var links=document.getElementsByTagName('link'), 
        i=0; i<links.length; i++)
{
    if(links[i].getAttribute('rel') == 'alternate stylesheet')
    {
        links[i].disabled = true;
    }
}

All that does is set a default disabled value for alternate stylesheets, which reflects the state they have anyway, but so that it can be recorded accurately. You should add the code before intializing CSSUtilities; and if you're implementing any kind of stylesheet switcher script, you should also add the fix before re-creating any saved state (such as from a cookie).

(This issue can also be fixed by adding a disabled="disabled" attribute to the alternate stylesheets; however that would be invalid HTML, for some reason.)

"api"

CSSUtilities.define("api", false);
external Selectors API settings [REQUIRED Boolean][, OPTIONAL Function]

A Boolean and a Function that together control any external Selectors API.

Much of the functionality available from CSSUtilities is only possible because of the existence of a Selectors API, that identifies elements matching a given CSS selector. The most recent browser versions (Opera 10, Firefox 3.5+, Safari, Chrome and IE8) have this functionality built-in, but to make it work in the other supported browsers (Opera 9, Firefox 1.5–3.0, Konqueror, IE6 and IE7) we must use an external library to provide the API; LlamaLab's Selector.js is bundled by default.

So, if you leave the "api" option undefined, the behaviors just described will apply.

The first value is a Boolean which forces every browser to use the fallback Selectors API (true), or lets each browser prefer its native implementation if one is available (false). The default value is false.

The second value allows you to specify a different library, other than the one bundled by default. Once you've added its <script> include to the page, you define a wrapper to acts as an intermediary between CSSUtilities and your library's Selectors API; like this:

CSSUtilities.define("api", false, function(selector, page)
{
    return queryMyAPI(selector, page);
});

The wrapper will be passed a String selector (the "selector" argument) and a Document context (the "page" argument), and must return an array or NodeList of zero or more Element nodes, as returned by a selector query. If your chosen API returns the data in a different structure or format, you'll have to convert it before returning.

CSSUtilities will test the wrapper as soon as you define it, and will throw a fatal error if it returns invalid data, that prevents any further use of the library.

You can further test it by examining the data it returns yourself — just change the first value to true and then all browsers will be using it.

If you are defining this option, you must define both "page" and "base" first, as they cannot be re-defined afterwards.

Here are some sample wrappers for a selection of popular libraries:

base2

base2 returns a static node list where square-bracket notation is not supported, so it must be converted to an array:

CSSUtilities.define('api', false, function(selector, page)
{
    var nodes = [], 
        list = base2.DOM.Document.querySelectorAll(page, selector);
    for(var len=list.length, i=0; i<len; i++)
    {
        nodes.push(list.item(i));
    }
    return nodes;
});

DOMAssistant

DOMAssistant occasionally returns undefined for a selector it's unable to match, so we test for that and return an empty array instead:

CSSUtilities.define('api', false, function(selector, page)
{
    var nodes = $(page).cssSelect(selector);
    return typeof nodes != 'undefined' ? nodes : [];
});

Sizzle / jQuery

Sizzle is missing support for a number of CSS3 selectors.

CSSUtilities.define('api', false, function(selector, page)
{
    return Sizzle(selector, page);
});

prototype

CSSUtilities.define('api', false, function(selector, page)
{
    return new Selector(selector).findElements(page);
});

dojo

CSSUtilities.define('api', false, function(selector, page)
{
    return dojo.query(selector, page);
});

YUI

CSSUtilities.define('api', false, function(selector, page)
{
    return YAHOO.util.Selector.query(selector, page);
});

The .supported property

CSSUtilities includes a global property for filtering-out known unsupported browsers. It's set as soon as the script exists, and can be used as a filter to minimize or eliminate scripting errors in older browsers.

The value is formed by testing for three properties — document.getElementById, document.styleSheets and document.nodeType — and if any one of those properties is missing, the browser will be marked as unsupported. This is known to completely exclude Internet Explorer 5.5 and Opera 8, but can be reasonably assumed to exclude most legacy browsers from that generation or earlier.

If you wish to use this then, simply wrap any or all uses of the library's methods with an “if-supported” condition:

if(CSSUtilities.supported)
{
    CSSUtilities.init();
    //etc.
}

main page | Functions Reference

top

Developer’s Guide

  1. Setup and Configuration [this page]
  2. Functions Reference

main page

Additional Credits

Selector.js
(Redundent Selectors API)
© 2009 henrik.lindqvist@llamalab.com

Categories...

Website gadgets

Bits of site functionality:

Usability widgets

Local network apps

Web-applications for your home or office network:

Game and novelties

Our internal search engine is currently offline, undergoing some configuration changes in preparation for a major site overhaul. In the meantime, you can still search this site using Google Custom Search.


In this area

Main areas


[brothercake] a round peg in a square hole, that still fits