Friday, April 20, 2007

Flex Modules, Compile and Run Time CSS

I've recently become involved in building a very module based Flex app, where the question of css and css inheritance came up, particularly with respect to how Flex handles run time loading of style sheets when they are loaded within not only the shell application but also the therein loaded modules.

Essentially, there are 3 rules that prevail:

  1. The most recent style to be loaded always wins.
  2. Run time overrides compile time styles
  3. StyleManager is a global Manager
  4. Style inheritance is granular to the attribute level
  5. Compile time css will only compile styles found in Application
Let me explain further, and to do so I'm just going to use our trusty <mx:Button>

The most recent style loaded always wins
Say you load a runtime style sheet with the following:

Button {
fillColors: #ff0000, #ff0000, #ffffff, #eeeeee; /*red */
letterSpacing:3;
}

Then in a (rather large) module you load another runtime style sheet with this:
Button {
fillColors: #006600, #009900, #ffffff, #eeeeee; /*green*/
letterSpacing:1;
}

You'll notice that the first color style is applied right away, to any buttons in the shell application. However, when the second module finishes loading, all Buttons will change from Green to Red, and spacing will be reduced to 1. To make it even more interesting, you could add an additional module of roughly the same size as the first and have it load its own style sheet. Then the end result is pretty much random, whichever module takes the longest to load wins, the shell run time style never to be heard from again.


Run time overrides compile time styles
In your shell application you add the following:
<style>
Button{
fillColors: #9900ff, #9900ff, #ffffff, #eeeeee;
letterSpacing:1;
}
</style>

Once your app loads the run time css, this will be overwritten. Now if you have a huge run time css swf with massive fonts or images embedded in, there's a chance you might even see your original color first and then have it overwritten later. This can cause for some funky effects in your app, in which case you might want to attach an eventListener to StyleEvent.COMPLETE, and hide your interface until that event has triggered using a ViewState or the like.

StyleManager is a global Manager
It that does not distinguish between <Applications> and <Modules>. Say you create a custom style class called .myDogAteMyHomeworkButton for Buttons in your module. You then place a corresponding style entry in your compile time or run time styles for this module. Now someone else happens, just happens to use the same exact style class in their module. Or the shell application developers happens to do so. Well again, the last one loaded wins. Your custom style class is never safe. But then again neither are their's. The best thing you can do is be very very unique in your style class naming conventions, and hope for the best.

Style inheritance is granular to the attribute level
Getting back to our original example, you'll notice the style attribute letterSpacing was defined explicitly in both style sheets. Because inheritance is on the attribute level, if you do not specify an attribute in say your run time style sheet for your application, then that attribute can be set in compile time style tags and it will override the default values. What's the lesson to be learned here? If you want to make sure that your runtime css has the final word, then get detailed and explicitly list every style attribute and its value, even if the default will suffice.

Compile time css will only compile styles found in Application
Ever seen this warning "The type selector 'HDividedBox' was not processed, because the type was not used in the application."? Until the dawn of Modules, this was a message I generally ignored. Made sense to me, if I didn't have an HDividedBox anywhere in my application, why include the style? But what would happen to modules that perhaps do use the HDividedBox control but rely solely on the shell application styles for their look and feel? The application doesn't know at runtime that one or more of its modules will need this style defined, so therefore it just ignores it. End result, your HDividedBox in your module will not be styled properly. The solution? Use run time stylesheets. (and that's how this all began!)

9 comments:

Harry said...

Which makes obvious the fact that, since a module is made to be loaded within a shell app (cannot run stand alone), it should be style-less.
Good to know all these...

Anonymous said...

Hello Vic,

I've been playing with runtime styles a bit and Im able to load and apply a precompiled swf style once the application has started, so you can notice visually how the application changes its style.

Now I have several precompiled styles ( one for each client ) and I want the application to select one ( based on some parameters, url, whatever ) and apply that style without applying the default style first. Basically, I want to avoid the visual effect of seeing my application changing its style and just apply the selected style from the beginning...possible?

Thank you so much.

Is there any way to load and apply a compiled SWF style

Victor Rubba said...

I usually fake it out... I use a viewstack in my application, and the first child in my viewstack is blank. So even when the app has finished loading, the user would be presented with a blank unstyled page. When I load a run time style sheet I set the auto apply on load parameter to false. Then I add an event listener myEvent.addEventListener(StyleEvent.COMPLETE, doUpdate); (see Help). When the stylesheet finishes loading, this event fires. At this point I apply the stylesheet and change the selectedIndex of the viewstack to the login view or whatever.

Anonymous said...

Yes, that could be a good trick heheh

I was just wondering if I could just load the style as part of some preloader or something.

Thank you Vic

Victor Rubba said...

You might be able to at least load the runtime swf's and get them into the local browser cache, while flex is preloading. Then the actual wait time on that blank canvas might be considerable quicker. I've never tried that though.

Anonymous said...

Hi Vic,

Im reading on the prerelease forums there's another way to achieve this by using a custom preloader + adding some event listeners; I will post this alternative here as soon as I get it working :)

Anonymous said...

Very good article explaining dynamic stylesheet implementation on all flex components

http://askmeflash.com///article_m.php?p=article&id=6

bots said...

Thanks a lot for sharing! You just saved me from a nervous breakdown!

Anonymous said...

Amiable post and this mail helped me alot in my college assignement. Gratefulness you on your information.