Home & blog  /  2011  /

Being Firebug, pt2: which rules, which elements?

posted: 12 Dec '11 18:27 tags: css, stylesheet, Firebug, Dragonfly

Ever wondered how Firebug, Dragonfly and other debug tools manage to detect which styles apply to an element, whether they're currently active, what stylesheet (if any) they're coming from, and other such info?

In this second part of a two-part blog post, I'll be showing you how to do just that.

You read part 1, right?

Before we begin, make sure you read part one, which I posted last week, as this post continues from there and assumes you understood what I was waffling on about there.

I read it - so now what?

So you read part one - therefore you understand how Javascript can read the CSS rules and styles coming into a page from stylesheets, and how it can also read 'computed styles' on a given element.

Now we're going to look at how to see which CSS rules apply to a given element - and which of its styles are active or have been overridden.

Detecting if a rule applies to an element

This would be horrible to do in native Javascript. Possible, but horrible. Thankfully, an unsuspecting jQuery method, .is(), is going to save us a hell of lot of code writing.

If you're not familiar with .is(), it returns true or false based on whether the element(s) on the jQuery stack matches the selector you pass to it. So for example:

1$('div').is('div'); //obviously returns true

2$('#someElement').is(':visible'); //returns true if #someElement is visible

Pretty simple. So how does that help with what we're doing? Well, as we saw in part one, Javascript's DOM CSS Rule object allows us to access not only the styles of a particular rule but also the selector text, i.e. the part before the {.

If we pass that to .is() on the element we're interested in, we find out if the rule applies to that element. Pretty neat, eh? Observe.

1//grab an element

2var el = document.getElementById('myElement');

3    

4//grab the first stylesheet loaded into the page

5var sheet = document.styleSheets[0];

6    

7//get its rules (remember, approach depends on browser)

8var rules = sheet.rules ? sheet.rules : sheet.cssRules;

9    

10//we'll log in an array the selector text of the rules that apply to the element

11var rulesThatApply = [];

12    

13//iterate over the rules

14for (var o=0, numRules = rules.length; o

15    

16     //see if the rule targets our element - if it does, log it in our array

17     if ($(el).is(rules[o].selectorText))

18         rulesThatApply.push(rules[o].selectorText);

Note I'm skipping the validation - i.e. checking that el actually finds an element with ID 'myElement', and that there really is a stylesheet[0] - just to keep the code succinct, but you'd obviously want to check for these things in a real-world environment.

If you console.log our rulesThatApply array, you'll see that it contains the selector text of the rules that apply to our element, just as we'd hoped. So far, so good.

In case you missed it...

See what we did there? See how the .is() method is they key? The method, as I mentioned above, returns true or false depending on whether the element(s) in the jQuery stack matches - in other words, is targetable by - the selector you pass to the method.

That is NOT to say that the CSS rule's selector text must match exactly the jQuery selector we use to target the element (if we use jQuery at all; in the above code, I target an element natively) - rather, that it must ultimately select the same element. So for example:

1//JS

2var el = $('#topNav ul');

1/* CSS */

2ul { border: solid 1px #f90; }

When our code runs, and the loop gets to that rule, we are effectively asking:

el.is('ul'); //true, of course, so the rule applies to this element

Detecting if a style is active or overridden

Detecting what rules are talking to an element is one thing. But that does not mean, of course, that all the styles of a particular rule are active on the element - they may have been overridden by other rules or by inline styles.

The key here is to test the implicitly-set CSS value of the style against the computed - i.e. current - value of the style. If they match, it means the CSS style is still in effect. It they don't, it has been overridden.

Simple, right? Well, pretty much, but there are a few pitfalls.

First, colours. You probably set colours in your CSS via HEX syntax - however most browsers return computed colours in RGB format. So if you set #f90 in your CSS, when you read it back from the computed styles you'll be told rgb(255, 153, 0).

Secondly, you'll want to harmonise string format before attempting comparison. The most obvious example here is the dashes-vs-camel-case thing going on between CSS and JS - i.e. text-decoration, but textDecoration in JS.

So, let's get to work. For demo purposes, let's work with just one CSS rule and its styles.

1//grab an element

2var el = document.getElementById('myElement');

3    

4//grab the first stylesheet loaded into the page

5var sheet = document.styleSheets[0];

6    

7//get the sheet's first rule

8var firstRule = (sheet.rules ? sheet.rules : sheet.cssRules)[0];

The first two parts of that are exactly the same as we did in the code earlier in this post. The last line simple grabs the first rule of the stylesheet. This returns to us a DOM CSS Rule object. This is an object of the various styles that the rule sets, e.g. color.

Let's iterate over these styles and, for each, compare it to the current, computed style. As I mentioned above, we'll need to harmonise the values so they are comparable. The most complex part of this is colours - we'll convert them all to HEX format.

1//create an object in which we'll log which styles are active and which have been overridden

2var styles = {active: [], overridden: []};

3

4for (var g=0; g

5    

6     //get the name of the style and harmonise to JS format, lowercase

7     var styleName = firstRule.style[g].replace(/\-(\w)/g, function($0, $1) { return $1.toUpperCase(); });

8    

9     //get this style value and the current, computed equivalent

10     var styleVal = firstRule.style[styleName];

11     var currEquiv = el.currentStyle ? el.currentStyle[styleName] : getComputedStyle(el, null)[styleName];

12    

13     //if it's a colour style, harmonise to HEX format

14     if (styleName.indexOf('color') != -1) {

15         styleVal = RGBToHex(styleVal);

16         currEquiv = RGBToHex(curr&quiv);

17     }

18    

19     //lastly, log in our feedback object whether the style is active or overriden

20     styles[styleVal == currEquiv ? 'active' : 'overridden'].push(styleName);

21    

22}

You might be wondering where that RGBToHex function comes from. Well, I wrote it - and here it is, so you'll need this, too.

1function RGBToHex(RGBStr) {

2     var rgb = RGBStr.match(/\d+/g), hexStages = '0123456789ABCDEF';

3     if (rgb && rgb.length == 3) {

4         if (rgb[i] < 0 || rgb[i] > 255) return false;

5         var ret = '#';

6         for (var i in rgb) ret += hexStages.substr(Math.floor(rgb[i] / 16, 1), 1)+hexStages.substr(rgb[i] % 16, 1);

7     } else

8         ret = RGBStr;

9     return ret.toLowerCase();

10},

That function is beyond the scope of this article, but hopefully it's fairly simple in what it does. Basically, you feed it an RGB colour (e.g. rgb(255, 0, 0) or even just 255,0,0) and it returns the colour in HEX format. If the colour is already in HEX format it merely returns what you give it, unchanged.

So, what did we just do?

Let's look at a few things in that code. As I said, in order to see if a style is active, we need to compare it to the current, computed equivalent. This means harmonising two values:

- Line 7: the name of style, from dashes to camel case, e.g. text-align to textAlign

- Lines 15/16: colours into HEX format, because most browsers return computed colours in RGB format (even if you set them in HEX!)

So there you have it

A lot to take in, but hopefully you've got an idea of how the likes of Firebug and Dragonfly do their thing.

Oh and do remember that, like all code that deals with the DOM, you'll want to use the code from this post inside a window.onload callback or, if using jQuery, inside a document ready handler.

3 comments | post new

Being Firebug, pt1: reading CSS in Javascript

posted: 03 Dec '11 20:16 tags: css, stylesheet, Firebug, Dragonfly

Ever wondered how Firebug, Dragonfly and other debug tools manage to detect which styles apply to an element, whether they're currently active, what stylesheet (if any) they're coming from, and other such info?

In a two-part post, I'll be looking at how that's done in Javascript. Yes, it's all Javascript.

I find many junior-to-intermediate JS developers are surprised to find out JS has a rich API for this sort of thing (given Javascript's limited API generally).

In this first post I'll be looking at how you read the styles loaded into your page. In the second post, in the coming week, I'll look at how you can then work out which styles apply to a particular element - and whether they're currently active or have been overridden by another style.

The API

There's two key parts here: one to detect rules and styles from loaded stylesheets (or inline