HowTo: Force Flex to apply width & height with CSS (and PureMVC)

I love the tools I work with but if I’m being honest, some things just don’t measure up. CSS in Flex for instance: it doesn’t do half the things a genuine CSS implementation should do. Thanks go to Tom Kennett for bringing to our attention this great blogpost about just how far short of the mark Flex’s CSS support falls.

One of the most irritating aspects is the fact that the ‘width’, ‘height’, ‘percentWidth’ and percentHeight’ properties of UIComponent - which is the base class for all visual components - are exactly that: properties, not styles. That means they can’t go into the CSS, they have to be added to the mxml tags themselves:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application 
  xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:VBox width="100%">
    <mx:HBox id="header" width="100%" height="80" />
    <mx:HBox id="content" width="100%" height="100%" />
    <mx:HBox id="footer" width="100%" height="50" />
  </mx:VBox>
</mx:Application>

Even in an unrealistically small file like this, the ‘width’ and ‘height’ attributes make it difficult to read. When working with actual, lengthy .mxml files, a bit of simple editing can become like hacking through the jungle. I’m not really into that so I thought I’d sit down today and see if I could concoct a workaround that would allow me to put the ‘width’ and ‘height’ declarations in the CSS instead.

The UIComponent class implements a method called ‘updateDisplayList’, which gets called when a component is instantiated and thereafter whenever a resize event occurs. This makes it an excellent candidate for modifying width and height properties. It’s a ‘protected’ function so if you wanna utilise it you have to extend UIComponent or a subclass of UIComponent, such as ‘Box’, and override it.

package uk.co.richtextformat.flex
{
  import mx.containers.Box;
 
  public class Page extends Box
  {
    public function Page()
    {
      super();
    }
 
    override protected function updateDisplayList
      (unscaledWidth:Number, unscaledHeight:Number):void
    {
      super.updateDisplayList(unscaledWidth, unscaledHeight);
    }
  }
}

UIComponent also puts a method called ‘getStyle’ at your disposal. It accepts the name of a style associated with a class and returns the value of said style. Our example above extends ‘Box’ and if that class had a style associated with it in the the Flex app’s CSS stylesheet, you could use ‘getStyle’ to access it, like this:

Box
{
  borderStyle: "solid";
}
var borderStyle:String = someBox.getStyle("borderStyle")

Now I had assumed until recently that you could only access genuine CSS styles. Well, I say ‘genuine’, what I mean is the documented styles associated with a given class. But interestingly you can put any property in your CSS declaration and access it via getStyle:

Box
{
  numberOfSides: 6;
}
var numberOfSides:int = someBox.getStyle("numberOfSides")

This means we can create any CSS style properties we want and use updateDisplayList to get hold of them on a resize event.

So, knowing that, how about we create a static method that can get hold of a UIComponent, look for width and height styles belonging to it and apply those styles to the UIComponent’s width and height properties? As ‘width’ and ‘height’ are dimensions we could call the method ‘DimensionsManager.setDimensions’ and to make it nice and adaptable we could pass it just a single UIComponent or a whole array of them. We’d have to recognise that a generic method probably needs generic style names to look for but that as the actual names ‘width’, ‘height’, etc. are already taken we’ll need slightly different names. As all visual components inherit from UIComponent we could use ‘componentWidth’, ‘componentHeight’, ‘componentPercentWidth’ & ‘componentPercentHeight’. And maybe if we’re doing all this we could add support for minimum dimensions too, with ‘componentMinWidth’ & ‘componentMinHeight’.

package uk.co.richtextformat.flex
{
  import mx.containers.Box;
 
  import uk.co.richtextformat.flex.utils.DimensionsManager;
 
  public class Page extends Box
  {
    public function Page()
    {
      super();
    }
 
    override protected function updateDisplayList
      (unscaledWidth:Number, unscaledHeight:Number):void
    {
      super.updateDisplayList(unscaledWidth, unscaledHeight);
      DimensionsManager.setDimensions(this);
    }
  }
}
Page
{
  componentWidth: 300;
  componentHeight: 300;
  componentPercentWidth: 50;
  componentPercentHeight: 20;
  componentMinWidth: 1;
  componentMinHeight: 1;
}

