while(thoughts){ "i am writing"}

Just about anything… and everything :)

Flex 4: Spark Component Life Cycle

with one comment

In our previous post, we learned about Skins used in Spark Components. Skins are an integral part of Spark Components OR we can say that skins are actually the building blocks for skinnable spark components.

Today, we will discuss about the life cycle of a skinnable spark component. During this discussion, we will create an actual component as part of this learning. I am not going through the screenshots, instead I will be sharing the code snippets for various steps. You can find the complete source code at the end of this post.

Lets call out skinnable component as ‘CustomComponent’. It will have a textinput to write a message, an append button and a text area with disabled text. User will be able to type the text in textinput control and on click of the append button, this text will be appended to the existing text in textarea.

First we need to create a skin for our CustomComponent. Lets call it CustomComponentSkin.

Steps to create a skin:

  • In Flashbuilder, right click on your mouse. Select New > MXML Skin.
  • In the opened dialog, give ‘skin’ as package name. Name will be ‘CustomComponentSkin’ and Host Component will be ‘CustomComponent’.
  • Click Ok and Flashbuilder will create a Skin with default settings.

In Spark, a component is made up of a skin and a component class. Skin class actually hold the parts(in our case, textinput, button and textarea) and component class manipulates these parts and their properties for various functionalities.

So we need to create those parts(textinput, button and textarea) in CustomComponentSkin as below:


<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
 xmlns:s="library://ns.adobe.com/flex/spark"
 xmlns:mx="library://ns.adobe.com/flex/mx">

 <!-- host component -->
 <fx:Metadata>
 [HostComponent("ui.CustomComponent")]
 </fx:Metadata>

 <!-- Define the normal view state. -->
 <s:states>
 <s:State name="normal"/>
 </s:states>

 <!--- Defines the appearance of the TextInput subcomponent. -->
 <s:TextInput id="textInput"
 width="150"
 x="0"
 minHeight="25"/>

 <!--- Defines the appearance of the Button subcomponent. -->
 <s:Button id="appendButton"
 width="150"
 x="175"
 minHeight="25"/>

 <!--- Defines the appearance of the TextArea subcomponent. -->
 <s:TextArea id="textArea"
 width="400"
 x="0"
 y="50"/>
</s:Skin>

As you have noticed, we also provided the metadata tag HostComponent pointing to our CustomComponent.

Now we have our components skin ready, lets move to create the actual component. Before we create our component, I would prefer to step through the various calls/functions used for it quickly (from adobe docs).

  1. Create the skin class for the component. You typically create the skin class in MXML. For more information on skins, see Spark Skinning.
  2. Create the component’s ActionScript class file.
    1. For a skinnable component, extend one of the base classes, such as SkinnableComponent, or a subclass of class SkinnableComponent. For a nonskinnable component, extend UIComponent or a Spark class that does not have SkinnableComponent in it class hierarchy.
    2. Implement the constructor.
    3. Implement the UIComponent.createChildren() method. You rarely have to implement this method for Spark components.
    4. Implement the UIComponent.commitProperties() method.
    5. Implement the UIComponent.measure() method. You rarely have to implement this method for Spark components.
    6. Implement the UIComponent.updateDisplayList() method. You rarely have to implement this method for Spark components.
    7. For a skinnable component, implement the SkinnableComponent.partAdded() and SkinnableComponent.partRemoved() methods.
    8. For a skinnable component, implement the SkinnableComponent.getCurrentSkinState() method.
    9. Add properties, methods, styles, events, and metadata.
  3. Deploy the component as an ActionScript file or as a SWC file.

You do not have to override all component methods to define a new component. You only override the methods required to implement the functionality of your component. If you create a subclass of an existing component, such as Button, you implement only the methods necessary for you to add any new functionality to the component.

Lets step through the component creation with the implementation of necessary functions:

  • In Flashbuilder, right click and select New > ActionScript Class.
  • Type in ‘ui’ as the package name, ‘CustomComponent’ in the name field and select ‘SkinnableComponent’ as the super class.
  • Click Finish. Flashbuilder will generate the wrapper class structure with a default constructor.
  • Set the skinclass (that we just created above) in the constructor using setStyle function as shown below:


