Wednesday 18 February 2009

True Runtime CSS ( without compiling it to swf file)

Here is a simple way . yet not perfect, to load a CSS file at run time.
The only flaw is berried within the StyleSheet function "parseCSS".
It converts the entire file to lowercase letters; Thus, without hard coding it, it's impossible to apply styles to all UIComponenets since the convention is using capitals.
The StyleManager would look for "button" even if you wrote in the CSS file "Button".
Same problem goes with styleNames. But here, you may consider to only use lowerCase names.

There is a bit of an ugly solution for this problem - you can modify the first letter into capital.
That would only solve components with only a noun (Button, Canvas, Label vs LinkButton, HBox etc.) .
If you can presume what the CSS file contains - you might want to use switch/case and for every UIComponent - update its string representation to the correct way the StyleManager recognizes it.
The perfect solution would be to write your own CSS parser.... G'luck :)



private function loadRunTimeCSS(cssURL:String):void{
var loader:URLLoader;
var req:URLRequest = new URLRequest(cssURL);
loader = new URLLoader();
loader.addEventListener(Event.COMPLETE, onCSSFileLoaded);
loader.addEventListener(IOErrorEvent.IO_ERROR, onLoadPartitionError);
loader.load(req);
}

private function onCSSFileLoaded(event:Event):void{
var sheet:StyleSheet = new StyleSheet();
try{
// Important Notice - the parseCSS function takes the entire CSS file
// and converts it all to lowerCase letters.
// Therefor - if you have a styleName for Button
// the style whould be recognized as button - thus- will be ingnored for all Button classes
// Also - is you created a style name - e.g. - .myStyle - then
// when applying to it - you must lowercase it
// e.g. - myButton.styleName = "mystyle"
//
sheet.parseCSS(event.currentTarget.data);

for each (var style:String in sheet.styleNames){
var generalStyle:CSSStyleDeclaration = new CSSStyleDeclaration(style + "_CSS_Style");
var styleObj:Object = sheet.getStyle(style);
for (var styleProp:String in styleObj){
var styleValue:String = styleObj[styleProp];
var regExp:RegExp = new RegExp("'\"","gi");
// we must take off any remainings of "" / ' in the string since the styleManager
// does not recognize it nor does the parseCSS function takes it off....
styleValue = styleValue.replace(regExp,'');

generalStyle.setStyle(styleProp, styleValue);
}

StyleManager.setStyleDeclaration(style, generalStyle, true);

}

_logger.info("**Done loading private labeling!");
}catch(e:Error){
_logger.info("Count not process runtime lableing: " + e.message);
}finally{

}

}

private function onLoadPartitionError(event:IOErrorEvent):void
{
_logger.info("Count not load runtime lableing.css: " + event.text);
}

1 comment:

  1. This mechanism doesn't seem to support all styles you could normally use in a "compile" time style style sheet. For example a type selector like this will not work!

    CheckBox {
    fillAlphas: 1, 1, 0, 0;
    fillColors: #ffffff, #ededed, #ffffff, #eeeeee;
    disabledIconColor: #ff0000;
    }

    First reason is the the type selector "CheckBox" gets converted to "checkbox" as u have correctly pointed out. To overcome this problem, lets say u modify the type selector to the following

    CheckBox {
    selectorName: "CheckBox";
    fillAlphas: 1, 1, 0, 0;
    fillColors: #ffffff, #ededed, #ffffff, #eeeeee;
    disabledIconColor: #ff0000;
    }

    Note the additional name/value pair - selector name - that has the real name of the type selector. The parsing, does not mess around with name/value pairs inside a selector. So now u have access to the Type selectors real name.

    So you can use logic like this
    var stSheet:StyleSheet = new StyleSheet();
    stSheet.parseCSS(cssData);

    var stylename:String = stSheet.styleNames[i];
    var curStyle:Object = stSheet.getStyle(stSheet.styleNames[i]);
    //style selectors before
    var selectors:Array = StyleManager.selectors;
    var selectorcount:int = selectors.length;

    var styleValue:String;
    // use real name if present in the selector description
    if(curStyle["selectorName"] != null) {
    styleValue = curStyle["selectorName"];\
    // strip "'s
    stylename = styleValue.replace("\"", "");;
    stylename = stylename.replace("\"", "");;
    }

    var currSelector:CSSStyleDeclaration = StyleManager.getStyleDeclaration(stylename);
    if(currSelector==null) {
    currSelector= new CSSStyleDeclaration();
    }

    Now while populating the currSelector with individual styles, you need to skip the "pseudo" tag labelled "selectorName" or else u will see error at runtime.

    With the above Type selector and the code changes, things will see allright until you run the application!

    You will get an error that says
    TypeError: Error #1034: Type Coercion failed: cannot convert "#ffffff, #ededed, #ffffff, #eeeeee" to Array.

    So now that tells you that you have to stuff all styles with comma separated values into an Array!

    So you need to add the logic ot create a style value object as follows
    if(stylevalue.indexOf(",") != -1) {
    var styleObj:Array = stylevalue.split(",");
    currSelector.setStyle(prop, styleObj);
    }

    Now if you try all this, the applications runs with a real runtime style sheet with no stack traces, but the style does not work right!

    With hundreds of styles, you literally have to debug each style to figure out the problem...

    Simple ones like these work right...
    .header {
    backgroundColor : #00F000;
    paddingLeft: 12;
    borderStyle: none;
    }
    Note the lower case class selector name...

    ReplyDelete