Menu

Per-Module Style Management

SourceForge Editorial Staff

Functional and Design Specification


Glossary


StyleManager: Class responsible for managing style declarations.

Summary and Background


There are two goals for this feature. The first is prevent type compatibility errors when spark components are used in modules.

The second goal of the feature to solve modules leaks associated with styles. The StyleManager was a singleton that tracks style declarations. If a style declaration was added from a module, that created a reference from StyleManager to the module which would keep the module in memory.

The plan is to implement this feature by creating a StyleManager for every application and module. This means the current StyleManager will no longer be a singleton.

Usage Scenarios


  • Two modules would like to specify different skins and text alignment for Button. Each module has different styles specified in the module. In Flex 3 the first module loaded would dictate the style (first in wins) of the Button. In Flex 4, each module can style their Buttons independently.

  • Joe is using Flex 3 and trying to figure out why his modules won't unload. He discovers that the styles loaded by the module are holding it in memory. Joe runs the same application in Flex 4 and the modules unload.

Detailed Description


System Overview

In Flex 3 a single StyleManager exists for a top-level application and its child applications and modules. In Flex 4 there will be a StyleManager for every application and module. The below picture shows an overview of the different configurations a StyleManager can be present in.

!system overview.png!

In the above diagram, application A has four children, A.1, A.2, A.3, and M.1. Application A.1 is an application loaded by SWFLoader with default settings. Applications A.2 and A.3 are Marshall Plan applications. Module M.1, represents a module loaded into a child ApplicationDomain.

Style Inheritance Model

Child
ApplicationDomain
Security Domain
Styles Inherited?

A.1
child
same
yes

A.2
sibling
same
no

A.3
unrelated
different
no

M.1
child
same
yes

What about cases where an application or module is loaded into an existing ApplicationDomain? Styles still inherit but the application or module can't be unloaded.

A StyleManager will hold the set of styles for the components of each application or module. The styles would typically apply to all the components below the application or module although this can be changed programmically by changing the module factory of a component. The inheritence of styles extends to sub-applications and sub-modules as well.

To compute the set of styles that apply to any given component you would first need to merge the styles of each StyleManager that parents a component. The styles are merged on a per-property basis starting with the closest parent StyleManager of a component and working upwards to the top-level StyleManager.

Example)
Say we have application A, application A.1, and module M.1 with the following declared styles.

Application A:

@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

mx|Button {
cornerRadius: 4;
textAlign: "center";
}

s|Button {
errorSkin: ClassReference("MyErrorSkin");
skinClass: ClassReference("spark.skins.spark.ButtonSkin");

}

Application A.1:

@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

mx|Button {
textAlign: "left";
}

s|Button {
skinClass: ClassReference("MyLeftButtonSkin");
}

Module M.1:

@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

mx|Button {
textAlign: "right";
}

s|Button {
skinClass: ClassReference("MyLeftButtonSkin");
}

For a component in module M.1, the effective styles after merging module M.1 with application A are:

@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

mx|Button {
cornerRadius: 4;
textAlign: "right";
}

s|Button {
errorSkin: ClassReference("MyErrorSkin");
skinClass: ClassReference("MyLeftButtonSkin");
}

The cornerRadius property is added to mx|Button from application A. The textAlign property with a value of "right" came from module M.1. Notice that application A also declared a textAlign property with a value of "center". Since the textAlign property is already specified in module M.1, the property in application A is ignored during the merge of styles.

The properties for s|Button are merged in the same way as mx|Button. Property errorSkin comes from application A and skinClass comes from module M.1.

The same style property merging applies to application A.1 as well.

StyleManager API Model

In the context of multiple StyleManagers the StyleManager API will return the merged properties from each property or function call. The properties are merged by consulting each parent StyleManager until the top-level StyleManager is reached. The exception of this rule is getStyleDeclaration(). It will only return the styles of the local StyleManager. This was done to support previous coding patterns.

