Tag Archives: components

It’s an Interface; It’s a Code-Behind; It’s a … Skin?

I managed to avoid skinning components in Flex 3 (I called it “dancing kitties”). I just made the components work. I tried to ignore the changes in Flex 4 skins, but Spark components are very different than MX components, although Adobe has succeeded in making the changes apparently minor.

In Flex 2 and 3, “skins” were CSS styles and images and, rarely, bits of code that created graphics.

What Flex 4 Skins Are

Flex 4 skins are ActionScript classes that are completely responsible for presenting the data from another ActionScript class.  Custom components can inherent behavior from their class ancestors (UIComponent, Group, etc.), but builds their appearance by choosing or creating a custom skin class. The component has the properties and methods to manage the component’s behavior and data, and the skin class presents it to the user. A skin class usually descends from the ancestor class Skin (itself a child of Group) and contains other components (e.g. Labels, Images, RichTextEdit) to display and gather data, (somewhat like the way a custom item-renderer contains other components). The nesting can seem a little recursive, but it has the same advantages as any other object oriented protocol: each object does its thing with loose linkages to other objects.

It’s Code-Behind

The relationship between a component and its skin is like a code-behind pattern: specified component class variables refer to the skin class’ sub-components by name. As the skin class constructs, it passes each of its sub-compents (e.g. Buttons) to the host component. The host component does two things: first, if it finds a variable annotated with a [SkinPart], it assigns the instance to the value from the skin. Second, it calls the addPart() method so the component can add event listeners, etc. For example, if a skin class used with a Button has a sub-component with the id “labelDisplay”, that sub-component’s “text” property will get the Button.label value via the ButtonBase.setContent() method:

The Component The Skin
[SkinPart(required="false")]
public var labelDisplay : IDisplayText;   // must have a .text property
<s:Label id="labelDisplay" />
protected function setContent(value : String) : void
{
    _content = value;  //    this stores the value in the component

    if (labelDisplay != null)  // it's not required, so it could be null
        labelDisplay.text = value;
}
The Label component displays the new value

It’s an interface

And I mean with a lower-case “i”: it’s is not an ActionScript Interface It is an agreement between the component and the skin so they can work together (actually, the component requires and the skin fulfills):

  • [SkinPart(required="true")] or [SkinPart]on the component requires the skin to have a sub-component with a specific name; the class will throw an exception during creation without it
  • [SkinPart(required="false")] on the component will connect to a skin’s sub-component with a specific name if it’s present

It’s a View

So skins have evolved from being styles and bitmaps to more of a MVC-type view, and the component acts as a controller and model. The component keeps a copy of all the visible data (like label texts) so it can change and initialize skins at any time. When a skin initializes, the component gets a notification for each sub-component of the skin object, and the component can add an event listener or set its value. While the component can query the skin about constraint-based position and sizes, the less the component knows about the view, the better.

Likewise, the less the skin knows about the component, the better. The component handles all the interaction with the outside world: methods to set and filter the data, validation of property values, etc. The skin gets data values and occasionally styles and top-level dimensions. The component uses its code-behind references directly to the skin’s sub-components to set property values and get events.

Binding Optional

While binding is very cool, this protocol does not usually use events and watchers and binding stuff: the code in the component explicitly assigns values to its references of the skin’s sub-components (e.g. SkinnableTextBase.setPrompt()). The skin can bind to the component’s properties (e.g. hostComponent.title), but it’s unusual.

The Component’s View of the Skin

These are the states, properties, and methods of the component that give it access to and control over the skin.

skin class
(will inherit from ancestor classes)
.skinClass
required skin states [SkinState("xxx")]
skin sub-components
[SkinPart(required="true")]
public var closeIcon : Button;

[SkinPart(required="false")]
public var labelDisplay : IDisplayText;   // must have a .text property
add/remove event listeners to skin objects
override protected function partAdded(partName : String, instance : Object) : void
{
    super.partAdded(partName,instance);
    if (instance == closeIcon)
    {
        closeIcon.addEventListener(MouseEvent.CLICK, onCloseIcon);
        closeIcon.source = getStyle(“icon”);
    }
}

override protected function partRemoved(partName : String, instance:Object) : void
{
    super.partRemoved(partName, instance);

    if (instance == closeIcon)
        closeIcon.removeEventListener(MouseEvent.CLICK, onCloseIcon);
}
skin instance
(rarely used)
.skin
supporting methods
SkinnableComponent.invalidateSkinState();
// this forces a call to getCurrentSkinState()
other miscellaneous overrides
override protected function getCurrentSkinState() : String { 

//    translate component states into skin states
//    note: the base class' method, 
//            SkinnableComponent.getCurrentSkinState(), returns null
}

