Saturnboy
 7.15

Building Flex 4 Containers with Multiple Content Areas

, , ,

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: CanvasGroup, HBoxHGroup, VBoxVGroup. 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:

  1. Extend SkinnableContainer – Extend SkinnableContainer or some child class. In our sample app, our custom container extends Application (which extends SkinnableContainer).
  2. Add Buckets – add some content buckets (in the form of xxxContent) 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.
  3. Add SkinParts – add some matching SkinParts (in the form of xxxGroup) as spark Groups. There are used in the custom skin to display the content. Also, I usually set required="false" to make everything optional.
  4. 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

Comments

7.15.2010

1

I think this is a great blog post!

I’m not sure you need to call invalidateSkinState() in the sidebarContent setter. The reason it’s done like this in Panel‘s controlBarContent property is because the skin state of the component changes when control bar content is present (for instance from “normal” to “normalWithControlBar”). However, here since the skin state isn’t changing, I don’t think you need it.

-Ryan

Scottae

7.15.2010

2

Very nice article. It is an eye opener for me since I am familiar with Flex 3 and new to using Flex 4. Thanks.

7.15.2010

3

@Ryan: Good catch, totally my bad. I updated the post to remove the superfluous invalidateSkinState() from the custom container code.

daniel

7.16.2010

4

Nice presentation. These are the exact same issues I struggled with when attempting to write a custom component for the face of a simple application. Reading the source code and skin file for the spark Panel container and struggling to understand what was going on took me a day or two but was an important step. I like how you make a point to call out the wrong way of doing things.

7.23.2010

5

Excellent one indeed. Thanks Justin.

7.23.2010

6

Really nice example. Thanks.

Q: in looking at the code, it’s not clear how the “contentGroup” is getting mapped to the skin since there’s no id on the RichTextArea tag in MultiContentArea.mxml and no mention of “contentGroup” in MainApp.as. Am I missing something simple?

Thanks again for the quality posts.

7.23.2010

7

@Daniel: Here is the flow:

  1. In your app, put any components you want into the <sidebarContent> bucket
  2. The custom container’s sidebarConent() setter is called
  3. In the setter, I use sidebarGroup.mxmlContent to “copy” the components to the sidebarGroup SkinPart
  4. In the skin, all you need is a <s:Group id="sidebarGroup" /> to display and position the sidebarGroup and thus the components it contains from step #1

7.23.2010

8

@justin. I got that much, but I guess my question is how does the “fourth” group of content in mainApp.xml — the part in the RichText control — get placed even though it doesn’t have an id.

I would have thought its ID would be “contentGroup” but it doesn’t have one. However, it still somehow makes it into the “contentGroup” container in the skin in your demo.

At least, that’s what I’m seeing in the “view source.”

Thanks for taking the time to explain!

7.24.2010

9

@Daniel: Ah the “magic” contentGroup. This is the “default” content bucket in SkinnableContainer. So anything that goes inside the open and close tag of a container is put into this group automatically.

For example, <s:Application> ...stuff... </s:Application>, all of the stuff goes into the default content bucket, and SkinnableContainer pushes it to the skin with a SkinPart called contentGroup.

So, if you create an Application skin or a Panel skin, there is an <s:Group id="contentGroup" /> that you need to display and position.

Hope that helps.

7.25.2010

10

Thanks Justin, all has become clear.

ash

7.27.2010

11

the flex 3-in-4 way is simple and straightforward and it’s certainly not wrong.

custom containers, skin containers, skin parts, buckets, and implementing component lifecycle methods smells of over-engineering.

i can’t remember a flex app i’ve worked on that needed a boatload of similar views, so being able to replace content in this manner (although neat) doesn’t justify the overhead.

and please everyone, don’t start building websites similar to the example. flash/flex are totally inappropriate.

7.28.2010

12

@ash: Thanks for your opinion.

I would just add that if you don’t see the need for skins, SkinnableContainer, custom containers, and friends, keep writing Flex 3. I’d just set my default SDK to Flex 3.5 in Flash Builder and not worry about it.

8.19.2010

13

