Archive for December, 2009

Adding width, height, x and y into the CSS Model of Flex

You know the problem, you are styling your flex application using css – you might even compile the css externally and load it through StyleManager at runtime. This is all fine, but when it comes to complex layouts you face one ugly thing, you can not set width, height, x and y through css as those are actionscript properties and not styles. But that quite sucks, you have to size everything through top, left, bottom and right styles – but sometimes you don’t want that.

To overcome that restriction I seeked throught the whole net and found the following

http://www.craftymind.com/2008/03/31/hacking-width-and-height-properties-into-flexs-css-model/

That was a nice base, but it required to override all the damn classes – this is no real solution for me, I need to have something easy I can just put into my application and get going. So i took what I found there and made another util out of it – the StylePropertyManager, this small class will help you to get to results as easy as possible. Here is an example how to use it (put the following into your main application or any container which should have it’s properties, and child properties, in the css model):

override public function styleChanged(styleProp:String):void{
	super.styleChanged(styleProp);
	StylePropertyManager.notifyStyleChanged(this,styleProp);				
}

That’s it – the class will do everything else for you! There is just one restriction, if you have elements that get initialized through PopupManager or states you need to put the line into that class also. Let’s say you have a component called “MyPopup” which is being opened by PopupManager you need to set it up like the following:

<mx:TitleWindow 
	xmlns:mx="http://www.adobe.com/2006/mxml" 
	initialize="this.styleName='TitleWindowCheckoutCompletion'"
	>
        <mx:Script>
		<![CDATA[
			import de.aggro.utils.StylePropertyManager;
			
			override public function styleChanged(styleProp:String):void{
				super.styleChanged(styleProp);
				StylePropertyManager.notifyStyleChanged(this,styleProp);				
			}
		]]>
	</mx:Script>
...

Note the code that get’s executed in the initialize handler, you need to set the stylename at that point to get to some results and you need to call the StylePropertyManager::notifyStyleChanged method.

Here is the code of StylePropertyManager:

package de.aggro.utils
{
	import mx.containers.*;
	import mx.controls.*;
	import mx.controls.sliderClasses.*;
	import mx.core.UIComponent;
	
	public class StylePropertyManager
	{
		
		private static const properties : Array = [
		"width", "height", "percentWidth", "percentHeight", 
		"x", "y", "visible", "verticalScrollPolicy", "horizontalScrollPolicy", 
		"columnWidth", "rowHeight", "layout", "direction"
		];
		
		private static const topLevelClasses: Array = [
			Slider, Button, ColorPicker, ComboBox, List, CheckBox, ButtonBar
		]
		
		public function StylePropertyManager()
		{
		}
		
		public static function notifyStyleChanged(scope:UIComponent, styleProp:String):void{
			if(!styleProp || styleProp == "styleName"){
				//if runtime css swap or direct change of stylename
				updateProperties(scope);
				updateChildProperties(scope);
			}
		}
			
		private static function updateChildProperties(obj:UIComponent):void{
			var numc:int = obj.numChildren;
			
			for ( var i : int = 0; i < numc; i++ ){
				var c:Object = obj.getChildAt(i);
				
				if(c is UIComponent){
					
					var child:UIComponent = c as UIComponent;
					updateProperties (child);
					
					var isTopLevel:Boolean = false;
					
					for each(var tclass:Class in topLevelClasses){
						if(c is tclass){
							isTopLevel = true;
							break;
						}
					}
					
					if(!isTopLevel)	updateChildProperties(child);
				}
				
			}
		}

		private static function updateProperties(obj:UIComponent):void{
			for each (var item:String in properties){
				var sdec:Object = obj.styleDeclaration;
				var prop:Object = obj.getStyle(item);
				if(prop != null){
					try{
						obj[item] = prop;
						obj.invalidateDisplayList();
						obj.invalidateProperties();
						obj.invalidateSize();
						
					}catch(e:Error){}					
				}
			}
		}

	}
}

Let me explain some things, first of all the properties array – it holds all the properties that you want to be reflected from the css to your component and it’s children. After that there is another array called topLevelClasses – it holds all the classes that have children which should not get the parents styles. I added the topLevelClasses array because I was setting the width of a slider and the manager reflected the width to it’s thumb, that looked kind of broken. Depending on your application needs you might need to adjust one or both of these arrays.

Ok now, you can set properties through css now – happy styling 🙂

P.S.: As usual this source code is free to use on a “do whatever you want with it” license, take it for commercial, private or whatever purpose and drop me a comment if you like it