Hacking width and height properties into Flex’s CSS model

As much as I love working with the Flex layout model, in many ways it feels inferior to its HTML cousin. I’ll take Flex’s container components over HTML tables and divs any day, but I’ve always been disappointed with the fairly weak CSS model found in Flex. From an architectural level the framework decisions makes sense and keeps internal logic fast and clean, but from an implementation standpoint, it becomes an annoying roadblock. Heres a quick example of the problem.

<mx:Style>
	.navItem {
		vertical-align: middle;
		corner-radius: 5;
		border-style: solid;
		border-color: #F3CB19;
		background-color: #A87500;
	}
</mx:Style>
<mx:HBox styleName="navItem" height="30"/>
<mx:HBox styleName="navItem" height="30"/>
<mx:HBox styleName="navItem" height="30"/>

In this case, since height is a class property, it must be defined on the HBox instance and can’t be moved into CSS. It makes the code less manageable and adds restrictions to changing themes at runtime.

In order to define class properties with CSS we need to break some of the framework rules. Since we can’t have class metadata defining a value as both a CSS and a class property, we need to leave all metadata as-is, and ‘foward’ the css definitions on to the class. In practical terms what this means is that changing the height in css will change the height on the component, but changing the height directly on the component wont update the css definition. The code below illustrates how this plays out inside a custom component.

package com.craftymind.controls
{
	import mx.containers.HBox;
	import mx.styles.StyleManager;

	public class extHBox extends HBox
	{
		override public function styleChanged(styleProp:String):void{
			super.styleChanged(styleProp);
			if(!styleProp || styleProp == "styleName"){ //if runtime css swap or direct change of stylename
				var classSelector:Object = StyleManager.getStyleDeclaration("." + styleName);
				if(classSelector != null){
					applyProperties(classSelector, ["width", "height", "percentWidth", "percentHeight", "x", "y", "visible"]);
				}
			}
		}
		private function applyProperties(styleObj:Object, arr:Array):void{
			for each (var item:String in arr){
				var prop:Object = styleObj.getStyle(item);
				if(prop != null) this[item] = prop;
			}
		}
	}
}

When the HBox detects that the styleName or runtime definition has changed, it scans the classSelector for width, height, x, y and visible properties to be applied. You can add whatever properties you want in there including text labels or image sources depending on the component being extended. I haven’t fully tested it so I’m not sure if type selectors work, and I know that calling obj.setStyle(“height”, 10); definitely wont work, but your better off calling obj.height = 10 in that case anyway.

Heres a complex menu system controlled completely with css including y positioning, height changes, and header visibility.


View Source

We now have separation of content from style thats alot closer to what HTML offers, and as a bonus, all property-based databinding continues to work on the values passed through CSS.

<cm:extHBox styleName="header">
	<mx:Label text="Header Text"/>
</cm:extHBox>
<cm:extHBox id="nav" styleName="navContainer">
	<cm:extHBox styleName="navItem">
		<mx:Label text="Nav Item 1"/>
	</cm:extHBox>
	<cm:extHBox styleName="navItem">
		<mx:Label text="Nav Item 2"/>
	</cm:extHBox>
	<cm:extHBox styleName="navItem">
		<mx:Label text="Nav Item 3"/>
	</cm:extHBox>
</cm:extHBox>
<mx:Button label="Switch CSS" click="switchCSS()" y="{nav.y+nav.height+2}"/>
.header {
	background-color: #000000;
	border-style: solid;
	border-color: #FFFFFF;
	border-thickness: 2;
	color: #FFFFFF;
	x: 0;
	y: 0;
	percent-width: 100;
	height: 20;
	visible: true;
}
.navContainer {
	background-color: #000000;
	border-style: solid;
	border-color: #FFFFFF;
	border-thickness: 2;
	vertical-align: bottom;
	horizontal-align: left;
	padding-left: 3;
	x: 0;
	y: 20;
	percent-width: 100;
	height: 50;
}
.navItem {
	vertical-align: middle;
	corner-radius: 5;
	border-style: solid;
	border-sides: "left top right";
	border-color: #FFFFFF;
	border-thickness: 2;
	background-color: #000000;
	padding-left: 3;
	padding-right: 3;
	color: #FFFFFF;
	height: 30;
}

