Flex Tag Cloud, XML, and External Stylesheets Loaded at Runtime

by syale 7. January 2012 07:51
I was doing a little work today and believe it or not, there aren't many posts online related to creating Flex Tag Clouds. I found a couple with the most helpful being on <a href="http://www.glodde.com/blog/default.aspx?id=1&amp;amp;t=Basic-Flex-TagCloud-Component" title="Brian Glodde's blog">Brian Glodde's Blog</a>. Brian's blog demonstrates a great way to handle your tags when you have a unsorted xml list. When you have a weighted tag, the process of creating the tag becomes quite a bit easier. I thought I finished my little widget when I was asked to add a way to enumerate styles from a style sheet, where the server side code was unaware of the styles to be applied. In other words, I was asked to create a way to handle passing a value(such as 1, 2, 3, etc) in the xml, that would be able to be handled in my Flex app at runtime.The first thing to note here is that in order to maintain ease in redeployment, it is helpful to have your stylesheet load at runtime, instead of being compiled into your application. In fact, I recommend this practice for all Flex projects as it doesn't require that you recompile you application in order to modify your styles. This is similar to externalizing your localization files. Flex 3 does a great job of holding your files in their own .swf's that can be loaded (and changed) while your application is running. We'll save the localization externals for another post, but I'll cover the process of externalizing your stylesheet here.First, create a .css file. Second, right click the stylesheet in FlexBuilder, and choose Compile CSS to SWF. This will automatically set your stylesheet to compile each time you build your project. If you want to disable this, right click again and uncheck Compile CSS to SWF.Ok, so now you've got your stylesheet taken care of. We'll look at what you do with it in a few. For brevity, here's what I did in the stylesheet:<code><span style="color: blue; font-size: 10px">.Tag1<br />{<br /> color: #000000;<br /> textRollOverColor: #CCCCCC;<br /> fontFamily: Verdana;<br />}<br />.Tag2<br />{<br /> color: #e4e703;<br /> textRollOverColor: #f1f289;<br /> fontFamily: Arial;  <br />}<br />.Tag3<br />{<br /> color: #0066FF;<br /> textRollOverColor: #87b4f9; fontFamily: "Times New Roman"; <br />}</span> </code>Let's have a look at a an xml file that will help us for the purposes of this project.<code><span style="color: blue; font-size: 10px"> <tag name="ShawnYale" loc="http://www.ShawnYale.com" weight="14" font="2" color="1" /><br /> <tag name="Google" loc="http://www.google.com" weight="35" font="3" color="4"/><br /> <tag name="Yahoo" loc="http://www.yahoo.com" weight="40" font="3" color="5"/><br /> <tag name="Flickr" loc="http://www.flickr.com" weight="10" font="4" color="1"/><br /> <tag name="YouTube" loc="http://www.youtube.com" weight="12" font="1" color="4"/><br /></code>As you can see, the xml is REALLY simple. As a matter of personal preference, I like to use e4x and access the attributes, rather than creating child nodes. However, there are times where child nodes (or sub-children rather) are a better option.Ok, two pieces down. Now we've got to think about loading the xml at runtime, and we'll handle that and the stylesheet in our application. For the purposes of the tag cloud, I found that the FlowBox component within the <a href="http://code.google.com/p/flexlib/" title="Flexlib project">Flexlib</a> project works great!<code><?xml version="1.0" encoding="utf-8"?><br /><mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"<br /> creationComplete="initApp()" preinitialize="preInit()" xmlns:containers="flexlib.containers.*"<br />  height="100%" width="100%" horizontalScrollPolicy="off" verticalScrollPolicy="off"<br />  backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#FFFFFF, #FFFFFF]"><br /> <mx:Script><br />  <![CDATA[<br />   import mx.controls.Alert;<br />   import mx.collections.Sort;<br />   import mx.controls.LinkButton;<br />   import mx.collections.SortField;<br />   import mx.rpc.events.FaultEvent;<br />   import mx.rpc.events.ResultEvent;<br />   import mx.collections.ArrayCollection;<br />   import flash.external.ExternalInterface;<br />      <br />   private var tag:LinkButton;<br />   <br />   private function preInit():void<br />   {<br /></code><code><span style="color: Green">    // This is all it takes to load your stylesheet at runtime.<br />    //In order to help with performance, we perform the stylesheet load on preInit instead of on creationComplete.<br /></span>    StyleManager.loadStyleDeclarations("styles.swf");<br />   }   <br />   private function initApp():void<br />   {<br /><span style="color: Green">    // This allows us to resize the flex container from the javascript outside our Flex application<br />    // @sHeight: controls the height of the flex app<br />    // @sWidth: controls the width of the flex app<br /></span>    if(this.parameters.sHeight != null &amp;amp;&amp;amp; this.parameters.sHeight!='')<br />    {<br />     this.height = this.parameters.sHeight;<br />    }<br />    if(this.parameters.sWidth != null &amp;amp;&amp;amp; this.parameters.sWidth!='')<br />    {<br />     this.width = this.parameters.sWidth;<br />    }<br /><span style="color: Green">    // Set the location of the external xml tag list document<br />    // @sXMLLoc: string location of the xml document<br /></span>    if(this.parameters.sXMLLoc != null &amp;amp;&amp;amp; this.parameters.sXMLLoc != '')<br />    {<br />     service.url = this.parameters.sXMLLoc;<br />     service.send();<br />    }<br />    else<br />    {<br />     // load the test xml file<br />     service.send();<br />    }<br />   }<br />   <br />   private function httpResultHandler(event:ResultEvent):void<br />   {<br />    buildTagCloud(new XML(event.result));<br />   }<br />   <br />   private function httpFaultHandler(event:FaultEvent):void<br />   {<br />    Alert.show("Error occurred: "+event.fault.message, "XML Transmission Error");<br />   }<br />   <br />   private function buildTagCloud(xml:XML):void<br />   {<br />    var xml:XMLList = xml.children();<br />    var array:ArrayCollection = new ArrayCollection();<br />    var sort:Sort = new Sort();<br />    sort.fields=[new SortField("name", true)];<br />    <br />    for each(var item:Object in xml)<br />    {<br />     var obj:Object = new Object();<br />     obj.name = item..@name;<br />     obj.weight = item..@weight;<br />     obj.loc = item..@loc;<br />     obj.font = item..@font;<br />     obj.color = item..@color;<br />     array.addItem(obj);<br />    }    <br />    array.sort = sort;<br />    array.refresh();    <br />    for(var i:Number = 0; i<array.length;i++)<br />    {     <br />     tagContainer.addChild(createLinkButton(array[i].name,array[i].loc, array[i].weight, array[i].color, array[i].font));<br />    }   <br />   }<br />   <br />   private function createLinkButton(label:String,url:String, size:Number, color:String, font:String):LinkButton<br />   {      <br />    tag = new LinkButton();<br />    tag.label = label; <br />    <br /><span style="color: Green">    // Since we are using a weighted list, we are just going to use the weight to handle the font size.<br /></span>    tag.setStyle("fontSize", size);        <br /><span style="color: Green">    // Here we get a little interesting.<br />    // Since we don't know what style is coming from the server<br />    // we want to be able to pick and choose which items from our stylesheet<br />    // we apply to each of our linkButton.<br />    // We get this functionality by using the StyleManager<br />    // and then accessing the individual style within that tag that we want to use.<br />    // Now I don't know WHY you would do this in a real life scenario<br />    // but this is HOW you do it if you ever need it.<br /></span>    tag.setStyle("color", StyleManager.getStyleDeclaration(".Tag"+color).getStyle("color"));<br />    tag.setStyle("textRollOverColor", StyleManager.getStyleDeclaration(".Tag"+color).getStyle("textRollOverColor"));<br />    tag.setStyle("fontFamily", StyleManager.getStyleDeclaration(".Tag"+font).getStyle("fontFamily"));<br />    tag.toolTip = url;<br />    tag.addEventListener(MouseEvent.CLICK, tagClickHandler);    <br />    tag.addEventListener(MouseEvent.MOUSE_OVER, tagOverHandler);<br />    tag.addEventListener(MouseEvent.MOUSE_OUT, tagOutHandler);<br />    return tag; <br />   }<br />   <br />   public function tagClickHandler(event:MouseEvent):void<br />   {<br /><span style="color: Green">    // Logic to fire the location click event in the external api    <br /></span>    ExternalInterface.call("LoadExtLink", event.currentTarget.toolTip.toString());<br />   }<br />   private function tagOverHandler(event:MouseEvent):void<br />   {<br /><span style="color: green">    // Add any tweens or effects here<br /></span>   }<br />   private function tagOutHandler(event:MouseEvent):void<br />   {<br /><span style="color: green">    // Add any tweens or effects here<br /></span>   }<br />  ]]><br /> </mx:Script><br /> <mx:HTTPService id="service" url="tagTest.xml" resultFormat="e4x" result="httpResultHandler(event)" fault="httpFaultHandler(event)"/><br /> <containers:FlowBox id="tagContainer" width="100%" height="100%" horizontalScrollPolicy="off" verticalScrollPolicy="off" verticalAlign="bottom" horizontalAlign="center" horizontalGap="0" verticalGap="0" borderStyle="solid" cornerRadius="10" borderColor="#ADADAD" borderThickness="3" themeColor="#B6F998"/><br /></mx:Application></code>That's it. You now have an externalizable, reusable widget that will automatically resize itself based upon the application size and rearrange the tag cloud items to fit the available screen size.

Tags: , , , ,

Adobe Flex | CSS | XML