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.
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; }