To get a style declaration that is the result of merging with parent style managers a new function, getMergedStyleDeclaration() has been added to IStyleManager2. All functions that modify properties will only affect the local StyleManager. Since getMergedStyleDeclaration() returns the result of merging StyleManager properties you will no longer be able to modify the returned reference to set styles.

Application and Module Style Initialization

The compiler generates code to initialize the styles of an application or module based on the classes linked in to that application or module. The generated style classes follow a mixin pattern. Each mixin class sets appropriate style selectors based on the default styles for that selector. When getting and setting the mixin styles only the local StyleManager will be used since each StyleManager can have different values for the same selector.

Finding the current StyleManager

Since there are many StyleManagers in the system, each component will need a way to access its relevant StyleManager. A component will need to get its StyleManager when it is on the display list and when it is off the display list. To get the current StyleManager use the StyleManager.getStyleManager() function. The getStyleManager function takes a component's module factory as an argument. The module factory will be used to find the StyleManager associated with a SystemManager or FlexModuleFactory.

If a component does not have a moduleFactory or the StyleManager property is not set, the StyleManager of the top-level SystemManager will be used.

Loading CSS Modules

When using the singleton StyleManager the loadStyleDeclarations() function loaded styles into the singleton StyleManager. Now the loadStyleDeclarations() will be called on some instance of a StyleManager. The styles will be loaded into that StyleManager instance.

It is recommended to load a style module into the application domain of the current application so the classes loaded by the style module can be shared by all the sub-applications and modules of the current application. The down-side to this is the style module cannot be unloaded.

API Description


