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

Advertisements
    • dabouncer
    • December 24th, 2009

    Hi,

    This code looks cool and i would love to try it out but there seams to be parts missing towards the end.

    Theres no ‘updateProperties(scope);’ and ‘updateChildProperties(scope);’ seems messed up some how.

    Any chance you can correct this.

    Cheers daboucner

    • Yes you are right, it was missing a function – damn wordpress eat my code. Now it’s fixed and should work.

        • dabouncer
        • January 7th, 2010

        Hi there kingschnulli,

        Cheers for fixing the code for testing.

        What can i say about this but Dude, this code is awesome.

        Its so easy to intergrate this into my current code and have all the properties open to me via css is ace.

        Well Done Man.

      • I am glad you like it, again one more annoyance of the flex framework eliminated 🙂

    • Sjonni
    • January 6th, 2010

    Hey

    This code is awesome! i really like the ability to apply this to all components not just one. There is one thing i’m wondering about why isn’t styleChange working with states and popupManager. Is there no way to get around this?

    Sjonni

    • The styleChange will not be send to states and popupmanager stuff because those are not children of the main application when the styleChange event occurs, you could get around it if you reset the styleName property after the children show up – but I haven’t yet figured out how to do that (I tried it, didn’t work out for me so i just go for overrides). The easiest way is to just override the classes you are using in states and as popups and add the override line to them as well.

      I made a whole bunch of classes which are looking like this:

      package de.aggro.controls
      {
      	import de.aggro.utils.StylePropertyManager;
      	
      	import mx.controls.Button;
      
      	public class StyleButton extends Button
      	{
      		public function StyleButton()
      		{
      			super();
      		}
      		
      		override public function styleChanged(styleProp:String):void{
      			super.styleChanged(styleProp);
      			StylePropertyManager.notifyStyleChanged(this,styleProp);				
      		}
      		
      	}
      }
      

      Those can be used in states and as popups without any problems. If you do not want to do that you could also just hack the flex framework – but that will not work if you are using the rsl framework.

    • Chris
    • January 19th, 2010

    Does this actually work?

    “…put the following into your main application or any container which should have its properties, and child properties, in the css model”

    Does this mean include *only once* within the application; or, add to *every* subclass of UIComponent?

    • If your application does not use states or popups it will be enough to add that to your main application as i wrote. Just go and try it.

    • Chinmay
    • April 29th, 2010

    Awesome man.. it does work!
    I am making a custom video player by extending the spark video player, and ran into the CSS issue. Your solution just works!!! 🙂
    I will surely run into multiple states, lets see how things go. Thanks for the good stuff! Keep it up!

  1. This is quality work – thanks 😉

    • Karol
    • October 19th, 2010

    Where exactly I should override styleChanged function if I use states?

    • In the component that you are using inside of the state, if you are using standard flex components you will have to subclass them

    • Ron
    • March 24th, 2011

    I can not get this to work in Flex 4.

    I’ve added the override part into Main.mxml, and added a s:SkinnableContainer styleName=”teststyle” in the Main.mxml as well. Then I’ve added a styles.css file and in there I have:

    .teststyle {
    width: 100;
    height: 100;
    }

    and gave the skinnablecontainer a backgroundcolor just so it’s visible.

    It doesn’t seem to pick up the width and height from the css file.

    I’ve also added a trace into the override that just traces the styleProp parameter and all I see in the console is “null”. Once. that’s it.

    Am I doing something wrong or does this not work with Flex 4?

    A full working example with source-code would be much appreciated.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: