Writing an Extension

eCSStender doesn’t do much on its own, but it does make it dead-simple to write code that leverages a site’s stylesheets to patch older browsers, implement future specs, and/or create new CSS properties and have them work in a completely cross-browser way. In order to take advantage of this feature, however, you need to create extensions and register them with eCSStender. Before doing so, however, you should be sure you understand how eCSStender works.

Hello World

eCSStender’s extension registration mechanism takes 3 arguments: the lookup object, an indication of the properties you want back, and a callback function to run on each style block that matches your extension’s lookup. The various options for these arguments will be discussed in greater detail below, but for now let’s write a simple extension.

  {'selector': 'p'},
  function( selector, properties, media, specificity )
    console.log('eCSStender matched the selector "' + selector +
                       '" in the media type ' + media + '. It’s properties follow:');
    console.log('The specificity of this selector is ' + specificity);

Now, assuming your stylesheet has a declaration block that selects a paragraph, this extension would match it and return every property it had to the callback function that would, in turn, log the details to the console (in Firebug or Safari). It’s a fairly contrived example, but it gives you a sense of how simple it can be to write an extension.

Crafting a Lookup

eCSStender offers four primary lookup schemes by which to attach extensions, all of which may be augmented in a few different ways. As demonstrated in the previous example, a lookup is defined in JSON, with specific properties that determine what it matches. One of the following four mutually-exclusive properties will form the foundation of the lookup as a whole:

selector (mixed)

This lookup method allows you to attach an extension to a specific selector. The value of this property may be

property (mixed)

This lookup method allows you to attach an extension based on the use of a particular property or properties. It supports either a single property name as a string or multiple property names in an array of strings.

fragment (string)

This lookup method allows you to attach an extension based on a fragment of a property name. For example, supplying “radius” would match both the border-radius and border-top-left-radius properties.

prefix (string)

This lookup method allows you to attach an extension based on the prefix of a property name (i.e. a vendor-specific extension). For example, supplying “foo” as the value would match the -foo-extended property.

Once you’ve established the basic lookup using one of these options, you can begin to refine your lookup further with additional properties:

media (string)

By including a medium (or multiple media, separated by commas), you can restrict an extension to only operating in a specific, well, medium.

filter (object)

On occasion, you’ll find yourself working on an extension that needs to run only when certain additional criteria are met. For example, when writing an extension to implement CSS3 rotation, you need access to the transform property, but only in the instance that its value contains the word “rotate” followed by a positive or negative integer in parentheses. This is when filtering becomes handy:

  'property': 'transform',
  'filter': {
    'value': /rotate\(-?\d+?\)/

The filter object currently supports two means of filtering the lookup: property and value. Each can take either a string or a regular expression that must be matched for the extension to run.

Test Cases

In order to effectively manage resources, eCSStender provides a mechanism for determining whether or not an extension even needs to run. The test(s) needed to make that determination should be bundled into a function and added to the lookup object as the test property.

test (function)

When building a test case or test suite for an extension, you want to ensure that the function returns a boolean true if the extension can/should run and false if it can’t/shouldn’t run. In practice, this usually means leveraging one or more of eCSStender’s core methods, such as isSupported. For example:

{ 'fragment': 'radius',
  'test':     function()
    return ( ! eCSStender.isSupported( 'property', 'border-top-left-radius: 3px' ) &&
               ( eCSStender.isSupported( 'property', '-moz-border-radius-topleft: 3px' ) ||
                 eCSStender.isSupported( 'property', '-webkit-border-bottom-left-radius: 3px' ) ||
                 eCSStender.isSupported( 'property', '-khtml-border-bottom-left-radius: 3px' ) ) );

This lookup object hooks into a fragment of “radius,” but it also tests to see if it needs to run by making sure border-top-left-radius is not supported and one of three possible vendor implementations is supported. If the test returns false (as it would in Internet Explorer or Opera), the eCSStension will not be run at all, saving both processing time and system resources.

Property Access

The second argument taken by eCSStender’s registration mechanism as to do with which properties you want to get back. It takes one of several values:

In each instance, if a property-based lookup (property, fragment or prefix) was made, the matched properties will always be returned to the callback function in addition to any specifically-requested properties. It is acceptable to supply a boolean value of false when using a property-based lookup and no additional properties are desired.

Doing Something with the Extension

The final argument for the registration mechanism is your callback function. This is where you get to work your magic. As we saw earlier, the callback will be passed four arguments in the following order:

  1. the selector of the currently-matched declaration block,
  2. the properties you requested,
  3. the media this particular match was found in, and
  4. the numeric specificity of the selector.

Armed with that knowledge, you should be able to run your extension, but if you need to dive back into the stylesheet for subsequent information, you can do that using eCSStender.lookup.