Back in the days of Flex 3, if you wanted multiple content areas in your main application, you’d need to arrange some set of containers (Canvas, HBox, VBox) in the app and fill them with content. It was just your basic Flex 3 development process. The danger, of course, is that you are mixing content with presentation, aka bad separation of concerns. Today, with the power of Flex 4 skins, we can avoid this issue by moving the presentation layer into a skin (or set of skins). And thus, we can do a much better job achieving a happy level of separation of concerns.
The Flex 3 Way
To give a concrete example, I’ll build a blog layout (yes, another blog layout) with a header, footer, sidebar, and main content areas. But before we get started, let’s review the old Flex 3 way:
<mx:HBox id="header">
<mx:Image source="@Embed('assets/logo.png')" />
</mx:HBox>
<mx:Canvas id="body" width="800">
<mx:Text text="main content" width="600" />
<mx:VBox id="sidebar" x="600" width="200">
<mx:Text text="Sidebar" />
<mx:Text text="sidebar content" width="100%" />
</mx:VBox>
</mx:Canvas>
<mx:VBox id="footer">
<mx:Text text="2010 saturnboy" styleName="footer" />
</mx:VBox>
The above code comes from a previous post, Designing in Flex 3, but has been modified to make sense here. You’ve got you basic blog design: a box for the header, footer, and body, where body is subsequently is divided into a main content area and a sidebar.
The 3-in-4 Way, aka The Wrong Way
The unfortunate next step in a Flex developer’s evolution is what I like to call the Flex 3-in-4 way. This is a the way of neanderthals, which is to say, it is an evolutionary dead end. If you ever have the bad luck to see 3-in-4 code, you can be sure you are dealing with a novice Flex 4 developer. In general, the 3-in-4 way consists of making the simple transcription: Canvas → Group, HBox → HGroup, VBox → VGroup. But the most damning tipoff of a 3-in-4 developer is the assertion that one is now a Flex 4 developer and the learning curve wasn’t all that bad. While I do think Flex 4 is more of an evolutionary release than a revolutionary release, it’s different enough. And it is particularly different on the design side of the framework, how it handles skins, layout, etc.
If we just transcribe the above example, we get some classic 3-in-4 code:
<s:HGroup id="header">
<s:Label text="Multi Content Area Example" styleName="header" />
</s:HGroup>
<s:Group id="body" width="800">
<s:Label text="main content" width="600" />
<s:VGroup x="600" width="200" styleName="sidebarBox">
<s:Label text="Sidebar" styleName="title" />
<s:Label text="sidebar content" styleName="sidebar" />
</s:VGroup>
</s:Group>
<s:VGroup id="footer">
<s:Label text="2010 saturnboy" styleName="footer" />
</s:VGroup>
*Barf*, please not do this. This code has all the same issues as the Flex 3 code in the first example, and moreover it is a slap in the face of The Flex 4 Way and all of its improvements.
The Flex 4 Way
Yes, there is a Flex 4 Way and it looks like this.
First, we rewrite the main app using a custom container. Ignoring the specifics of the custom container for a moment, here is the re-written main app (minus some clutter):
<containers:headerContent>
<s:Label text="Multi Content Area Example" styleName="header" />
</containers:headerContent>
<containers:sidebarContent>
<s:RichText left="0" right="0" styleName="sidebar">
<s:content>
<s:p fontSize="20">Sidebar</s:p>
<s:p>sidebar content</s:p>
</s:content>
</s:RichText>
</containers:sidebarContent>
<containers:footerContent>
<s:Label text="2010 saturnboy" styleName="footer" />
</containers:footerContent>
<s:RichText left="0" right="0">
<s:content>
<s:p fontSize="30">Content</s:p>
<s:p>main content</s:p>
</s:content>
</s:RichText>
As you can see, the main app is now a nice set of semantic buckets, one for each of the content areas. Header stuff goes in the headerContent bucket, footer stuff goes in the footerContent bucket, etc.
Building a Multi Content Area Container
Second, we need to create a custom container with the nice set of semantic buckets used in the above code. This is achieved by following a straightforward formula:
- Extend SkinnableContainer – Extend
SkinnableContainer or some child class. In our sample app, our custom container extends Application (which extends SkinnableContainer).
- Add Buckets – add some content buckets (in the form of xxx
Content) as Arrays. These become the MXML tags used to bucket components together. Each content bucket has a public getter, but most importantly a public setter that accepts an incoming Array of IVisualElements and uses the magical mxmlContent property to assign it to the associated SkinPart.
- Add SkinParts – add some matching SkinParts (in the form of xxx
Group) as spark Groups. There are used in the custom skin to display the content. Also, I usually set required="false" to make everything optional.
- Add partAdded() & partRemoved() – override the new Flex 4 skinning lifecycle methods to wire the incoming content to the outgoing
SkinPart.
The custom component code is actually easier to follow then the description. Here is a custom container with only one additional content bucket, sidebarContent, and its matching SkinPart, sidebarGroup:
package containers {
import spark.components.Group;
import spark.components.Application;
public class MainApp extends Application {
[SkinPart(required="false")]
public var sidebarGroup:Group;
private var _sidebarContent:Array = [];
public function MainApp() {
super();
}
[ArrayElementType("mx.core.IVisualElement")]
public function get sidebarContent():Array {
return _sidebarContent;
}
public function set sidebarContent(value:Array):void {
_sidebarContent = value;
if (sidebarGroup) {
sidebarGroup.mxmlContent = value;
}
}
override protected function partAdded(partName:String, instance:Object):void {
super.partAdded(partName, instance);
if (instance == sidebarGroup) {
sidebarGroup.mxmlContent = _sidebarContent;
}
}
override protected function partRemoved(partName:String, instance:Object):void {
super.partRemoved(partName, instance);
if (instance == sidebarGroup) {
sidebarGroup.mxmlContent = null;
}
}
}
}
Following the four steps: we extend Application, have a sidebarContent bucket and its associated sidebarGroup SkinPart, and override partAdded() and partRemoved() to wire everything together.
Skinning a Multi Content Area Container
Skinning in Flex 4 is awesome, and like everyone says, it’s easily one of the best new features in the framework. While I find the skinning process fairly straightforward, I would never call it trivial, mostly due to the depth and flexibility of the skinning system.
We need a custom skin for our custom multi content area component. This is probably the 10% case for skinning, but it’s also the coolest. In my experience, an average Flex 4 app has many Button skins (like 10 or even 20), a few default component skins (skins for List, DropDownList, TextInput, etc.), and maybe only two or three skins for custom components.
The skin itself is nothing special. To display our custom component’s SkinParts, we simply include a Group with the matching id attribute. For example, our skin will include a <s:Group id="sidebarGroup" /> to display the sidebarGroup SkinPart. Just rinse, wash, repeat, to add all of our custom content areas in the container to the skin.
Here is a trivial skin:
<s:Skin ... >
<fx:Metadata>
[HostComponent("containers.MainApp")]
</fx:Metadata>
<s:states>
<s:State name="normal" />
<s:State name="disabled" />
</s:states>
<s:VGroup left="40" right="40" top="40" bottom="40" gap="20">
<s:Group id="headerGroup" width="100%" />
<s:Group id="contentGroup" width="100%" />
<s:Group id="sidebarGroup" width="100%" />
<s:Group id="footerGroup" width="100%" />
</s:VGroup>
</s:Skin>
In this trivial skin, we just shove all the content groups (including SkinnableContainer‘s default Group, contentGroup) into a VGroup. Also note, we correctly set HostComponent to our custom container. If you are thinking, "Hey, this skin looks similar to the Flex 3 and 3-in-4 example code, just minus the content" that’s exactly the point.
Lastly, we wire out skin to our custom component via CSS:
containers|MainApp {
skinClass:ClassReference('skins.TrivialAppSkin');
}
Using skinClass to wire a skin to a component is so 2009. The sample app has its CSS inline, but in any real app I’ll always put this in an external file.
Conclusion
After this, there’s really not much more to say. You can certainly create a more complicated arrangement of the multiple content areas by making a more complicated skin. I’ve done exactly this in the final sample, which includes three different skins and a skin switcher (click 1, 2, or 3 to switch skins).
» view MultiConentArea sample (view source enabled)
Files
