sean hess

  • Twitter
  • LinkedIn
  • i.TV
    • 5
      6 Nov 2009

      Use css to style a custom spark skin in Flex 4

      • Edit
      • Delete
      • Tags
      • Autopost

      I’ve finally had a chance to dive in to Flex 4. One trick I learned is incredibly powerful. CSS is a powerful way to specify settings to something from the outside, but it quickly becomes a pain if you need to push pixels around. I’m going to show you how you can create a skin that reads its properties from a stylesheet. This allows css to do what it does best: set simple properties, and lets you do the pixel pushing in the Skin.

      Let’s start with some Flex 3 code we’d like to duplicate. This will create a “modal” overlay that covers the app with a translucent screen. You might use this for a LightBox.

      <!-- MyOverlay.mxml -->
      <mx:Canvas 
          width="100%" height="100%"
          styleName="overlay"
          >
      
          <!-- Put a panel or something here -->
      
      </mx:Canvas>
      
      /** styles.css **/
      .overlay {
          background-color: #000000;
          background-alpha: 0.7;
      }

      Our First Skin

      We want to create this in Flex 4. The (rough) equivalent to canvas is a Group, but it isn’t skinnable, so we can use a SkinnableContainer instead.

      <!-- MyOverlay.mxml -->
      <s:SkinnableContainer
          width="100%" height="100%"
          skinName="overlay"
          >
      
          <!-- Stuff -->
      
      </s:SkinnableContainer>

      SkinnableContainer doesn’t have a way to set backgroundColor or backgroundAlpha! The css will not work.

      Let’s create the same functionality in a skin instead.

      <!-- skins/OverlaySkin.mxml -->
      <s:SparkSkin ... >
      
          <!-- The highest-level base class that can use this-->
          <fx:Metadata>
              [HostComponent("spark.components.SkinnableContainer")]
          </fx:Metadata>
          
          <!-- specify all the states the host component uses -->
          <s:states>
              <s:State name="normal"/>
              <s:State name="disabled"/>
          </s:states>
          
          <!-- Here's our overlay! -->
          <s:Rect left="0" right="0" top="0" bottom="0">
              <s:fill>
                  <s:SolidColor color="#000000" alpha="0.7"/>
              </s:fill>
          </s:Rect>
          
          <!-- Put the panel, or whatever is in the component here --> 
          <s:Group id="contentGroup" verticalCenter="0" horizontalCenter="0">
              <s:layout><s:BasicLayout/></s:layout>
          </s:Group>
          
      </s:SparkSkin>

      That’s not too bad. We have a skin, and now we can apply it to our component by setting theskinClass property. For example:

      <!-- MyOverlay.mxml -->
      <s:SkinnableContainer
          width="100%" height="100%"
          skinClass="skins.OverlaySkin"
          >
      
          <!-- content -->
      
      </s:SkinnableContainer>

      Or we can set the skinClass in the style.

      <!-- MyOverlay.mxml -->
      <s:SkinnableContainer
          width="100%" height="100%"
          styleName="overlay" 
          >
      
          <!-- content -->
      
      </s:SkinnableContainer>
      
      /** styles.css **/
      .overlay {
          skinClass: ClassReference("skins.OverlaySkin");
      }

      Using CSS on the Skin

      Now we have a styleName we can apply to any SkinnableContainer and it will make it look like our black, translucent overlay. This works great, so we start using it, but pretty soon we realize that we need to be able to add a background to another container, but we don’t want to create a new skin just to tweak the color and alpha. Here is where it gets fun. We change the Rect tag in our skin to look like this:

      <s:Rect left="0" right="0" top="0" bottom="0">
              <s:fill>
                      <s:SolidColor color="{getStyle('backgroundColor')}" alpha="{getStyle('backgroundAlpha')}"/>
              </s:fill>
      </s:Rect>

      Now it will get it’s color and alpha from the style. We can change our overlay style to look like this

      /** styles.css **/
      .overlay {
          background-color: #000000;
          background-alpha: 0.7;
          skinClass: ClassReference("skins.OverlaySkin");
      }

      That looks a lot more like the Flex 3 version! We can then add more styles for other kinds of containers that need backgrounds, and our skin is now much more useful than before.

      The Final Version

      <!-- skins/BackgroundSkin.mxml -->
      <?xml version="1.0" encoding="utf-8"?>
      <s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" 
                   xmlns:s="library://ns.adobe.com/flex/spark" 
                   xmlns:mx="library://ns.adobe.com/flex/halo">
          
          <fx:Metadata>
              [HostComponent("spark.components.SkinnableContainer")]
          </fx:Metadata>
          
          
          <s:states>
              <s:State name="normal"/>
              <s:State name="disabled"/>
          </s:states>
          
          <s:Rect left="0" right="0" top="0" bottom="0">
              <s:fill>
                  <s:SolidColor color="{getStyle('backgroundColor')}" alpha="{getStyle('backgroundAlpha')}"/>
              </s:fill>
          </s:Rect>
          
          <!-- Tells the skin where to put the children you have specified -->
          <s:Group id="contentGroup" verticalCenter="0" horizontalCenter="0">
              <s:layout><s:BasicLayout/></s:layout>
          </s:Group>
          
      </s:SparkSkin>
      
      /** styles.css **/
      .overlay {
          background-color: #000000;
          background-alpha: 0.7;
          skinClass: ClassReference("skins.OverlaySkin");
      }
      
      .error {
          background-color: #FF0000;
          background-alpha: 1.0;
      }
      
      .success {
          background-color: #00FF00;
          background-alpha: 1.0;
      }

      Using this technique will allow you to play to the strengths of both CSS and Spark Skins.


      Luan
      Great example but do when I do this in the Eclipse designer, my page is blank. Any idea? If I use the skinClass, all is well.

      Rick
      Thanks for the help, this stopped my frustrations

      Sean Hess
      Luan - you caught me. I never use the designer because, well, too many things make it break :)

      Try this instead: color="{getStyle('backgroundColor') || 0x000000}" which should use black if the style is null. 

      I haven't tried it, but it might work.


      AJ
      Hi Sean, Great example, helped me understanding the basic concepts. 

      One question though, can I define the layout type in host component instead of the skin file? I want to use the background rect in many containers but the layout type differs... any suggestions?


      AJ
      I think, I can define the layout in the host component as well, so that might just work.

      Can we define the style names in the component to class selector, ie:

      DataGrid 
      {
      backgroundAlpha: 0.2;
      headerStyleName: "GridHeader";
      }
      .GridHeader
      {
      color: #594733;
      }
      So for SkinnableContainer, something like:

      SkinnableContainer
      {
      styleName:rectStyle;
      }
      .rectStyle
      {
      background-color:Black;
      }


      Sean Hess
      Any layout you specify in the host component overrides the the one you specify for the contentGroup.

      Tim Oxley
      Hi Sean, Regarding your above default styling code:

      color="{getStyle('backgroundColor') || 0x000000}"


      I tried this with backgroundAlpha

      color="{getStyle('backgroundAlpha') || 0.5}"

      But I was wishing to set it to 0 in my style:

      <customContainer backgroundAlpha="0" />

      but 0 == false, so the OR fails and the backgroundAlpha is set to 0.5

      Workaround is to use a ternary operator in the skin, testing explicitly for undefined instead of anything that's equal to false (0, undefined, null, false, etc):

      <s:Group alpha="{getStyle('backgroundAlpha') != undefined ? getStyle('backgroundAlpha') : 0.2}" />

      :)


      Moxie Zhang
      the combination of color="{getStyle('backgroundColor') and CSS "skinClass: ClassReference..." works very well. 
      However, there is a bug (maybe). By doing so, if you have a transition for a state that to change a property of hostComponent, it won't work. Such as: Resize heightTo="5" target="{hostComponent}". Only way works is to assign skinClass directly in MXML while skinClass ref is still in CSS.

      Anybody knows a work around?

      • views
      • Tweet
      • Tweet
    • 0
      11 Aug 2009

      Client-Side Flex Logging with Shared Object

      • Edit
      • Delete
      • Tags
      • Autopost

      Having your app keep a detailed log can be indispensable for figuring out tricky user-reported bugs. This is easy on the server-side, where you can use any logging framework or just write to a file. Flash includes the trace function, which is useful while developing, but nearly impossible to get from an end-user. Flex, however, has a good logging api that lets you put log statements anywhere you want. I’ve always wanted the interface to be as easy as trace, so I write a couple of scripts to make it easier.

       

      Flog (Flex Logging) on Github
      log.as
      SharedObjectTarget.as

       

      There are two files in there now. The first is log.as. I didn’t realize before that it was possible to define your own package-level functions. Some might disagree with me, but I think this syntax is a lot nicer than the default. It looks just like trace. All it does is create an easy way to access the flex logging api.

      import net.seanhess.flog.log;
      
      function stuff():void
      {
          log("Executing stuff");
          // do stuff 
          log("Finished!");
      }

      You can replace all your trace statements with these. It’s easy enough to add a trace target to the logging api, so all log messages are traced. See the Flex Logging API for more details, but here’s how you would do it.

      import mx.logging.Log;
      import mx.logging.targets.TraceTarget;
      
      // Only once in your app
      function addLoggingTargets():void
      {
          Log.addTarget(new TraceTarget());
      }

      The second little tool is a new logging target. I wanted a way to log to a local file in flex, so the support guys could get an end-user to email it. The only way to do this without hacks is to write to a shared object. It turns out if you write an array of strings to a shared object, it’s easily human-readable. You attach SharedObjectTarget like any other target and away you go.

      import mx.logging.Log;
      import net.seanhess.flog.SharedObjectTarget;
      
      // Only once in your app
      function addLoggingTargets():void
      {
          Log.addTarget(new TraceTarget());
          Log.addTarget(new SharedObjectTarget("MyCustomAppLogging"));        
      }

      The file is located in the application data folder, which is in~/Library/Preferences/Macromedia/Flash Player/#SharedObjects on the mac. (Google it for windows). The target does some fancy stuff like automatically saving if it hasn’t received a log message for one second. It also automatically rotates messages once the size reaches about 50k (The default allowed size for shared objects is 100k).

      There’s more that can be done here. I might add a debug and an error function. You could easily write an app to send the contents to a server too.

      For alternatives and more information look at the approaches of Tom Barker, Virgil Cristea, and John Wilker.


      Tomasz
      Hi!

      Your library seems to be just what I've been looking for. The only question remaining is the license under which it has been published. Could you please add some licensing information together with the sources?

      • views
      • Tweet
      • Tweet
    • 0
      30 Jul 2009

      Page Stack - Navigate by Page Name

      • Edit
      • Delete
      • Tags
      • Autopost

      Building a navigation system in Flex is harder than it should be. Trying to figure out how to decouple your navigation model from the view is unintuitive. ViewStack is the obvious choice for it, but it leaves you with only two options, neither of which is good.

      1. Use selectedIndex - You can throw your pages/containers in a ViewStack and store the currently selected page index in a model. This works great, except that your model is coupled to the order of the pages in the ViewStack. You can store the indices in constants, but if someone adds a new page in there, it will throw off all the indices.

      2. Use selectedChild - You can mitigate the index problem by storing the selectedChildproperty on your model, but then your model has a reference to a view. Yuck.

      The solution - Page Stack

      This simple component allows you to store the name of the selected page. You can set the page. You can then store a string on your model that you bind to the selectedPage property of PageStack. If you set the name on each child of the PageStack, they’ll match up.

      MyView.mxml

      <components:PageStack selectedPage="{navigation.selectedPage}">
          <mx:Canvas name="{Navigation.FIRST}"/>
          <mx:Canvas name="{Navigation.SECOND}"/>
          <mx:Canvas name="{Navigation.THIRD}"/>                
      </components:PageStack>
      
      <mx:Button label="Go to Page 3" click="navigation.selectedPage = Navigation.THIRD"/>

      Navigation.as

      public class Navigation
      {
          public static const FIRST:String = "first";
          public static const SECOND:String = "second";
          public static const THIRD:String = "third";
              
          [Bindable]            
          public var selectedPage:String = FIRST;
      }

      And here’s the source!

      package net.seanhess.components
      {
          import flash.display.DisplayObject;
      
          import mx.containers.ViewStack;
          import mx.core.Container;
      
          public class PageStack extends ViewStack
          {
              protected var pages:Object = {};
              protected var newChildren:Boolean = false;
      
              override public function addChildAt(child:DisplayObject, index:int) : DisplayObject
              {
                  newChildren = true;
                  invalidateProperties();
                  return super.addChildAt(child, index);
              }
      
              override protected function commitProperties() : void
              {
                  super.commitProperties();
      
                  if (newChildren)
                  {
                      newChildren = false;
                      pages = {};
      
                      for each (var child:DisplayObject in getChildren())
                      {
                          var name:String = child.name;
                          pages[name] = child;
                      }
                  }
              }
      
              public function set selectedPage(value:String):void
              {
                  var child:Container = pages[value] as Container;
      
                  if (!child)
                      throw new Error("Could not find page: " + value);
      
                  selectedChild = child;
              }
      
              public function get selectedPage():String
              {
                  return selectedChild.name;            
              }
          }
      }

       

       


      Jonathan
      Sean, Nice post. I am eager to try this out and maybe solve problems with the Glue Library Example. Just to let you know your missing a " after Canvas..{Navigation.Number}. After I added the code and set "public var navigation:Navigation;" on the view I receive an error "Cannot access a property or method of a null object reference." I must be missing something pretty obvious. Thanks!


      Sean Hess
      Thanks for the catch Jonathan. I've corrected it. If I were using Glue, I would make Navigation a manager, put it in the glue map, and inject only the selectedPage to MyView. So, MyView would have public var selectedPage:String, and the page stack would just bind to that.


      Jex Chan
      Hi Sean, i just found Glue from github, it's very interested framework. is any possible you can give one example for this Page Stack sample, You said to make Navigation a manager and combine with Glue. thanks a lot

       

      • views
      • Tweet
      • Tweet
    • Search

    • Sites I Like

      • Lessons Learned
      • Both Sides of the Table 2x Entrepreneur turned VC
    • Tags

      • tutorial
      • framework
      • php
      • ruby
      • component
      • sinatra
      • agile
      • code
      • git
      • iphone
      • sequel
      • spark
    • Archive

      • 2012 (1)
        • February (1)
      • 2011 (8)
        • December (1)
        • October (2)
        • June (2)
        • May (2)
        • January (1)
      • 2010 (4)
        • December (2)
        • May (2)
      • 2009 (7)
        • November (1)
        • August (2)
        • July (4)
    • Obox Design
  • sean hess

    Sean is cofounder and CTO of i.TV.

    62454 Views
  • Get Updates

    Follow this Space »
    You're following this Space (Edit)
    You're a contributor here (Edit)
    This is your Space (Edit)
    Follow by email »
    Get the latest updates in your email box automatically.
    Loading...
    Subscribe via RSS
    TwitterLinkedIn