Pages

March 5, 2011

Using Flex 4 Skins & States to Disable a Component

Posted by Jeremy Mitchell
In a previous post entitled Temporarily Disable a Flex Component, I utilized a handful of classes to disable and enable a Flex component as needed. In short, the technique involved adding or removing a modal overlay to a container on demand. With the Flex 4 skinning architecture, the process of "disabling" a component is simplified.

I will start by creating an MXML component (Editor.mxml) that extends the Panel class.
<?xml version="1.0" encoding="utf-8"?>
<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"
   xmlns:s="library://ns.adobe.com/flex/spark"
   xmlns:mx="library://ns.adobe.com/flex/mx"
   xmlns:comps="comps.*"
   width="500"
   title="Editor">
 
 <mx:Form width="100%" height="100%"
    backgroundColor="#CCCCCC">
  
  <mx:FormHeading label="Field"/>
  
  <mx:FormItem label="Name:"
      required="true">
   <s:TextInput/>
  </mx:FormItem>
  
 </mx:Form>
 
</s:Panel>
Next, I will create an MXML skin for the Editor class by selecting New > MXML Skin, naming the skin "EditorSkin" and selecting "Editor" as the host component. I will leave checked the checkbox entitled "Create as copy of: spark.skins.spark.Panel" and choose "Remove ActionScript styling code". Now, I have a skin class that serves as a starting point for my component.

I need to map the skin class to the host component (Editor) so I will add the skinClass property and the appropriate value to the host component's root tag.
<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"
   xmlns:s="library://ns.adobe.com/flex/spark"
   xmlns:mx="library://ns.adobe.com/flex/mx"
   xmlns:comps="comps.*"
   width="500"
   title="Editor"
   skinClass="skins.EditorSkin">
Since I want to "disable" my component while it is in a specific state, I'll add a new state to my skin class. I'll name this new state "loading".
<s:states>
 <s:State name="normal" />
 <s:State name="disabled" />
 <s:State name="loading"/>"
 <s:State name="normalWithControlBar" stateGroups="withControls" />
 <s:State name="disabledWithControlBar" stateGroups="withControls" />
</s:states>
With my new "loading" state defined, I'll create a Group to hold a "modal overlay" that will be turned on when the loading state is active thus "disabling" the component. I'll place this new Group next to the contentGroup but inside of a new Group used to enforce absolute positioning. Notice that the "loading" Group is defined below the contentGroup's MXML declaration allowing it to lay on top of the component and serve as a "modal overlay".
<s:Group width="100%">
 
 <!--- @copy spark.components.SkinnableContainer#contentGroup -->
 <s:Group id="contentGroup" width="100%" height="100%" minWidth="0" minHeight="0">
 </s:Group>
 
 <s:Group id="loadingGroup"
    left="0"
    right="0"
    top="0"
    bottom="0"
    includeIn="loading">
  
  <s:Rect id="modal"
    left="0"
    right="0"
    top="0"
    bottom="0">
   
   <s:fill>
    <s:SolidColor color="#FFFFFF"
         alpha="0.5"/>
   </s:fill>
   
  </s:Rect>
  
  <mx:ProgressBar id="loader" 
      x="{(width / 2) - (loader.width / 2)}"
      y="{(height / 2) - (loader.height / 2)}"
      
 </s:Group>
   
</s:Group>
Notice the "includeIn" property? This limits the inclusion of the "loading Group" to the loading state ONLY.

Next, I will define the skin states that the host component (Editor) will utilize to disable or enable itself. These skin states need to be defined as Metadata in the Editor class.
<fx:Metadata>
 [SkinState("loading")]
 [SkinState("normal")]
</fx:Metadata>
I will also create a couple of public methods designed to toggle a property that "invalidates" the skin state and turns the loading state on and off. Here is my new Editor class:
<?xml version="1.0" encoding="utf-8"?>
<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"
   xmlns:s="library://ns.adobe.com/flex/spark"
   xmlns:mx="library://ns.adobe.com/flex/mx"
   xmlns:comps="comps.*"
   width="500"
   title="Editor"
   skinClass="skins.EditorSkin">
 
 <fx:Metadata>
  [SkinState("loading")]
  [SkinState("normal")]
 </fx:Metadata>
 
 <fx:Script>
  <![CDATA[   
   public static const VIEW_MODE_LOADING:String="loading";
   public static const VIEW_MODE_NORMAL:String="normal";
   
   private var _viewMode:String = VIEW_MODE_NORMAL;
   
   protected function set viewMode(value:String):void
   {
    _viewMode = value;
    invalidateSkinState(); // this forces a call to getCurrentSkinState
   }
   
   public function turnOnLoadingIndicator():void
   {
    viewMode = VIEW_MODE_LOADING;
   }
   
   public function turnOffLoadingIndicator():void
   {
    viewMode = VIEW_MODE_NORMAL;
   }
   
   override protected function getCurrentSkinState():String
   {
    // this forces a change in skinstate
    return _viewMode;
   }
  ]]>
 </fx:Script>
 
 <mx:Form width="100%" height="100%"
    backgroundColor="#CCCCCC">
  
  <mx:FormHeading label="Field"/>
  
  <mx:FormItem label="Name:"
      required="true">
   <s:TextInput/>
  </mx:FormItem>
  
 </mx:Form>
 
</s:Panel>
Finally, I'll add a couple of buttons to the main Application file required to toggle the state of the Editor component.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
      xmlns:s="library://ns.adobe.com/flex/spark"
      xmlns:mx="library://ns.adobe.com/flex/mx"
      xmlns:comps="comps.*">
 
 <s:layout>
  <s:VerticalLayout/>
 </s:layout>
  
 <comps:Editor id="editor"/>
 
 <s:HGroup width="{editor.width}"
     horizontalAlign="center">
  
  <s:Button id="disableBtn" 
      label="Click to disable component"
      click="editor.turnOnLoadingIndicator()"/>
  
  <s:Button id="enableBtn" 
      label="Click to enable component"
      click="editor.turnOffLoadingIndicator()"/>
  
 </s:HGroup>
 
</s:Application>
The result is a Flex component (Editor.mxml) with a skin (EditorSkin.mxml) that can be disabled or enabled on demand. The following application represents the final component with a few extra features, a fancier loading image and some best practices in place (view source is enabled).