CSS Advanced Selectors - Functional and Design Specification


Glossary


  • Selector: In general, a CSS selector is a pattern to match elements in a document. The associated style declaration is applied to any element that matches the selector pattern.

    Note: When discussing CSS in the context of Flex, instead of a document "element" we will use the term "component". It is assumed these components implement the necessary interfaces to participate in the Flex style subsystem.
  • Universal Selector: CSS has a special case of "*" as the subject is called the "universal selector" which matches any type. The "*" character can be omitted if other conditions exist on the subject.

Note: Flex has an alternate special root selector called "global" which functions as the root of the inheriting style protochain. Additional conditions are not meaningful on the "global" selector.

  • Type Selector: A CSS type selector matches instances of a component by local name. For example, the following simple type selector Button matches the component with local name Button (i.e. in Flex the Button component is implemented with the ActionScript class mx.controls.Button).
    Button { color: #CC0000; }
  • Class Selector: A CSS class selector matches components that meet a class condition. The CSS syntax to declare a class selector is to prefix the condition with a dot. You can either declare a class selector as a condition of a type selector, or universally to any type that meets the class condition.
    .header { background-color: #CCCCCC; }
    
    HBox.footer { background-color: #999999; }

    Note: In Flex a class condition is met using the styleName attribute on a component. For example, you may have two classes of HBox: "header" and "footer". Above, the first selector applies to any component with styleName="header"; the second selector should only apply to HBox components with styleName="footer" (something that actually needs to be fixed and enforced in Gumbo, as to-date class selectors have only been universal and any type in the selector is ignored).

  • id Selector: A CSS id selector matches components that meet an id condition. The CSS syntax to declare an id condition is to prefix the id with a hash sign.
    #button13 { background-color: #CCCCCC; }
    
    Button#button13 { background-color: #999999; }

    Note: In Flex an id condition is met by setting the id attribute on a particular component.

  • Descendant Selector: A CSS descendant selector matches components depending on their relationship to ancestor components in the document. A descendant selector allows you to match components based on whether they descend (i.e. are children, grandchildren, great grandchildren, etc...) from particular types of components.
    Panel Button { color: #CCCCCC; }
    
    VBox Panel Button { color: #999999; }

    Note: In Flex the ancestor/descendant relationship is determined based on the display list. Styles are only applied to components in the display list and thus when matching based on a relationship to an ancestor component, those ancestors must also be in the display list.

  • Pseudo Selectors (for States): A CSS pseudo selector matches components based on an additional condition that may be dynamic and not necessarily defined by the document tree.
    Button:down { color: #CCCCCC; }
    
    Button:up { color: #999999; }

    In Gumbo we plan to make use of pseudo selectors to apply styles to components only when they are in a specified state. Flex's use of pseudo selectors are similar to CSS pseudo-element selectors in that they can only be applied to the subject of a selector, but since a components' state is transient they are also like pseudo-class selectors in that they may gain or lose a pseudo-class over time. As such, we simply refer to these selectors in Flex as pseudo-selectors.

  • Subject (of a selector): The subject of a selector is the right most simple type selector in a potentially complex set of conditions. In the following examples, Button is the subject of each of these selectors:
    VBox Panel Button#button12 { color: #DDDDDD; }
    
    VBox.special Button { color: #CCCCCC; }
    
    Button:up { color: #EEEEEE; }
    
    Button.special { color: #FFFFFF; }

Summary and Background


Cascading Style Sheets (CSS) are used in Flex to apply styles to visual components on the application display list. To-date we've supported simple type selectors and universal class selectors. Customers have requested more advanced CSS selectors and in Gumbo we plan to provide two new selector types - id selectors and descendant selectors - as well as fix class selectors so that they can be a condition of type selectors.

As a B-feature we plan to support psuedo-selectors for component states.

Update: With the dropping of the Fx prefix from Spark component names, CSS type selectors must now be namespace qualified to avoid collisions between MX and Spark component names. Namespace qualified type selectors will be resolved to an ActionScript classname in the same manner that an MXML tag would (i.e. through top level manifests configured in flex-config.xml and swc catalog.xml files). See the CSS Namespaces Support specification for more details.

Usage Scenarios


The usage scenarios for styling components is a well known feature in Flex. These advanced selector additions simply allow the developer to apply styles to components in a more precise manner.

Detailed Description


Flex maintains a runtime style subsystem to calculate which style properties apply to which components based on their position in the display list (as style properties may be declared for the component or inherited from a parent component).

Calculation of styles is performed at runtime because the display list can change and this impacts which style rules match for a component. When any of these conditions change, style property caches are cleared and need to be recalculated. This may be potentially expensive so opportunities for further optimization will be sought during implementation.

New selector types

Flex 3 supports simple type selectors and universal class selectors:

Button { color:#00FF00; }     /* Simple type selector */
.special { color:#FF0000; }     /* Universal class selector */

In Gumbo, in addition to these selectors, we plan to add support for class conditions on type selectors, id selectors, descendant selectors and pseudo-selectors:

Button { color:#00FF00; }     /* Simple type selector */
.special { color:#FF0000; }     /* Universal class selector */
Button.special { color:#FF0000; }     /* Type selector with a class condition */
Button#b13 { color:#0000FF; }     /* Type selector with an id condition */
#b13 { color:#FF9933; }     /* Universal id selector */
Panel VBox Button { color:#990000; }     /* Descendant selector */
Button:up { color:#FF9900; }     /* Type selector with a pseudo-condition */
:up { color:#FF9933; }     /* Universal pseudo-selector */

To support these new selectors the data structures used to store style information needs to be changed in both the compiler and the client runtime. Instead of only using the selector String as a key, the subject must first be identified and this should serve as a key to find the collection of rules that apply to this subject. The associated selectors will then be matched at runtime based on a component's local name (type), styleName (class), identity (id), state (pseudo) and/or position in the display list (descendant).

See the Glossary section above for examples and an explanation of each type of selector.

The styleName property now supports a list of class selectors

In Gumbo, the styleName property will be enhanced to support a list of class selectors separated by a space. In the following example, the Button will match on the .redText class selector even though it additionally specifies a list of classes in its styleName property. (It would also match a .smallText class selector if one were declared).

<Style>
  @namespace "library://ns.adobe.com/flex/spark";

   Button.redText {
        color:#FF0000;
   }
</Style>

<Button styleName="redText smallText" />

CSS selector specificity

When determining the styles for a component at runtime, the following steps need to be taken to find the right selectors.

First, all style declarations that apply for a subject are located. Then the order of precedence needs to be considered. Developer specified styles override default styles. Then selector specificity is considered. The W3C's http://www.w3.org/TR/CSS2/cascade.html#specificity suggests calculating selector specificity as follows:

  • count the number of ID attributes in the selector (= a)
  • count the number of other attributes and pseudo-classes in the selector (= b)
  • count the number of element names in the selector (= c)
  • ignore pseudo-elements.
    Then apply some suitable weight to a, b, c (such as: a*100 + b*10 + c).

If two selectors match in rank, the last one specified wins. This means the declaration order must be preserved.

For example, given this MXML snippet:

<s:Group>
   <s:Button styleName="sbUpButton" id="upButton" />
</s:Group>

After applying the following two CSS rules, the Button component will have a skin property of type Bar as the second selector chain has a specificity of 101, where as the first selector chain has a specificity of 10.

<Style>
    @namespace "library://ns.adobe.com/flex/spark";

   .sbUpButton {
        skin: Foo;
   } 

   Group #upButton {
        skin: Bar;
   }
</Style>

Identical selectors and declaration order

Identical selectors will continue to be merged into a single style declaration in the compiler as an optimization; the last property descriptor declared wins. However, in Flex 3, adding multiple identical selectors were not supported at runtime. The last selector added would override the entire previous selector rather than merging the corresponding property declarations. In Gumbo we will simply retain multiple identical selectors at runtime and the declaration order will be preserved when gathering selector matches.

<Style>
    @namespace "library://ns.adobe.com/flex/spark";

    Button.foo {
        color:#FF0000;
    }

    Button.foo {
        font-family:"Arial";
    }
</Style>

API Description


Today, Flex components must at least implement the mx.styles.ISimpleStyleClient interface (though typically implement the more complete mx.styles.IStyleClient interface) to participate in the style subsystem at runtime.

To match components based on advanced style selector criteria such as identity, state or descendant position in the display list, we need to introduce a new interface mx.styles.IAdvancedStyleClient.

package mx.styles
{

public interface IAdvancedStyleClient extends IStyleClient
{
    function get id():String;

    function get styleParent():IAdvancedStyleClient;


    function matchesCSSState(cssState:String):Boolean;

    function matchesCSSType(cssType:String):Boolean;
}

}

The constructor for CSSStyleDeclaration will be modified by relaxing the type to Object to allow for both legacy String selectors and new strongly typed CSSSelector instances, and the following properties and methods will be added. For backwards compatibility, if the selector argument is null, the subject will be interpreted as a simple type selector name or a universal class selector if it begins with a dot.

package mx.styles
{

public class CSSStyleDeclaration
{
...

public function CSSStyleDeclaration(selector:Object=null);

...

public function get selector():CSSSelector;
public function set selector(value:CSSSelector):void;

mx_internal function get selectorString():CSSSelector;
mx_internal function set selectorString(value:String):void;

public function get specificity():int;

public function get subject():String;


...

public function matchesStyleClient(object:IAdvancedStyleClient):Boolean;

mx_internal function isAdvanced():Boolean;

mx_internal function getPseudoCondition():String;

...
}

}

Advanced selectors require more than a simple String to represent the conditions and ancestors. A new mx.styles.CSSSelector class will be added to represent a potential chain of selectors each with conditions and ancestors.

package mx.styles
{

public class CSSSelector
{

public function CSSSelector(subject:String, conditions:Array=null, ancestor:CSSSelector=null);

public function get ancestor():CSSSelector;

public function get conditions():Array; /* of CSSCondition */

public function get specificity():int;

public function get subject():String;


mx_internal function getPseudoCondition():String;

public function matchesStyleClient(object:IAdvancedStyleClient):Boolean;

public function toString():String;
}

}

A new mx.styles.CSSCondition class will be added to record each type of selector condition.

package mx.styles
{

public class CSSCondition
{

public function CSSCondition(kind:String, value:String)

public function get kind():String;

public function get specificity():int;

public function get value():String;

public function matchesStyleClient(object:IAdvancedStyleClient):Boolean;

public function toString():String;
}

}

Another enumeration class mx.styles.CSSConditionKind will be added for the different kinds of conditions.

package mx.styles
{

public class CSSConditionKind
{
    public static const CLASS:String = "class";
    public static const ID:String = "id";
    public static const PSEUDO:String = "pseudo";
}

}

Since mx.styles.StyleManager will need to keep track of multiple CSSStyleDeclaration's for each subject, we will update the Flex 3 mx.styles.IStyleManager2 interface as follows:

package mx.styles
{

public interface IStyleManager2
{

...

function get typeHierarchyCache():Object;
function set typeHierarchyCache(value:Object):void;

...

function getStyleDeclarations(subject:String):Array; // Array of CSSStyleDeclaration

function hasPseudoCondition(state:String):Boolean;

function hasAdvancedSelectors():Boolean;
}

}

B Features


We also plan to support pseudo selectors for component states. Pseudo selectors can only be specified on the subject of a style declaration selector. In order for components to make use of pseudo selectors, they must implement the mx.styles.IAdvancedStyleClient interface.

For non-skinnable Spark components (i.e. those derived from spark.components.supportClasses.GroupBase) and MX components (see Enhanced States Syntax), pseudo selectors are matched based on the value of a component's currentState property.

For skinnable Spark components (see Gumbo Skinning), pseudo selectors are matched based on the current skin state, e.g.

Button:up {color:#FF0000}

...would apply only when the matching Button component skin was in the "up" state.

When a component state changes, the style proto-chain of a component (and any of its children) may need to be recalculated. We will need to pay close attention to whether state changes will actually cause a change in the matching CSS selectors to avoid recalculating styles (and hence redrawing the display list) if no change would be observed.

Examples and Usage


See the Glossary section for examples of the kinds of selectors and their usage in Flex.

Additional Implementation Details


To be determined.

Prototype Work


An initial prototype exists in the Gumbo Alpha release.

Compiler Work


Batik's CSS Parser already detects and creates selectors for descendant selectors, conditional id selectors, and pseudo selectors. The Flex compiler will be updated to allow these type of selectors to be defined in Flex style sheets and will generate the appropriate ActionScript code to register them with the runtime style manager.

The flex2.compiler.css.StylesContainer class's extractStyles() method must be updated to allow for more selector types. Similar updates must be made to the flex2.compiler.css.CssCompiler class's extractStyles() method (which is used to compile .css files to .swf modules).

The flex2.compiler.css.StyleDef class needs to be updated to consider more than a boolean switch controlling whether a definition is from a type selector. Instead a subject needs to be identied and multiple selectors (potentially advanced) registered for the subject.

In fact, a major limitation of the current compiler representation is that selectors are not stored by subject, but rather just by type selector name or the class constraint name if it is present - this does not handle when class constraints are applied to type selectors or where there are multiple selectors per subject. The result is the incorrect merging of styles for unrelated subjects.

Accordingly, the StyleDef.vm Velocity Template (and any associated direct AST generation code) will be duplicated to preserve existing behavior and a new section for advanced styles will be created to register the multiple runtime style declarations per subject with the style manager.

For the direct compilation of .css to .swf modules, the StyleModule.vm and StyleLibrary.vm velocity templates (and any associated direct AST generation code) must be duplicated too to handle the new kinds of advanced selectors and multiple selectors per subject.

Web Tier Compiler Impact


None expected.

Flex Feature Dependencies


The style subsystem is something that most features interact with, however, this enhancement attempts to add advanced features to the existing subsystem and other features are not expected to need to change to take advantage of these new capabilities.

Backwards Compatibility


Syntax changes

This feature introduces new CSS syntax that was not valid before and that in itself should not affect backwards compatibility.

Behavior

However, the existing behavior for class conditions is not fully implemented. Now that we're adding more complex conditions for selectors we need to fix how multiple rules are applied for a given subject.

In the following example, the two subjects of the two rules are Button and Text respectively. Both of these selectors make use of a class condition for styleName "customStyle". This is not handled in Flex today as the class constraint is merged for both subjects and the last rule overrides the color property declaration.

Button.customStyle {color:#0000FF; font-style:italic}
Text.customStyle {color:#00FF00;}

The result is that both the Button and the Text field are green and italic. However, the correct behavior is for the Button label to be italic and blue, and the text field to be plain and green. We will fix this in Gumbo.

Note: The compatibility version compiler switch will be checked and if the version is set to version 3 or earlier, we will revert to the simple CSS selector behavior.

Warnings/Deprecation

There is an existing feature where by the compiler warns if unused type selectors are declared. Gumbo introduces more complex selector scenarios, however, we will not detect whether a condition is actually met for a descendant selector. We will only warn if the class matching a simple type selector as the subject of a rule is used or not. If an id selector does not match an id in an Application, we will not give a warning.

Accessibility


TBD.

Performance


Runtime performance of calculating styles may be expensive for descendant selectors. TBD.

Globalization


None known.

Localization


Compiler Features

Any new compiler errors or warning messages related to the new selector types will be added to sdk/modules/compiler/src/java/flex2/compiler_en.properties.

Any new compiler errors or warning messages that are related to the linker (flex2.linker.* packages) will be added to sdk/modules/compiler/src/java/flex2/linker_en.properties.

Framework Features

Localization will be considered for any new runtime error messages introduced during implementation.

Issues and Recommendations


The performance of states-based "pseduo" style declarations is in question.

Documentation


Style documentation will need to be extensively updated to describe the new CSS selector types.

You must be logged in to comment.

I'd like to vote for that: "As a B-feature we plan to support psuedo-selectors for component states."

Cheers, David

We did end up getting some support in for pseudo-selectors so if you get a chance to try out a nightly build or wait for beta 1 we'd really appreciate your feedback. For gumbonents, i.e. skinnable components introduced in Gumbo, the pseudo-selector matches against the skin state. For all other components, it matches against the document state that the component instance happens to be in.

I'm fairly new to the Adobe communities and my apologies if this isn't the right place to post my comment. It seems to me that most of the examples I've found relating to styles in Flex show a style being applied to a visual object, like a button, but I am interested in applying styles to html text content displayed in a TextArea, including margins and padding support. I'd like to be able to do create a stylesheet with multiple rules in mxml, give the sheet an ID, and then assign the sheet to the TextArea, so it can be applied to all classes in the TextArea's html content. Something like this:

http://forums.adobe.com/thread/522307?tstart=0

Thanks