blog

XML skin (update) at Jozef Chúťka's blog

October 5, 2009

No comments

recursive runtime skinning xml

XML skin (update)

Simple runtime skinning using XMLSkin. Currently suported inheritance, values, styles, texts, statics, assets.

Passing values and styles via inheritance:

<?xml version="1.0" encoding="utf-8"?>
<data style.backgroundColor="#000000">
    <button1 style.backgroundColor="#CC00CC" width="500" label="Hallo">
        <parent height="200" style.backgroundColor="#FF0000"/>
    </button1>
</data>

Update (Mar 4, 2010): Passing longer text values:

<?xml version="1.0" encoding="utf-8"?>
<data>
    <myPannel>
        <title>some title</title>
    </myPannel>
    <myText>
        <htmlText><![CDATA]></htmlText>
    <myText>
</data>

Update (Mar 6, 2010): Passing static variables:

<?xml version="1.0" encoding="utf-8"?>
<data>
    <com.some.namespace.SomeClass
        staticVariable="hallo world" />
</data>

import com.somenamespace.ComeClass required in application

Update (Mar 8, 2010): Using embedded assets:

<?xml version="1.0" encoding="utf-8"?>
<data>
    <myImage asset.source="IMAGE_CLASS" />
    <myButton asset.style.skin="IMAGE_CLASS" />
</data>

requires different apply approach, see Usage with embedded assets

sk.yoz.data.XMLSkin

package sk.yoz.data
{
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
    import flash.utils.getDefinitionByName;
    
    /**
     * XMLSkin class makes runtime settings/skinning easy
     * get latest version and examples on:
     * http://blog.yoz.sk/2009/10/xml-skin/
     * updated: 2010-03-07
     */
    
    public class XMLSkin extends Object
    {
        protected var assets:Object;
        protected var assetsSource:String;
        protected var root:Object;
        protected var skin:XML;
        protected var autoApply:Boolean;
        protected var loader:Loader;
        
        public function XMLSkin(root:Object, skin:XML)
        {
            super();
            this.root = root;
            this.skin = skin;
        }
        
        public function apply():void
        {
            XMLSkin.apply(root, skin, loader);
        }
        
        public static function apply(root:Object, skin:XML, 
            assetsLoader:Loader = null):void
        {
            if(!skin)
                return;
            for each(var a:XML in skin.@*)
                doApply(root, a.name(), a.toString(), assetsLoader);
            var children:XMLList = skin.children();
                
            if(!children.length())
                return;
            var childName:String;
            for each(var child:XML in children)
            {
                childName = child.name();
                if(!childName)
                    continue;
                if(child.text().toString())
                    doApply(root, childName, child.text().toString(), 
                        assetsLoader);
                if(root.hasOwnProperty(childName))
                    XMLSkin.apply(root[childName], child, assetsLoader);
                else if(getDefinitionByName(childName))
                    XMLSkin.apply(getDefinitionByName(childName), child, 
                        assetsLoader);
            }
        }
        
        protected static function doApply(root:Object, attribute:String, 
            value:String, assetsLoader:Loader = null):void
        {
            var name:String = attributeToName(attribute);
            if(isStyle(attribute))
                root.setStyle(name, castStyle(root, value, attribute, 
                    assetsLoader));
            else if(root.hasOwnProperty(name))
                root[name] = castProperty(root, value, attribute, assetsLoader);
        }
        
        protected static function castProperty(object:Object, value:String, 
            attribute:String, assetsLoader:Loader = null):*
        {
            var name:String = attributeToName(attribute);
            switch(typeof object[name])
            {
                case "boolean":
                    return Boolean(value.toLowerCase() == "true");
                case "number":
                case "string":
                    return value;
                default:
                    return getClassFromLibrary(value, assetsLoader)
                        || getClassFromAssets(value, assetsLoader)
                        || value;
            }
        }
        
        protected static function castStyle(object:Object, value:String, 
            attribute:String, assetsLoader:Loader = null):*
        {
            var name:String = attributeToName(attribute);
            if(!assetsLoader && isAsset(attribute))
                return null;
            if(isAsset(attribute))
                return getClassFromLibrary(value, assetsLoader)
                    || getClassFromAssets(value, assetsLoader);
            return value;
        }
        
        protected static function getClassFromLibrary(value:String, 
            assetsLoader:Loader):Class
        {
            var assetClass:Class;
            try
            {
                assetClass = getDefinitionByName(value) as Class;
            }
            catch(error:Error){}
            return assetClass;
        }
        
        protected static function getClassFromAssets(value:String, 
            assetsLoader:Loader):Class
        {
            var assetClass:Class;
            try
            {
                var applicationDomain:ApplicationDomain = 
                    assetsLoader.contentLoaderInfo.applicationDomain;
                var assetsClass:Class = 
                    applicationDomain.getDefinition("Assets") as Class;
                assetClass = assetsClass[value];
            }
            catch(error:Error){}
            return assetClass;
        }
        
        public function loadAssets(assetsSource:String):Loader
        {
            this.assetsSource = assetsSource;
            loader = new Loader();
            
            var info:LoaderInfo = loader.contentLoaderInfo;
            info.addEventListener(Event.COMPLETE, assetsComplete, false, 
                int.MAX_VALUE);
            info.addEventListener(IOErrorEvent.IO_ERROR, assetsIOError, false, 
                int.MAX_VALUE);
            info.addEventListener(SecurityErrorEvent.SECURITY_ERROR, 
                assetsSecurityError, false, int.MAX_VALUE);
            
            var request:URLRequest = new URLRequest(assetsSource);
            var context:LoaderContext = new LoaderContext(false, 
                ApplicationDomain.currentDomain);
            loader.load(request, context);
            return loader;
        }
        
        protected function assetsComplete(event:Event):void
        {
            apply();
        }
        
        protected function assetsIOError(event:IOErrorEvent):void
        {
        }
        
        protected function assetsSecurityError(event:SecurityErrorEvent):void
        {
        }
        
        protected static function isStyle(attribute:String):Boolean
        {
            attribute = stripAsset(attribute);
            return attribute.toLowerCase().substr(0, 6) == "style.";
        }
        
        protected static function stripStyle(attribute:String):String
        {
            if(isStyle(attribute))
                return attribute.substr(6);
            return attribute;
        }
        
        protected static function isAsset(attribute:String):Boolean
        {
            return attribute.toLowerCase().substr(0, 6) == "asset.";
        }
        
        protected static function stripAsset(attribute:String):String
        {
            if(isAsset(attribute))
                return attribute.substr(6);
            return attribute;
        }
        
        public static function attributeToName(attribute:String):String
        {
            return stripStyle(stripAsset(attribute));
        }
    }
}

