Migrating legacy themes to Feathers 2.0

Feathers 2.0 includes a new skinning architecture. The DisplayListWatcher class that legacy themes extended still exists, and you can continue using it for the foreseeable future. However, if you're ready to modernize your theme to take advantage of the new style provider system, you will need to make a number of fundamental changes to your themes.

Extend a new class: StyleNameFunctionTheme

A legacy theme will extend the feathers.core.DisplayListWatcher class.

// legacy
public class CustomTheme extends DisplayListWatcher

To create a modern theme, extend feathers.themes.StyleNameFunctionTheme instead.

// modern
public class CustomTheme extends StyleNameFunctionTheme

After this change, if you try to compile, you will probably see a number of errors. We need to make a few more changes, but they're pretty straightforward.

Replace calls to setInitializerForClass()

The modern StyleNameFunctionTheme still calls functions that set properties on components, similar to legacy themes. You can still use strings (called style names) to differentiate between components of the same type that need to have different appearances. The API has changed a bit for setting these functions, though.

In a legacy theme, you might call setInitializerForClass() and pass in a class and a function. You could optionally pass in a style name as the optional third argument, to specify function for alternate styles:

// legacy
this.setInitializerForClass( Button, setButtonStyles );
this.setInitializerForClass( Button, setCustomButtonStyles, "my-custom-button" );

In a modern theme, you first ask for then global style provider for a specific class. Then, you can either set its default style function or set a function for a specific style name:

// modern
this.getStyleProviderForClass(Button).defaultStyleFunction = setButtonStyles;
this.getStyleProviderForClass(Button).setFunctionForStyleName( "my-custom-button", setCustomButtonStyles );

The quick-and-dirty way

Replacing every call to setInitializerForClass() can be time consuming and tedious. If you need to migrate faster, and you want to worry about cleaning things up later, you can copy the following function into your theme class:

public function setInitializerForClass(type:Class, styleFunction:Function, styleName:String = null):void
{
    var styleProvider:StyleNameFunctionStyleProvider = this.getStyleProviderForClass(type);
    if(styleName)
    {
        styleProvider.setFunctionForStyleName(styleName, styleFunction);
    }
    else
    {
        styleProvider.defaultStyleFunction = styleFunction;
    }
}

As you can see, it implements setInitializerForClass() with the same function signature, but it uses style providers under the hood.

Replace calls to setInitializerForClassAndSubclasses()

There is no direct replacement for this function. It mainly existed to work around limitations in the legacy architecture where a subclass wouldnt be automatically skinned like its superclass. A modern theme will treat subclasses the same as their superclasses (unless a component chooses to opt out), so this function is no longer necessary for its original purpose.

Here's an example of calling setInitializerForClassAndSubclasses() in a legacy theme:

// legacy
this.setInitializerForClassAndSubclasses( Scroller, setScrollerStyles );

You should switch to calling that function directly when you style the subclasses. For instance, if you have a function that sets common styles on the Scroller class, you would call that function in a function that sets specific styles on the List class.

// modern
protected function setListStyles( list:List ):void
{
    this.setScrollerStyles( list );
 
    // set other styles here
}

When an instance of the List class (or any of its subclasses) needs to be styled, setScrollerStyles() will be called too.

Replace calls to exclude()

In a legacy theme, you could exclude a component from being skinned by passing it to the exclude() function defined by DisplayListWatcher.

// legacy
theme.exclude( button );

In a modern theme, you can remove a component's style provider:

// modern
button.styleProvider = null;

Make sure you do that before the component initializes. That's when the theme is asked to style the component. By default, a component will initialize when it is added to the stage.

Replace name and nameList with styleName and styleNameList

In order to fix some issues developers had using getChildByName(), Feathers no longer uses the name and nameList properties to indicate to the theme that it should give a component an alternate visual appearance.

In a legacy theme, you would add a string to the nameList property (or set the name property directly):

// legacy
button.nameList.add( "my-custom-button" );
// or
button.name = "my-custom-button";

In a modern theme, you should use the styleNameList or styleName properties instead:

// modern
button.styleNameList.add( "my-custom-button" );
// or
button.styleName = "my-custom-button";

The nameList property still exists, temporarily. It simply maps to the styleNameList property so that legacy code will continue to work. However, the nameList property is considered deprecated, and it will be removed in a future version of Feathers.

The name property is no longer used for styling Feathers components at all. It does not map to the styleName property the way that nameList maps to the styleNameList property. This change makes a strict distinction between name and styleName in order to fix issues using getChildByName() with Feathers components.