package ui
{
 import spark.components.supportClasses.SkinnableComponent;

 public class CustomComponent extends SkinnableComponent
 {
 public function CustomComponent()
 {
 super();

 // Set the skinClass style to the name of the skin class.
 setStyle("skinClass", CustomComponentSkin);
 }
 }
}

  • In next step, define the skin parts for the TextInput, Button and TextArea subcomponents and implement the commitProperties() method to handle any changes to the CustomComponent’s properties as shown below:


[SkinPart(required="true")]
 public var textInput:TextInput;

 [SkinPart(required="true")]
 public var appendButton:Button;

 [SkinPart(required="true")]
 public var textArea:TextArea;

override protected function commitProperties():void {
 super.commitProperties();

 if (bTextChanged) {
 bTextChanged = false;
 textInput.text = _text;
 }
 }

  • In next step, implement the partAdded() method to initialize  the TextInput, Button and TextArea subcomponents. Also implement the partRemoved() method to remove the eventlisteners added by the partAdded().


override protected function partAdded(partName:String, instance:Object):void {
 super.partAdded(partName, instance);
 if (instance == textInput) {
 textInput.text= _text;
 textInput.addEventListener("change", textInput_changeHandler);
 }

 if (instance == appendButton) {
 appendButton.label = "Append Text";
 appendButton.addEventListener("click", appendButton_clickHandler);
 }

 if (instance == textArea) {
 textArea.editable = false;
 }
 }

override protected function partRemoved(partName:String, instance:Object):void {
 super.partRemoved(partName, instance);

 if (instance == textInput) {
 textInput.removeEventListener("change", textInput_changeHandler);
 }

 if (instance == appendButton) {
 appendButton.removeEventListener("click", appendButton_clickHandler);
 }

 if (instance == textArea) {
 //textArea.removeEventListener("change", textArea_changeHandler);
 }
 }

  • In the next step, add properties, methods and metadata.


private var _text:String = "Enter your text";
 private var bTextChanged:Boolean = false;

 // Create a getter/setter pair for the text property.
 [Bindable]
 public function set text(t:String):void {
 trace("In set text");
 _text = t;
 bTextChanged = true;
 invalidateProperties();
 }

 public function get text():String {
 trace("In get text");
 return textInput.text;
 }

 // Dispatch a change event when the CustomComponent.text
 // property changes.
 private function textInput_changeHandler(eventObj:Event):void {
 dispatchEvent(new Event("change"));
 }

 // Handle the Button click event to append text
 // to the TextArea subcomponent.
 private function appendButton_clickHandler(eventObj:Event):void {
 if(textInput.text.length < 5)
 {
 textArea.text += "Oh.. sorry buddy. You were less then 5, the minimum required length..hehehe..\n" + "[Text length this turn: " + textInput.text.length + "]\n\n";
 }
 else
 {
 textArea.text += textInput.text + "\n" + "[Text length this turn: " + textInput.text.length + "]\n\n";
 }
 }

And we are done.

Here is how our CustomComponent.as will look like finally. I have added the steps and trace statements for various function calls to better understand the control flow. It has some additional functions just to trace the control flow in a component life cycle.


