notes
to accompany version 0.3 (alpha)
OK. How does it work?
In a nutshell, a DHTML Behavior loads and parses all style sheets (at page load) and re-writes them. Simulating CSS2 in Explorer is achieved by converting CSS selectors that Explorer does not understand into selectors that it does understand. Namely, classes.
This means that most of the CSS fixes are performed using CSS!
For example:
p > a {
/* blah blah blah */
}
becomes:
.ie7_22 {
/* blah blah blah */
}
and:
[this="that"] {
/* bling bling bling */
}
becomes:
.ie7_23 {
/* bling bling bling */
}
etc.
Explorer supports multiple class names. It has a buggy implementation (we fix this later) but it is sufficient for this solution.
Now it’s all well and good converting these unknown selectors into classes but just how do we assign those classes?
I considered writing some kind of query function to assign them but realised I already had one. CSS!
By using expressions (a Microsoft technology that allows the creation of Dynamic Properties) I can dynamically allocate classes according to CSS rules. Exactly what I want!
This means that a CSS rule that Explorer does not understand is split into two rules. The first is the original rule with the selector replaced by a class (as shown above). The second rule contains an expression that assigns the class of the first rule. It is best explained by example.
Consider this rule:
.menu > .popup {
background: yellow;
}
This is split in to two rules. The first is just a simple conversion of the
.menu > .popup selector to a class:
.ie7_99 {
background: yellow;
}
Then we add another rule which contains
an expression that allocates the above class:
.menu .popup {
-ie7-class:
expression((parentElement.className.test(/\smenu\b/))?99:0);
}
If you are not familiar with JavaScript regular expressions then the above code
will look like gobbledygook. Translated, it tests that the selected element’s
parent is in the class "menu". If it is, then it assigns itself to the class
"ie7_99". This is achieved by watching the style.ie-class property
(using an onpropertychange event handler).
When this property changes it allocates the "ie7_99" class.
function watch() {
switch (event.propertyName) {
case "style.ie7-class":
// trigger for allocation of an ie7 class
// the class name will now change
addClass(event.srcElement);
break;
case "style.ie7-dynamic":
// trigger for allocation of a dynamic ie7 class
// event handlers will be attached
addDynamicClass(event.srcElement);
break;
...
This technique is used throughout, with different expressions for each situation. It’s even used to fix the multiple class names bug that Explorer exhibits! Using this approach also preserves the original cascade of the style sheet.
Complex selectors may generate several rules which in turn allocate lots of classes.
For example, this selector:
.visible my|menu > my|popup:hover {
background: yellow;
}
Simply becomes:
.ie7_6 {
background: yellow;
}
But generates the following rules:
.visible my\:menu {
-ie7-class: expression(4);
}
.ie7_4 my\:popup {
-ie7-class:
expression((parentElement.className.test(/\sie7_4\b/))?5:0);
}
.ie7_5 {
-ie7-dynamic: expression(6);
}
When the property style.ie7-dynamic changes, event
handlers are attached to the target element. This enables the dynamic
pseudo-classes :active, :focus and
:hover. In the above example, when the appropriate
event is fired, the element is dynamically allocated the class
"ie7_6" which in turn loads the style background: yellow.
Attribute selectors ([attr="value"]) are handled
slightly differently. They are also converted into classes but these
classes are allocated programmatically (not using CSS selectors).
To improve performance, a separate style sheet is built containing all rules
that allocate classes. This style sheet once applied, is then unloaded.
If you change the structure of your document (by using the DOM to add nodes
or just by inserting HTML) then you may wish to reload these rules.
You can do this by calling document.recalc(). This method
has been overriden to provide support for these additional classes/expressions.
I’ll document this solution in more detail at a later date. For the time being this should give you some insight if you want to read the source-code.
The source is a bit jumbled (I’ve had difficulty imposing order on it). The mechanism for loading the generated style rules is pretty complex. Hopefully I’ll think of a better way of doing it. But for the moment it seems to work...
Dean Edwards