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

Leave a comment

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