package ui
{
 import flash.events.Event;

 import mx.events.FlexEvent;

 import skin.CustomComponentSkin;

 import spark.components.Button;
 import spark.components.TextArea;
 import spark.components.TextInput;
 import spark.components.supportClasses.SkinnableComponent;

 // CustomComponent dispatches a change event when the text of the
 // TextInput subcomponent changes.
 [Event(name="change", type="flash.events.Event")]

 /** a) Extend SkinnableComponent. */
 public class CustomComponent extends SkinnableComponent
 {
 /** b) Implement the constructor. */
 public function CustomComponent()
 {
 //TODO: implement function
 trace("In constructor");
 super();

 // Set the skinClass style to the name of the skin class.
 setStyle("skinClass", CustomComponentSkin);

 //Adding eventlisteners
 addEventListener(FlexEvent.PREINITIALIZE, preInitializeHandler);
 addEventListener(FlexEvent.INITIALIZE, initializeHandler);
 addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler);
 addEventListener(FlexEvent.UPDATE_COMPLETE, updateCompleteHandler);
 }

 override protected function createChildren():void {
 trace("In createChildren");
 super.createChildren();
 }

 /** c) Define the skin parts for the TextInput, Button
 *     and TextArea subcomponents. **/
 [SkinPart(required="true")]
 public var textInput:TextInput;

 [SkinPart(required="true")]
 public var appendButton:Button;

 [SkinPart(required="true")]
 public var textArea:TextArea;

 /** d) Implement the commitProperties() method to handle the
 *     change to the CustomComponent.text property.
 *     Changes to the CustomComponent.text property are copied to
 *     the TextArea subcomponent. */
 override protected function commitProperties():void {
 trace("In commitProperties");
 super.commitProperties();

 if (bTextChanged) {
 bTextChanged = false;
 textInput.text = _text;
 }
 }

 override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
 trace("In updateDisplayList");
 super.updateDisplayList(unscaledWidth, unscaledHeight);
 }

 override protected function measure():void {
 trace("In measure");
 super.measure();
 }

 override protected function attachSkin():void {
 trace("In attachSkin");
 super.attachSkin();
 }

 override protected function detachSkin():void {
 trace("In detachSkin");
 super.detachSkin();
 }

 /** e) Implement the partAdded() method to
 *     initialize the TextInput, Button and TextArea subcomponents. */
 override protected function partAdded(partName:String, instance:Object):void {
 trace("In partAdded");
 super.partAdded(partName, instance);
 if (instance == textInput) {
 textInput.text= _text;
 textInput.addEventListener("change", textInput_changeHandler);
 }

 if (instance == appendButton) {
 appendButton.label = "Append Text";
 appendButton.addEventListener("click", appendButton_clickHandler);
 }

 if (instance == textArea) {
 textArea.editable = false;
 }
 }

 /** f) Implement the partRemoved() method to remove the
 *     event listeners added by partAdded(). */
 override protected function partRemoved(partName:String, instance:Object):void {
 trace("In partRemoved");
 super.partRemoved(partName, instance);

 if (instance == textInput) {
 textInput.removeEventListener("change", textInput_changeHandler);
 }

 if (instance == appendButton) {
 appendButton.removeEventListener("click", appendButton_clickHandler);
 }

 if (instance == textArea) {
 //textArea.removeEventListener("change", textArea_changeHandler);
 }
 }

 /** g) Add methods, properties, and metadata.
 *     The general pattern for properties is to specify a
 *     private holder variable. */

 // Implement the CustomComponent.text property.
 private var _text:String = "Enter your text";
 private var bTextChanged:Boolean = false;

 // Create a getter/setter pair for the text property.
 [Bindable]
 public function set text(t:String):void {
 trace("In set text");
 _text = t;
 bTextChanged = true;
 invalidateProperties();
 }

 public function get text():String {
 trace("In get text");
 return textInput.text;
 }

 // Dispatch a change event when the CustomComponent.text
 // property changes.
 private function textInput_changeHandler(eventObj:Event):void {
 dispatchEvent(new Event("change"));
 }

 // Handle the Button click event to append text
 // to the TextArea subcomponent.
 private function appendButton_clickHandler(eventObj:Event):void {
 if(textInput.text.length < 5)
 {
 textArea.text += "Oh.. sorry buddy. You were less then 5, the minimum required length..hehehe..\n" + "[Text length this turn: " + textInput.text.length + "]\n\n";
 }
 else
 {
 textArea.text += textInput.text + "\n" + "[Text length this turn: " + textInput.text.length + "]\n\n";
 }
 }

 private function preInitializeHandler(eventObj:Event):void {
 trace("In preInitialize");
 }
 private function initializeHandler(eventObj:Event):void {
 trace("In initialize");
 }
 private function creationCompleteHandler(eventObj:Event):void {
 trace("In creationcomplete");
 }
 private function updateCompleteHandler(eventObj:Event):void {
 trace("In updatecomplete");
 }

 }
}

You can download the complete project source code from here.

Enjoy flexing :)

Advertisement

Written by MD

December 8, 2010 at 9:15 PM

One Response

Subscribe to comments with RSS.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.