Justin this is a sweet blog post, thanks for your example work here. Your totally right, presentation and content segregation is the way forward! Good luck with your FlexLayouts project also man!

Simon

8.19.2010

14

Good article indeed.

We are using the exactly the same approach to display multiple content area in our Flex 4 based application. The only difference is that our custom content areas are typed of IDeferredInstance type in order to support “progressive” loading of
separate content areas.

BTW, in Flex 3 we had an ability to create so-called “templated” components

http://livedocs.adobe.com/flex/3/html/help.html?content=templating_3.html

and I advise any Flex 3 developer to follow this route to build multi-content UI components in Flex 3.

8.19.2010

15

@Simon: Thanks man!

@Jabby: Great point about using IDeferredInstance, and a really great point about using “templated” components for the Flex 3 holdouts.

Ron

8.25.2010

16

In your example of “how to do it the flex 4 way” you don’t even seperate concerns properly yourself so imho you shouldn’t be the one talking with such a condescending tone as if you are the king of flex 4, because from what I see here you certainly are not.

9.22.2010

17

Hi,

I really like what you’ve done here. The separation is really nice, however I think there is a fundamental problem caused by the way Flex is put together. In your examples, layout is governed in your content-slots by the groups layout props inside the skins. This is great, because layout declarations are kept inside the skin, and you can declare the content inside your main mxml file without needing to polute the semantic tags with any layout code. However, what if the layout is a basic layout and you need a button at x=”30″ / y=”45″ and an image with width=”100″ height=”200″? You are stuck declaring this layout information inside the main mxml file.

If you look at the way HTML handles this, complete separation is achieved through CSS. We can almost get there, but x/y and width/height are not settable through CSS, so we are stuck.

We can work around this limitation by using another layer – a view that fits into the content slot and declares its content as skin parts, but this is a far from ideal solution.

Would be good to hear your thoughts.

9.22.2010

18

@Pedr: Being such a fan of layouts, of course I would just say that you should write a custom layout. Or download one from FlexLayouts.org.

At the core, the goal is to connect a chunk of formatting info with a content bucket. In HTML, CSS is one way. In Flex, a custom layout is one way. But what is best?

I’d say if you need a one-off view, then as you say, just code & skin that view and pass it through to the content bucket. If you need something repeatable, I’d recommend the custom layout route.

9.22.2010

19

The problem with BasicLayout is that it forces layout information into the component tags. As you say, layouts are great, but it seems like this nice separation breaks down if you need to position items individually.

I wonder if anyone has looked at creating custom layouts for non-repeating content? Every single example I’ve seen has assumed repetion. It seems to me, we should be able to create a view-specific layout that can position items individually – so that the decision about where to position them is contained in the layout, not in the MXML markup. That way we could maintain semantic declartions right down to the contents of the containers.

9.22.2010

20

In your partRemoved method: you need to call super last, otherwise your if else statement will never do anything because “instance” (being an reference object, not a value) will be null at that point.

Should be:

override protected function partRemoved(partName:String, instance:Object):void{
if(instance == headerGroup){
headerGroup.mxmlContent = null;
} else if (instance == sidebarGroup) {
sidebarGroup.mxmlContent = null;
} else if (instance == footerGroup) {
footerGroup.mxmlContent = null;
}

super.partRemoved(partName, instance);
}

9.24.2010

21

I ran into some fun with custom states within the component. Managed to get it working (hoping I have not missed a trick) and blogged the bad boy.

Peace

Neil

10.19.2010

22

The flex docs say that we should not be using the mxmlContent property directly, what would be the alternative?

Thanks for sharing your efforts.

10.19.2010

23

@Neil: I really just followed what the Adobe engineers did when they build the spark Panel component. Since they used mxmlContent, I used mxmlContent. As far as I know, it’s not possible any other way.

Sachin Chandorkar

4.17.2012

24

Excellent article. Thanks for the explanation :)

Stg

8.19.2012

25

Hi! I followed your example and it works really well, till point where I try to bind some properties of elements in for ex. footer to properties in header. bindings simply not working…:( Can you tell, what can be a problem

© 2014 saturnboy.com