Simple usage

config.xml

<?xml version="1.0" encoding="utf-8"?>
<data style.backgroundColor="#000000">
    <button1 style.backgroundColor="#CC00CC" width="500" label="Hallo">
        <parent height="200" style.backgroundColor="#FF0000"/>
    </button1>
</data>

Application.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" applicationComplete="init()">
<mx:Script>
<![CDATA[
    import sk.yoz.data.XMLSkin;
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;
    
    private function init():void
    {
        service.send();
    }
    
    private function serviceResult(event:ResultEvent):void
    {
        XMLSkin.apply(this, XML(event.result));
    }
    
    private function serviceFault(event:FaultEvent):void
    {
    }
]]>
</mx:Script>
<mx:HTTPService id="service" url="config.xml" result="serviceResult(event)" fault="serviceFault(event)" resultFormat="e4x"/>
<mx:Canvas>
    <mx:Button id="button1" />
</mx:Canvas>
</mx:Application>

XMLSkin application

Usage with embedded assets:

config.xml

<?xml version="1.0" encoding="utf-8"?>
<data assetsSource="Assets.swf" 
    style.backgroundColor="#000000">
    <button1 style.backgroundColor="#CC00CC" width="500" label="Hallo">
        <parent height="200" style.backgroundColor="#FF0000"/>
    </button1>
    <img asset.source="IMAGE_CLASS" y="30"/>
</data>

Notice attribute prefix for <img> assets.source. Use assets prefix for elements to be gathered from asset library. You may use assets within style this way:

asset.style.backgroundImage="IMAGE_CLASS"

Assets.as contains our IMAGE_CLASS. Needs to be compiled into Assets.swf before used.

package
{
    import flash.display.Sprite;
    
    public class Assets extends Sprite
    {
        // embed any required elements this way
        [Embed(source="../assets/dart.png")]
        public static const IMAGE_CLASS:Class;

        public function Assets(){}
    }
}

Application

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" applicationComplete="init()">
<mx:Script>
<![CDATA[
    import sk.yoz.data.XMLSkin;
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;

    private function init():void
    {
        service.send();
    }

    private function serviceResult(event:ResultEvent):void
    {
        var skin:XML = XML(event.result);
        var xmlSkin:XMLSkin = new XMLSkin(this, skin);
        xmlSkin.loadAssets(skin.@assetsSource);
    }

    private function serviceFault(event:FaultEvent):void
    {
    }
]]>
</mx:Script>
<mx:HTTPService id="service" url="config2.xml" result="serviceResult(event)" fault="serviceFault(event)" resultFormat="e4x"/>
<mx:Canvas>
    <mx:Button id="button1" />
    <mx:Image id="img" />
</mx:Canvas>
</mx:Application>

XMLSkin automaticaly loads and applies assets

XMLSkin application

You may Leave a comment or Subscribe to Comments RSS or Trackback this entry.

Leave a comment

Please be polite and on topic. Your e-mail will never be published.