StyleManager

  • The entire API will be deprecated except the newly added getStyleManager(). Callers of the old API will be directed to use style manager instances. One example is shown but all the public API will be deprecated.
  • Add getStyleManager function.

    public class StyleManager
    {
    ...

    /**
     *  Returns an Array of all the CSS selectors that are registered with the StyleManager.
     *  You can pass items in this Array to the <code>getStyleDeclaration()</code> method 
     *  to get the corresponding CSSStyleDeclaration object.
     *  Class selectors are prepended with a period.
     *  
     *  @return An Array of all of the selectors
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */ 
    [Deprecated(
    

    replacement="IStyleManager2.selectors on a style manager instance from IAdvancedStyleClient.styleManager",
    since="4.0")]

    public static function get selectors():Array
    {
        return impl.selectors;
    }
    
    ...
    
    /**
     *  Returns the style manager for an object.
     *
     *  @param moduleFactory The module factory of an object you want the 
     *  style manager for. If null, the top-level style manager is returned.
     *
     *  @return the style manager for the given module factory.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public static function getStyleManager(moduleFactory:IFlexModule):IStyleManager2
    {
       ...
    }
    

    }

IStyleManager2

  • Add a parent property.
  • Add a getMergedStyleDelcaration() function;

    public interface IStyleManager2 extends IStyleManager
    {
    //--------------------------------------------------------------------------
    //
    // Properties
    //
    //--------------------------------------------------------------------------

    /**
     *  The style manager that is the parent of this style manager.
     *  
     *  @return the parent style manager or null if this is the top-level style manager.
     */
    function get parent():IStyleManager2;
    
    /**
     *  @private
     */
    function set parent(parent:IStyleMananger2):void;
    
    ...
    

    /
    * Gets a CSSStyleDeclaration object that stores the rules
    * for the specified CSS selector. The CSSStyleDeclaration object is the created by merging
    * the properties of the specified CSS selector of this style manager with all of the parent
    * style managers.
    *
    *

    If the selector parameter starts with a period (.),
    * the returned CSSStyleDeclaration is a class selector and applies only to those instances
    * whose styleName property specifies that selector
    * (not including the period).
    * For example, the class selector ".bigMargins"
    * applies to any UIComponent whose styleName
    * is "bigMargins".


    *
    *

    If the selector parameter does not start with a period,
    * the returned CSSStyleDeclaration is a type selector and applies to all instances
    * of that type.
    * For example, the type selector "Button"
    * applies to all instances of Button and its subclasses.


    *
    *

    The global selector is similar to a type selector
    * and does not start with a period.


    *
    * @param selector The name of the CSS selector.
    * @param localOnly Controls whether the returned style declaration is the result of merging
    * the properties of this and any parent style managers or if the style declaration is only
    * from this style manager.
    *
    * @return The style declaration whose name matches the selector property.

    * @langversion 3.0
    * @playerversion Flash 9
    * @playerversion AIR 1.1
    * @productversion Flex 3
    /
    function getMergedStyleDeclaration(selector:String):CSSStyleDeclaration;

    ...

IFlexModuleFactory

  • Add a styleManager property.
  • Move registerImplementation() and getImplementation() from ISystemManager to IFlexModuleFactory.

    public interface IFlexModuleFactory
    {
    ...

    /**
     *  Register an implementation for an interface.
     *  Similar to Singleton.registerClass, but per-
     *  ISystemManager, and takes an instance not a class
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    function registerImplementation(interfaceName:String,
                            impl:Object):void;
    
    /**
     *  Get the implementation for an interface.
     *  Similar to Singleton.getInstance, but per-
     *  ISystemManager
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    function getImplementation(interfaceName:String):Object;
    
    ...
    

    }

ISystemManager

  • Move registerImplementation() and getImplementation() from ISystemManager to IFlexModuleFactory.

IModuleInfo

  • Add IFlexModuleFactory argument to load().
    /**
     *  Requests that the module be loaded. If the module is already loaded,
     *  the call does nothing. Otherwise, the module begins loading and dispatches 
     *  <code>progress</code> events as loading proceeds.
     *  
     *  @param applicationDomain The current application domain in which your code is executing.
     *  
     *  @param securityDomain The current security "sandbox".
     * 
     *  @param bytes A ByteArray object. The ByteArray is expected to contain 
     *  the bytes of a SWF file that represents a compiled Module. The ByteArray
     *  object can be obtained by using the URLLoader class. If this parameter
     *  is specified the module will be loaded from the ByteArray. If this 
     *  parameter is null the module will be loaded from the url specified in
     *  the url property.
     *  
     *  @param moduleFactory The module factory that parents the load. The loaded
     *  module's parent style manager will be determined from this argument.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    function load(applicationDomain:ApplicationDomain = null,
                  securityDomain:SecurityDomain = null,
                  bytes:ByteArray = null,
                  moduleFactory:IFlexModuleFactory = null):void;
    

Request

  • Add a new GET_PARENT_FLEX_MODULE_FACTORY_REQUEST request so a module or application can ask who its parent module factory is.

    /*
    * This is an event that is expects its data property to be set by
    * a responding listener
    /
    public class Request extends Event
    {

    /*
    * Dispatched from a sub-application or module to find the module factory of its parent
    * application or module. The recipient of this request should set the data property to
    * their module factory.
    *
    * The message is dispatched from the content of a loaded module or application.
    /
    public static const GET_PARENT_FLEX_MODULE_FACTORY_REQUEST:String =
    "getParentFlexModuleFactoryRequest";

UIComponent

  • Add styleManager property to get the styleManager for this object.

    public function get styleManager():IStyleManager2
    {
    // TODO:
    }

IStyleModule

  • Added a new function to allow a style manager to be passed in for context when the style module sets its styles.

    /
    * Creates and sets style declarations from the styles modules into the given
    * style manager.

    * @param styleManager The style manager where the style declarations will be
    * loaded into. The style declarations will be created relative to the this
    * style manager. The unload() function will unload styles from this style
    * manager. If null is passed the top-level style manager is used.
    *
    * @langversion 3.0
    * @playerversion Flash 9
    * @playerversion AIR 1.1
    * @productversion Flex 4
    /
    function setStyleDeclarations(styleManager:IStyleManager2):void;

CSSStyleDeclaration

  • Add a second parameter to the constructor so a StyleManager can be passed in. The argument as a default value for backwards compatibility.
  • Add a third parameter to control whether the style declaration is set into the style manager.
    /**
     *  Constructor.
     *
     *  @param selector - If the selector is a CSSSelector then advanced
     *  CSS selectors are supported. If a String is used for the selector then
     *  only simple CSS selectors are supported. If the String starts with a
     *  dot it is interpreted as a universal class selector, otherwise it must
     *  represent a simple type selector. If not null, this CSSStyleDeclaration
     *  will be registered with StyleManager. 
     *  
     *  @param styleManager - The style manager to set this declaration into. If the
     *  styleManager is null the top-level style manager will be used.
     * 
     *  @param autoRegisterWithStyleManager - If true set the selector in the styleManager. If setSelector
     *  is false this style declaration can be set in the styleManager at a later time.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function CSSStyleDeclaration(selector:Object=null, 
                                        styleManager:IStyleManager2=null, 
                                        autoRegisterWithStyleManager:Boolean = true)
    

Examples and Usage


This is an example of an application that loads two modules, "LeftModule" and "RightModule". The main application contains no styles. Both "LeftModule" and "RightModule" declare styles to modify the text alignment of Buttons. The example shows how modules can have different values for the same selector. Prior to this feature, the module that was loaded first would have its styles loaded and the other modules would have to use those styles as well.

Main Application

The main application contains two buttons. The two buttons toggle the loaded state of modules.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="1024" minHeight="768">
    <fx:Script>
        <![CDATA[
            private function toggleLeftModule():void
            {
                if (lml.url == null)
                    lml.url = "LeftModule.swf";
                else
                    lml.url = null;
            }

            private function toggleRightModule():void
            {
                if (rml.url == null)
                    rml.url = "RightModule.swf";
                else
                    rml.url = null;
            }
        ]]>
    </fx:Script>

    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
    <mx:Button label="Left Module" width="300" click="toggleLeftModule()" />
    <mx:Button label="Right Module" width="300" click="toggleRightModule()" />
    <mx:ModuleLoader id="lml" />
    <mx:ModuleLoader id="rml" />
</s:Application>

LeftModule

This module contains a Style block that sets the Halo Button to be left aligned and sets a skinClass for the Spark button that makes the Spark Button left aligned.

<?xml version="1.0" encoding="utf-8"?>
<mx:Module xmlns:fx="http://ns.adobe.com/mxml/2009" 
           xmlns:s="library://ns.adobe.com/flex/spark" 
           xmlns:mx="library://ns.adobe.com/flex/mx" layout="vertical" width="400" height="100">
    <fx:Style>
        @namespace s "library://ns.adobe.com/flex/spark";
        @namespace mx "library://ns.adobe.com/flex/mx";

        mx|Button {
            textAlign: "left";
        }

        s|Button {
            skinClass: ClassReference("MyLeftButtonSkin");
        }

    </fx:Style>

    <mx:Button label="Left Button" width="300" />   
    <s:Button label="Left Skinned Button" width="300" />

</mx:Module>

RightModule

This module contains a Style block that sets the Halo Button to be right aligned and sets a skinClass for the Spark button that makes the Spark Button right aligned.

<?xml version="1.0" encoding="utf-8"?>
<mx:Module xmlns:fx="http://ns.adobe.com/mxml/2009" 
           xmlns:s="library://ns.adobe.com/flex/spark" 
           xmlns:mx="library://ns.adobe.com/flex/mx" layout="vertical" width="400" height="100">
    <fx:Style>
        @namespace s "library://ns.adobe.com/flex/spark";
        @namespace mx "library://ns.adobe.com/flex/mx";

        mx|Button {
            textAlign: "right";
        }

        s|Button {
            skinClass: ClassReference("MyRightButtonSkin");
        }
    </fx:Style>

    <mx:Button label="Right Button" width="300" />
    <s:Button label="Right Skinned Button" width="300" />
</mx:Module>

Additional Implementation Details


Creation and initialization of StyleManager

The steps involved in creating and initializing a new StyleManager for an application.

  1. SWFLoader creates a Loader and adds a listener for an EVENT.INIT event.
  2. In the Event.INIT event handler SWFLoader listens for a GET_PARENT_FLEX_MODULE_FACTORY_REQUEST request on the loaderInfo.content.
  3. SystemManager creates and initializes the FlexInit mixin.
  4. The FlexInit mixin creates a new StyleManager, passing the current IFlexModuleFactory to StyleManager.
  5. StyleManager registers its implementation with the IFlexModuleFactory it was passed.
  6. StyleManager dispatches a Request.GET_PARENT_FLEX_MODULE_FACTORY_REQUEST unless the IFlexModuleFactory is the top-level SystemManager or this application is a Marshal Plan application (A.2 or A.3). If the IFlexModuleFactory is the top-level SystemManager then this is the top-level StyleManager.
  7. SWFLoader handles the Request.GET_PARENT_FLEX_MODULE_FACTORY_REQUEST message and set the data property to be its local module factory.
  8. StyleManager get return value of the request and sets its parent StyleManager to be the StyleManager of the returned module factory. If the return value is null then the StyleManager of the top-level SystemManager is set a the parent StyleManager.

The steps involved in creating and initializing a new StyleManager for a module.

  1. ModuleLoader get a IModuleInfo instance from ModuleManager.
  2. ModuleLoader calls IModuleInfo.load() to load the module, passing its module factory to the load function.
  3. IModuleInfo creates a Loader and adds a listener for an EVENT.INIT event.
  4. In the Event.INIT event handler ModuleManager adds a listener for a GET_PARENT_FLEX_MODULE_FACTORY_REQUEST request on the loaderInfo.content.
  5. FlexModuleFactory creates and initializes the FlexInit mixin.
  6. The FlexInit mixin creates a new StyleManager, passing the current IFlexModuleFactory to StyleManager.
  7. StyleManager registers its implementation with the IFlexModuleFactory it was passed.
  8. StyleManager dispatches a Request.GET_PARENT_FLEX_MODULE_FACTORY_REQUEST.
  9. IModule handles the request by setting the request's value property to the module factory passed to the load function.
  10. StyleManager get return value of the request and sets its parent StyleManager to the StyleManager of the returned module factory. If the return value is null then the StyleManager of the top-level SystemManager is set a the parent StyleManager.

Prototype Work


Glenn did a prototype of StyleManager using an approach where the StyleManager is modified to track the styles of all modules. This specification describes a different design where each application has its own StyleManager. There is not prototype for this method.

Compiler Work


The generated code for styles will need to be updated.

Compiler Options

-isolate-styles
An option to determine whether a style manager is created to manage the styles for this application or module. Creating a style manager turns on the Per-Module Styles feature for the application or module being compiled. When this option is set to false the application or module will use its parent's style manager. Compiling all applications and modules with this option set to false will disable the Per-Modules Styles feature.

Flex Feature Dependencies


None.

Backwards Compatibility


  • The singleton StyleManager API will still continue to compile and only use the top-level StyleManager.
  • Flex 3 apps can be compiled with -compatibility-version=3.0.0 and the sub-ordinate StyleManagers will not be created.

Behavior

  • Modules and sub-applications will be allowed to have independent styles for the same selector. Applications that relied on the first-in wins selector rules may see a difference in behavior.

Performance


  • Property merges between StyleManagers could get expensive and is something to watch.
  • The deeper applications and modules are nested the more parent/child StyleManagers there will be. The more StyleManagers in a path from the root application to any node, the more property merges will need to happen and the more performance will be impacted.


Related

Wiki: Flex 4

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.