8 thoughts on “Hacking width and height properties into Flex’s CSS model”

  1. This is very cool and useful – thanks for posting it. However, it seems to have a limitation: if you want to specify the position or dimensions of a component through CSS, you have to put it in an extHBox. What I really wanted is to be able to specify dimensions for, say, a text label directly in that text label’s CSS declaration.

    So here’s what I came up with. This is a Canvas that not only gets its position and dimension properties from its CSS declaration, but gives the same flexibility to its children.

    package view.containers
    {
    /* inspired by http://www.craftymind.com/2008/03/31/hacking-width-and-height-properties-into-flexs-css-model

    This is a canvas that allows for the properties width, height, etc (see the properties array) to be set
    through the component’s CSS file.
    */

    import mx.containers.Canvas;
    import mx.core.IUIComponent;
    import mx.core.UIComponent;

    public class CanvasExt extends Canvas
    {
    private static const properties : Array = [“width”, “height”, “percentWidth”, “percentHeight”, “x”, “y”, “visible”];

    public function CanvasExt()
    {
    super();
    }

    override public function styleChanged(styleProp:String):void
    {
    super.styleChanged(styleProp);
    if(!styleProp || styleProp == “styleName”)
    {
    //if runtime css swap or direct change of stylename
    updateProperties(this);
    for ( var i : int = 0; i < numChildren; i++ )
    {
    var child : UIComponent = UIComponent ( getChildAt ( i ) );
    updateProperties ( child );
    }
    }
    }

    private function updateProperties(obj:UIComponent):void
    {
    for each (var item:String in properties)
    {
    var prop:Object = obj.getStyle(item);
    if(prop != null)
    obj[item] = prop;
    }
    }
    }
    }

  2. Hi, I read your post because I was looking info on how to change width, height and other properties trough css.

    I came with a solution to modify the CSS when you change the properties.

    I spent a few hours trying to do so, and I’ll be gladly if you tell me of a way to improve the code.

    Basically what I did was overriding the setters for those properties. Pretty much like this:

    class …. extends Box{

    private var _RealizarModificacionAPropiedadInternaPublica:Boolean;

    override public function set width( value:Number ):void{

    if( this._RealizarModificacionAPropiedadInternaPublica ){

    super.width = value;
    this._RealizarModificacionAPropiedadInternaPublica = false;

    }else{

    this.setStyle( CineticaContenedorCorredizo.ESTILO_ANCHO_PRINCIPAL, value );

    }

    }

    override public function styleChanged( styleProp:String ):void{

    switch( styleProp ){

    case CineticaContenedorCorredizo.ESTILO_ANCHO_PRINCIPAL:

    this._RealizarModificacionAPropiedadInternaPublica = true;
    this.width = this.getStyle( ‘width’ );
    this.invalidateDisplayList( );
    break;

    default:
    super.styleChanged( styleProp );
    break;

    }

    }

    }

    Let me know what you think.

  3. I’m using exactly the code you provide above. I’m loading different compiled CSS at runtime to change the size of the button. When I load a CSS without any width and height properties, the width and height do not revert to the default values. That is, the button does not automatically resize to fit the text of the label and they retain the width and height of the last CSS loaded. I tried changing the IF statement in applyProperties to

    if (prop != null)
    this[item] = prop;
    else
    this[item] = 0;

    but my button becomes invisible when I don’t specify width and height in the CSS. Any suggestion?

  4. Hi,

    for existing components i do this:

    <mx:Button label="…" click="action()" styleName="myStyle"
    height="{StyleManager.getStyleDeclaration('.myStyle').getStyle('height')}"

    styleName – attribute is for other css properties
    :)

Comments are closed.