Monday, February 13, 2012

Understand Spark Button Source Code

The best way to understand how to write good custom component is to go through some of them. Recently I was just going thorough the Spark Button source code and found it quite interesting to share.


This is not the full and exhaustive treatment or line-by-line explanation of source code. Rather I will try to highlight the main coding snippets and some very good practices we can follow up. If you like this post please let me know so that I can bring more such posts.


So at the top level Spark Button extends ButtonBase class (which contains much of the magic ;) ) and implements IButton interface.

public class Button extends ButtonBase implements IButton
{
public function Button()
{
        super();
      }
…..
}


Spark Button class extends ButtonBase and implements IButton interface. The class ButtonBase is the base class for all Spark button components and has all the logic. The Button (current class) and ToggleButtonBase classes are subclasses of ButtonBase. The CheckBox and RadioButton are subclasses of ToggleButtonBase.



public class ButtonBase extends SkinnableComponent implements IFocusManagerComponent
{
public function ButtonBase()
      {
        super();

        // DisplayObjectContainer properties.
        // Setting mouseChildren to false ensures that mouse events
        // are dispatched from the Button itself,
        // not from its skins, icons, or TextField.
        // One reason for doing this is that if you press the mouse button
        // while over the TextField and release the mouse button while over
        // a skin or icon, we want the player to dispatch a "click" event.
        // Another is that if mouseChildren were true and someone uses
        // Sprites rather than Shapes for the skins or icons,
        // then we we wouldn't get a click because the current skin or icon
        // changes between the mouseDown and the mouseUp.
        // (This doesn't happen even when mouseChildren is true if the skins
        // and icons are Shapes, because Shapes never dispatch mouse events;
        // they are dispatched from the Button in this case.)
        mouseChildren = false;
       
        // add event listeners to the button
        addHandlers();
      }

protected function addHandlers():void
      {
        addEventListener(MouseEvent.ROLL_OVER, mouseEventHandler);
        addEventListener(MouseEvent.ROLL_OUT, mouseEventHandler);
        addEventListener(MouseEvent.MOUSE_DOWN, mouseEventHandler);
        addEventListener(MouseEvent.MOUSE_UP, mouseEventHandler);
        addEventListener(MouseEvent.CLICK, mouseEventHandler);
      }
protected function mouseEventHandler(event:Event):void
      {
        var mouseEvent:MouseEvent = event as MouseEvent;
        switch (event.type)
        {
            case MouseEvent.ROLL_OVER:
            {
                // if the user rolls over while holding the mouse button
                if (mouseEvent.buttonDown && !mouseCaptured)
                    return;
                hovered = true;
                break;
            }

            case MouseEvent.ROLL_OUT:
            {
                hovered = false;
                break;
            }
           
            case MouseEvent.MOUSE_DOWN:
            {
// When the button is down we need to listen for mouse //events outside the button so that we update the state //appropriately on mouse up.  Whenever mouseCaptured //changes to false,it will take care to remove those //handlers.

                addSystemMouseHandlers();
                mouseCaptured = true;
                break;
            }

            case MouseEvent.MOUSE_UP:
            {
                // Call buttonReleased() if we mouse up on the button and if
                // the mouse was captured before.
                if (event.target == this)
                {
                    hovered = true;
                   
                    if (mouseCaptured)
                    {
                        buttonReleased();
                        mouseCaptured = false;
                    }
                }
                break;
            }

            // Prevent the propagation of click from a disabled Button.
            // This is conceptually a higher-level event and
            // developers will expect their click handlers not to fire
            // if the Button is disabled.
            case MouseEvent.CLICK:
            {
                if (!enabled)
                    event.stopImmediatePropagation();
                else
                    clickHandler(MouseEvent(event));
                return;
            }
        }
        if (mouseEvent)
            mouseEvent.updateAfterEvent();
      }
…..
}