Having done all this, we no longer need to put up with confusing, ugly mxml and the whole job becomes much simpler and enormously more readable. In the following example the ‘pages:HomePage’ component has its dimensions controlled via the above CSS style:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  xmlns:pages="uk.co.richtextformat.flex.*">
 
  <mx:Style source="default.css" />
  <pages:HomePage />
 
</mx:Application>

Now that’s good… but there’s still something about not quite right about it. The ‘Page’ class above only extends ‘Box’. What would we do if we wanted to control the widths and heights of instances of other classes, like ‘Canvas’ and ‘Button’. We’d have to subclass all of them have a whole bunch of near identical classes with names like ‘DimensionableCanvas’ and ‘DimensionableButton’. Not good.

So what can we do? What we need is a single place we can put the call to ‘DimensionsManager.setDimensions’. The only real way to do this would be to add it to UIComponent’s own ‘updateDisplayList’ method and that’s obviously not possible. In fact using a standard Flex project it’s near impossible but that’s OK because there is another option. If we build our project around the PureMVC framework we give ourselves a chance.

This isn’t really the place to discuss PureMVC. If you’re not familiar with it you can head over to their site and read about it. For the purposes of this discussion it suffices to say that PureMVC utilises a class called ‘Mediator’ which acts as a kind of manager for visual components. When PureMVC is implemented correctly every visual component is supposed to have it’s own instance of ‘Mediator’ and thus if we create a subclass of ‘Mediator’ - called, say, ‘DimensionableMediator’ - we can put the call to ‘DimensionsManager.setDimensions’ in there and that tidies the whole thing away for us. Thereafter we can apply any of the six CSS styles we created above to any subclass of UIComponent without adding any extra code to our mxml components, ActionScript classes or CSS stylesheets.

Here’s an example of an extension of ‘Mediator’ that suited my purpose. I’m sure you’ll recognise it if you’re familiar with PureMVC:

package uk.co.richtextformat.flex.puremvc
{
  import mx.core.UIComponent;
 
  import org.puremvc.as3.multicore.interfaces.IMediator;
  import org.puremvc.as3.multicore.patterns.mediator.Mediator;
 
  import uk.co.richtextformat.flex.utils.DimensionsManager;
 
  public class DimensionableMediator extends Mediator 
    implements IMediator
  {
    public function DimensionableMediator
      (mediatorName:String=null, viewComponent:Object=null)
    {
      super(mediatorName, viewComponent);
      DimensionsManager.setDimensions(this.viewComponent as UIComponent);
    }
  }
}

Here a single ‘Mediator’ is setting the dimensions on its single ‘viewComponent’ property via it’s constructor method. To use this when writing a new ‘Mediator’ you’d just extend ‘DimensionableMediator’ instead of ‘Mediator’. Also, as DimensionsManager can accept a whole array of UIComponents, you can throw another call to ‘DimensionsManager.setDimensions’ somewhere in your subclass of ‘DimensionableMediator’ and resize every last ‘Canvas’. ‘Box’ and ‘Button’ in the visual component your ‘Mediator’ controls. Great!

One last thing: Flex priorities percentage widths over fixed, pixel widths, therefore DimensionsManager should work the same. If a stylesheet style has a ‘componentPercentWidth’ property and a ‘componentWidth’ property the former should be used rather than the latter. And that’s exactly what the DimensionsManager class I wrote this afternoon does.

Feel free to download the DimensionsManager class - AND bonus class ‘DimensionableMediator’ too, you lucky things! - here.

:)

Tags: , , , , , , ,

One Response to “HowTo: Force Flex to apply width & height with CSS (and PureMVC)”

  1. rtf Says:

    having just got up this morning it has occurred to me that if there are any other things missing from or incorrectly implemented Flex' CSS support - as there undoubtedly is - we could rename 'DimensionsManager' to something like 'CSSImplementor' and give it a whole bunch of other static methods too, to adjust aspects beyond just width and height, including one method called 'setAllCSSStyles' which could simple call all the class' other static methods in turn.

    i don't really have the time or the need to do this right now but if i get a second later i might. or in any enterprising visitor - you maybe? - wants to give this a go, i'd be MORE than happy for you to extend/whatever the DimensionsManager class. :)