override public function styleChanged(styleProp : String) : void
{
    super.styleChanged(styleProp);

    if (styleProp == “icon”)
        closeIcon.source = getStyle(“icon”);
}
data values push values into the skin using code-behind
currentState the component sets the skin’s state to reflect some aspect (e.g. disabled)

The Skin’s View of the Component

These are the properties the skin used to receive and communicate with the host. In general, the skin assumes that the synchronization with the component will push values into the sub-components and change the skin’s state; it does not reference the .hostComponent directly.

host class [HostComponent("spark.components.Button")]
host instance
(rarely used)
.hostComponent
data values pushed into values by the component
currentState the component sets the skin’s state to reflect some aspect (e.g. disabled)

Skin State

The component can require specific skin states (e.g. “normal”, “disabled”). It uses those states to instruct the skin to change appearance, but leaves the specifics up to the skin. Adobe could have used an event, an expected method, or some other way to signal the skin; the advantage of using states is that it’s easy to set up an MXML component to respond to states. The base component’s invalidateSkinState() method (called when the component’s state change, for example) triggers a call to getCurrentSkinState(). That method examines the component’s properties and returns the skin state that fits.

For example, SkinnableTextBase requires the skin to have states “normalWithPrompt” and “disabledWithPrompt”, and uses them as appropriate in getCurrentSkinState() to have the skin show the .prompt text (using whatever sub-component the skin chooses to show it).

Object-Oriented Skins … Or Not

Adobe has been uncharacteristically forceful in their declaration that  we should not create inheritance hierarchies of skin classes; Adobe wants us to copy and paste when creating new skin classes. Part of that is political: they have lots of tools for designers to work with developers, and designers do not sub-class anything. Although I am a OOP-freak, I see their point: skins are so thin (i.e. they have very little behavior implemented in code) that the logistical overhead of having some parts in the ancestor and some in a descendant does not have much benefit to balance it. A well-designed skin class can use CSS as a replaceable properties file, and that can bundle a lot of behavior in a thin class without sub-classes.

 

Reuse … Your Brain

Components were supposed to save us; they were the silver bullet that would make code reuse actually work. The problem with reusing code is that it takes at least three times as much work to make a component that works in all cases rather than just the ones the component author had in mind, and no one wants to put the work in unless they are getting paid.

The fun part of components is figuring out the best level of encapsulation and making a piece that fits nicely into that spot. Testing, debugging, documentation, and validation are not (for most people) the fun parts. for components to be useful in more than one context, they have to be internally and externally consistent, they have to handle attribute changes in all possible combinations, and they have to handle external error conditions at every possible point. Software libraries and SDK’s distribute themselves as classes and components, and they make applications possible. For the infrastructure organizations and component vendors, this is their business.

Components built within organizations do not have the support of paying users, and so they tend not to have the fit and finish of their commercial brethren. A commenter named Tim put it well in a discussion about Agile/XP and reuse:

The first time you have to pay for it to be coded,
The second time you have to pay for it to be reusable,
The third time it’s free.

With all due respect to Agile and XP, I err towards the other end of the spectrum: I like to build software clean enough that I am not embarrassed to have others use it. I want it so obvious that I will understand it quickly when I come back to it later. I like building well-rounded components that will survive a little transplantation or changes in usage. I like to take the time to make classes that know how to clone, clear, deep-copy/assign, and test equality. In other words:

Treat Yourself As You Would Have Others Treat You

Nevertheless, managers see a component named DataManager and assume (or at least hope) they can reuse it anywhere their design calls for a data manager. They don’t want to “waste” the effort spent building it the first time. Here is when intuition fails us all: we talk of “building” software instead of “composing” it. Software is ideas made manifest (albeit in a structure that judges those ideas against a very solid and unforgiving reality).  Software is not an engine where the number of cylinders is fixed. However, composing a better component is easier than building a second house. It’s often impossible to rebuild a component exactly the same as its original because the composer/author/developer/coder can’t stop themselves from improving their idea.

Besides running the same code in different environments, the best reuse is reusing experience. Experience grows all the time (and grows faster from bad outcomes). Rather than looking at components as commodities that remove the need for experienced developers, look at components as the steps that developers can stand on to see farther.