Here the method addHandlers() in the constructor adds various mouse related events and the corresponding listener mouseEventHandler. The listener method mouseEventHandler checks out event type and then takes proper action. In simple terms for roll-over it sets the hovered variable to true (which is further used by getCurrentSkinState to return proper state), roll-out makes hovered to false, for mouse down some of the system-mouse-handlers are added and when mouse is out we call up respective methods. If button is disabled then we want to prevent mouse-click event and so is done by event.StopImmediatePropagation() method.


Here we have mouse event we want the update to happen at this particular render cycle itself and dont want to wait for next render event, that is done by this method call (updateAfterEvent).





IButton interface is more of a marker interface and it has only one property emphasized and one function callLater.
The IFocusManagerComponent interface defines the interface that focusable components must implement in order to receive focus from the FocusManager. The base implementations of this interface are in the UIComponent class, but UIComponent does not implement the full IFocusManagerComponent interface since some UIComponents are not intended to receive focus. Therefore, to make a UIComponent-derived component be a valid focusable component, we simply add "implements IFocusManagerComponent" to the class definition.
The SkinnableComponent class defines the base class for skinnable components. The skins used by a SkinnableComponent class are typically child classes of the Skin class.



When the label for the button is set, it actually sets the value of content:

public function set label(value:String):void
{
        // label property is just a proxy to the content.
        // The content setter will dispatch the event.
        content = value;
}

public function get label():String         
{
        return (content != null) ? content.toString() : "";
}


private var _content:*;
   
[Bindable("contentChange")]
public function get content():Object
{
        return _content;
}

public function set content(value:Object):void
{
        _content = value;

        // Push to the optional labelDisplay skin part
        if (labelDisplay)
            labelDisplay.text = label;
        dispatchEvent(new Event("contentChange"));
}
 The content property lets us pass an arbitrary object to be used in a custom skin of the button. When a skin defines the optional part labelDisplay then a string representation of content will be pushed down to that part's text property. The default skin uses this mechanism to render the content as the button label.
[SkinPart(required="false")]

/**
  *  A skin part that defines the label of the button.
  * 
  *  @langversion 3.0
  *  @playerversion Flash 10
  *  @playerversion AIR 1.5
  *  @productversion Flex 4
  */
public var labelDisplay:TextBase;



As labelDisplay is a skin part, we also have partAdded and partRemoved function.
override protected function partAdded(partName:String, instance:Object):void
{
    super.partAdded(partName, instance);
       
    if (instance == labelDisplay)
    {
      labelDisplay.addEventListener("isTruncatedChanged",
                                    labelDisplay_isTruncatedChangedHandler);
           
      // Push down to the part only if the label was explicitly set
            if (_content !== undefined)
                labelDisplay.text = label;
   }
}
override protected function partRemoved(partName:String,instance:Object):void
{
   super.partRemoved(partName, instance);
       
   if (instance == labelDisplay)
   {
     labelDisplay.removeEventListener("isTruncatedChanged",
                                     labelDisplay_isTruncatedChangedHandler);
   }
}

This was a brief introduction and I hope you like it. Some further good references are:

Useful Links


Simple Custom Component (Sortable List)






5 comments:

FlexProg said...

Very good post. I was looking for something like that since long.

Akhil said...

Good to know that you found what you were looking for :) Even I was not able to find any post that can help me learn the source code, that why I wrote a simple post like this :)

sids said...

Thanx boss , for posting it really nice ......i have been wandering around abt how to create your own components ? Can u suggest some tips of how can we really master this ..

Akhil Mittal said...

your welcome sids. What exactly you want to know? Well there are many things you take care of:
1. Never start from the scratch until you really need it.
2. Start with some simple customization it will boost your confidence.
3. Check out simple custom components example.
4. If you extend any Spark components (most of them extend SkinnableComponent), so get idea of base component and skin contract.

mightystudents.com said...

Even I was not able to discover any publish that can help me understand the resource value, that why I had written a easy publish like this i have been roaming around abt how to make your own elements ? Can u recommend some guidelines of how can we really expert this ..