Loading...

Thursday, January 28, 2010

Flex Basics - Local Shared Object

From Wikipedia, a browser cookie is a "small piece of text stored on a user's computer by a web browser. A cookie consists of one or more name-value pairs containing bits of information".

A web application uses cookies for session management, personalization and tracking. Most browsers restrict the size of a cookie to 4 kb.

In a Flex application, storing data on a user's computer for later use is accomplished using Local Shared Objects rather than cookies. A Local Shared Object can store up to 100 kb of data without asking permission of the user. Unlike cookies that are capable of storing only text values, Local Shared Objects can store many data types including Number, String, Boolean, XML, Date, Array & Object. Instances of a custom class can also be stored in a Local Shared Object when the custom class is registered using the [RemoteClass] metadata tag.

Create / retrieve a Local Shared Object

The process for creating a new Local Shared Object or retrieving an existing Local Shared Object is identical. In both cases, the static method getLocal() is used.

public static function getLocal(name:String, localPath:String = null, secure:Boolean = false):SharedObject

First, perform a call to the static method getLocal() of the SharedObject class, and then save the reference in a variable.

var myNewLocalSharedObject:SharedObject = SharedObject.getLocal("myNewLocalSharedObject");

Flash Player will look in a local directory on the user's computer for a Local Shared Object named myNewLocalSharedObject. More specifically, it will look in a directory unique to the SWF's domain for a file named myNewLocalSharedObject.sol. If myNewLocalSharedObject.sol is found, a reference to the existing Local Shared Object is returned. Otherwise, myNewLocalSharedObject.sol is created and a reference is returned to the newly created Local Shared Object.

To determine whether a new or existing Local Shared Object was returned, query the size property.

var myNewLocalSharedObject:SharedObject = SharedObject.getLocal("myNewLocalSharedObject");

if (myNewLocalSharedObject.size > 0)
{
    trace("Existing Local Shared Object");
}
else
{
    trace("New Local Shared Object");
}
The getLocal() method has three parameters. They should be used as follows:
  • The name parameter is required to assign a name to the Local Shared Object.

  • The localPath parameter is optional and should be used if you anticipate multiple SWF files from the SAME domain needing access to one Local Shared Object, or if the SWF file responsible for creating the Local Shared Object may be moved to another location within the SAME domain.

  • The secure parameter is optional and is used to create a secure Local Shared Object. Once created, any subsequent calls to the secure Local Shared Object must be made by a SWF delivered over HTTPS.

Access / update a Local Shared Object

A Local Shared Object may have many attributes. For example, customer data may include name, age, address and order IDs. Each of these attributes is represented by a property of the Local Shared Object's data property. These values can be accessed, updated or deleted.

// retrieve a reference to the existing Local Shared Object (customerData.sol)
var so:SharedObject = SharedObject.getLocal("customerData");

// accessing the values of the Local Shared Object
var name:String = so.data.name;
var age:int = so.data.age;

// updating the values of the Local Shared Object
so.data.name= "John Smith";
so.data.age = 23;
so.data.orderIds = [234562444425432];

// a custom class must be registered and marked serializable using the [RemoteClass] metadata tag
var address:Address = new Address();
address.street = "125 Foo Lane";
so.data.address = address;

// deleting values of the Local Shared Object
delete so.data.age;
delete so.data.orderIds;

Persist a Local Shared Object

Use the flush() method to immediately write a Local Shared Object to the user's computer.

public function flush(minDiskSpace:int = 0):String

A call to flush() returns one of two possible values, SharedObjectFlushStatus.PENDING or SharedObjectFlushStatus.FLUSHED. If more space is needed on the user's computer to persist the Local Shared Object, SharedObjectFlushStatus.PENDING is returned and a dialog box is presented to the user to obtain permission to allocate more space.



When the user selects "Allow" or "Deny" a NetStatusEvent.NET_STATUS event is dispatched targeting the Local Shared Object. Register an event listener to handle this event.

so.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
If the Local Shared Object is successfully written to the user's computer, SharedObjectFlushStatus.FLUSHED is returned and the user is not notified.

The flush() method has one parameter. It should be used as follows:
  • The minDiskSpace parameter is optional and is used to allocate additional space (over 100 kb) on the user's computer for the Local Shared Object. If used, a dialog box is presented to the user asking for permission to allocate additional space.

private function writeSharedObject():void
{
    var so:SharedObject = SharedObject.getLocal("mySO");
    so.data.name = "Jeremy Mitchell";
    try
    {
        // wrap in a try to handle a scenario where local storage has been disallowed
        so.flush(500000);
    }
    catch (e:Error)
    {
        trace("Local storage has been disabled for this domain");
    }
}
Note: The flush() method is not required to persist a Local Shared Object. When the application SWF closes, an attempt is made to persist all Local Shared Objects on the user's computer. However, if adequate space is not allocated for the Local Shared Object, the attempt will fail silently. Use the flush() method to ensure success and recover from any possible failure.

Delete a Local Shared Object

To purge the data of a Local Shared Object and delete it from the user's computer call the clear() method.

public function clear():void
var so:SharedObject = SharedObject.getLocal("loginInfo");
so.clear();

Labels: ,

Monday, December 7, 2009

Flex Basics - Creating a Popup Window with PopUpManager

Popup windows serve as a handy tool for data collection or display. Popup windows in Flex are frequently implemented as a TitleWindow or a subclass of TitleWindow. TitleWindow is a popular choice as it inherits the capabilities of a Panel (title bar, control bar, border & content area) while adding a nifty close button.



With built-in modal support, popup windows can demand the user's attention or force interaction when necessary. This is possible because windows below a modal popup window cannot receive mouse events until a modal popup window is dismissed.

The PopUpManager class is used to manage popup windows in Flex. You can create, center and remove popup windows by using the static methods of the PopUpManager class.

addPopUp()

public static function addPopUp(window:IFlexDisplayObject, parent:DisplayObject, modal:Boolean = false, childList:String = null):void
createPopUp()

public static function createPopUp(parent:DisplayObject, className:Class, modal:Boolean = false, childList:String = null):IFlexDisplayObject
centerPopUp()

public static function centerPopUp(popUp:IFlexDisplayObject):void
removePopUp()

public static function removePopUp(popUp:IFlexDisplayObject):void

Creating a popup window

Flex provides two distinct, yet very similar, methods for creating a popup window and placing it on a layer above all other visible windows – addPopUp() and createPopUp(). The difference between each method is subtle and determining which to use is mostly a matter of preference.

Note: Adobe documentation states that "using the addPopUp() method may be preferable to using the createPopUp() method if you have to pop up a simple dialog box that is never used elsewhere."
Looking closely at each method signature (addPopUp & createPopUp), the difference dictates how to use the method rather than when to use it. (or so it seems)

Method characteristicaddPopUp()createPopUp()
Requires a popup window instance as an argumentYesNo
Returns a value that can be stored in a variableNoYes

Here are some guidelines I tend to follow:

If you want to create a quick and dirty simple popup window that you don't plan to reuse, use addPopUp().
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="openTitleWindow(event)" >

    <mx:Script>
        <![CDATA[
            import mx.events.CloseEvent;
            import mx.controls.Label;
            import mx.events.FlexEvent;
            import mx.containers.TitleWindow;
            import mx.managers.PopUpManager;

            // method to open the TitleWindow on creationComplete
            private function openTitleWindow(evt:FlexEvent):void {
                // create and configure the TitleWindow
                var tw:TitleWindow = new TitleWindow();
                tw.title = "Title Goes Here";
                tw.showCloseButton = true;
                tw.addEventListener(Event.CLOSE, closeTitleWindow);
                // create and configure a Label
                var label:Label = new Label();
                label.text = "This is a very simple popup window";
                // add the Label to the TitleWindow
                tw.addChild(label);
                // open the TitleWindow as a modal popup window
                PopUpManager.addPopUp(tw, thistrue);
            }
            
            // method to close the TitleWindow targeted by a close event
            private function closeTitleWindow(evt:CloseEvent):void {
                PopUpManager.removePopUp(TitleWindow(evt.target));
            }
        ]]>
    </mx:Script>
    
</mx:Application>
If reuse of a popup window is a priority, use createPopUp() and store the returned object in a variable for later use.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="openPopUpWindow(event)" >

    <mx:Script>
        <![CDATA[
            import comps.MyPopUpWindow;
            import mx.events.FlexEvent;
            import mx.managers.PopUpManager;
            
            // declare a variable for the reusable custom PopUp Window
            private var popup:MyPopUpWindow;

            // method to open the PopUp Window on creationComplete
            private function openPopUpWindow(evt:FlexEvent):void {
                // open the PopUp Window as a modal popup window
                // and store it in a variable for later use
                popup = PopUpManager.createPopUp(this,MyPopUpWindow,trueas MyPopUpWindow;
            }
        ]]>
    </mx:Script>
    
</mx:Application>
<?xml version="1.0" encoding="utf-8"?>
<!-- custom popup window component - comps/MyPopUpWindow.mxml -->
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" 
        layout="vertical" 
        title="My Custom PopUp Window"
        showCloseButton="true">
         
        <mx:Form>
        
            <mx:FormHeading label="User Login"/>
            
            <mx:FormItem label="Username">
                <mx:TextInput id="un"/>
            </mx:FormItem>
            
            <mx:FormItem label="Password">
                <mx:TextInput id="pd"/>
            </mx:FormItem>
            
            <mx:ControlBar>
                <mx:Button id="save" label="Login"/>
                <mx:Button id="cancel" label="Cancel"/>
            </mx:ControlBar>
             
        </mx:Form>
        
</mx:TitleWindow>

Centering a popup window

A popup window can be centered in relation to any DisplayObject. When creating a popup window using addPopUp() or createPopUp(), it is important to understand that the parameter named "parent" (datatyped as DisplayObject) will be used to determine the reference point for centering a popup window. In most cases, you’ll want a popup window to be centered over the Application.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="openPopUpWindow(event)" >

    <mx:Script>
        <![CDATA[
            import comps.MyPopUpWindow;
            import mx.events.FlexEvent;
            import mx.managers.PopUpManager;
            
            // declare a variable for the reusable custom PopUp Window
            private var popup:MyPopUpWindow;

            // method to open the PopUp Window on creationComplete
            private function openPopUpWindow(evt:FlexEvent):void {
                // open the PopUp Window as a modal popup window
                // and store it in a variable for later use
                popup = PopUpManager.createPopUp(this,MyPopUpWindow,trueas MyPopUpWindow;
                // center the PopUp window
                PopUpManager.centerPopUp(popup);
            }
        ]]>
    </mx:Script>
    
</mx:Application>

In the previous example, notice that the DisplayObject used to center the popup window was this. In this case, this refers to the Application container, and therefore, the popup window was centered nicely over the Application. In many cases this will not refer to the Application container (i.e. inside custom components). When opening a popup window from within a custom component, use the following code to ensure the popup window is centered over the Application container and not the custom component.

popup = 
PopUpManager.createPopUp
(DisplayObject(this.parentApplication),MyPopUpWindow,trueas MyPopUpWindow;

Removing a popup window

Once our popup window has been created, displayed and optionally centered, there are a couple of options for removing it from display.

1. A parent removes a popup window from display

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="openPopUpWindow(event)">

    <mx:Script>
        <![CDATA[
            import mx.events.FlexMouseEvent;
            import comps.MyPopUpWindow;
            import mx.events.FlexEvent;
            import mx.managers.PopUpManager;
            
            // declare a variable for the reusable custom popup window
            private var popup:MyPopUpWindow;

            // method to open the popup window on creationComplete
            private function openPopUpWindow(evt:FlexEvent):void {
                // open the popup window as a modal popup window
                // and store it in a variable for later use
                popup = PopUpManager.createPopUp(this,MyPopUpWindow,trueas MyPopUpWindow;
                // register event listeners for closing the popup window
                // when the popup window close button is clicked...
                popup.addEventListener(Event.CLOSE, closePopUpWindow);
                // or when an area outside the popup window is clicked...
                popup.addEventListener(FlexMouseEvent.MOUSE_DOWN_OUTSIDE, closePopUpWindow);
                // center the popup window
                PopUpManager.centerPopUp(popup);
            }
            
            // method to close the popup window on close or mousedownoutside event
            private function closePopUpWindow(evt:Event):void {
                PopUpManager.removePopUp(popup);
            }
        ]]>
    </mx:Script>
    
</mx:Application>

2. A popup window removes itself from display

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="openPopUpWindow(event)">

    <mx:Script>
        <![CDATA[
            import mx.events.FlexMouseEvent;
            import comps.MyPopUpWindow;
            import mx.events.FlexEvent;
            import mx.managers.PopUpManager;
            
            // declare a variable for the reusable custom popup window
            private var popup:MyPopUpWindow;

            // method to open the popup window on creationComplete
            private function openPopUpWindow(evt:FlexEvent):void {
                // open the popup window as a modal popup window
                // and store it in a variable for later use
                popup = PopUpManager.createPopUp(this,MyPopUpWindow,trueas MyPopUpWindow;
                // center the popup window
                PopUpManager.centerPopUp(popup);
            }
        ]]>
    </mx:Script>
    
</mx:Application>
<?xml version="1.0" encoding="utf-8"?>
<!-- custom popup window component - comps/MyPopUpWindow.mxml -->
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" 
        layout="vertical" 
        title="My Custom PopUp Window"
        showCloseButton="true"
        close="PopUpManager.removePopUp(this)">
        
        <mx:Script>
            <![CDATA[
                import mx.managers.PopUpManager;
            ]]>
        </mx:Script>
         
        <mx:Form>
        
            <mx:FormHeading label="User Login"/>
            
            <mx:FormItem label="Username">
                <mx:TextInput id="un"/>
            </mx:FormItem>
            
            <mx:FormItem label="Password">
                <mx:TextInput id="pd"/>
            </mx:FormItem>
            
            <mx:ControlBar>
                <mx:Button id="save" label="Login"/>
                <mx:Button id="cancel" label="Cancel"/>
            </mx:ControlBar>
             
        </mx:Form>
        
</mx:TitleWindow>

I hope that gives you some ideas about how to use PopUpManager & TitleWindow in your Flex apps. Have fun!

Labels: , ,

Tuesday, November 3, 2009

Flex Tips - Using Bindable Metadata Events

One powerful feature of Flex is data binding. On the surface, data binding is simple to implement.

1. Mark a class as bindable to activate data binding on all public properties of the class

[Bindable]
public class Order
Or, mark individual properties as bindable

public class Order
{
    public function Order()
    {
    }
   
    [Bindable]
    public var orderNumber:int;
   
    [Bindable]
    public var orderName:String;
}

2. Declare an instance of the class preceded with the [Bindable] metadata tag

[Bindable]
private var order:Order = new Order();

3. Use a bindable property as the source of a data binding association. 3 techniques exist:

  • Wrap the source property in curly brackets

    <mx:Label id="myLabel" text="{order.orderName}"/>

  • Use a Binding tag

    <mx:Binding source="order.orderName" destination="myLabel.text"/>

  • Create the data binding association at runtime

    BindingUtils.bindProperty(myLabel, "text", order, "orderName");
The most common practice includes the use of curly brackets. The following example demonstrates data binding in its simplest form.

// Value object class marked bindable
    
[Bindable]
public class Order
{
    public function Order()
    {
    }
   
    public var orderNumber:int;
    public var orderName:String;
}

<!-- Application file -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
        layout="vertical">
    
    <mx:Script>
        <![CDATA[
            import comps.Order;
            
            [Bindable]
            private var order:Order = new Order();
        ]]>
    </mx:Script>
    
    <!-- binding #-->
    <mx:Label  text="{'Order #: ' + order.orderNumber}"/> 
    
    <!-- binding #-->
    <mx:Text text="{'Order Name: ' + order.orderName}"/>
    
    <mx:HBox>
    
        <mx:Button label="Change Order #" click="order.orderNumber = Math.floor(Math.random()*50)+1"/>
        <mx:TextInput id="ti" text="Enter name..." enter="order.orderName = ti.text" />
        
    </mx:HBox>
    
</mx:Application>

In most scenarios, this approach is acceptable. However, there are times when another technique is recommended. It involves explicitly defining which event type to dispatch when a source property changes. Let's review what we did previously.

We marked an entire class as bindable

[Bindable]
public class Order
Or, marked individual properties as bindable

public class Order
{
    public function Order()
    {
    }
   
    [Bindable]
    public var orderNumber:int;
   
    [Bindable]
    public var orderName:String;
}
This is equivalent to:

[Bindable(event="propertyChange")]
public class Order
Or

public class Order
{
    public function Order()
    {
    }
   
    [Bindable(event="propertyChange")]
    public var orderNumber:int;
   
    [Bindable(event="propertyChange")]
    public var orderName:String;
}

Data binding includes a lot of code generation. This is part of it. When using the [Bindable] metadata tag without explicitly defining the event type, the default event type is used – propertyChange.

Using the default event type (propertyChange) is fine except when you mark the entire class as bindable and the class contains several public properties (see Cairngorm's ModelLocator class). In this case, each public property will dispatch the same event type (propertyChange) when their value changes. This can be problematic.

To understand why this is a problem, let's take a closer look at what happens behind the scenes when data binding is used.

Note: This is a very high-level analysis of the inner-workings of data binding. For a detailed description of data binding, check out Michael Labriola's presentation entitled Diving in the Data Binding Waters

To accomplish data binding, here's what is required of you:

First, mark a class as bindable to activate data binding on all public properties of the class:

[Bindable]
public class Order
Or, mark individual properties as bindable

public class Order
{
    public function Order()
    {
    }
   
    [Bindable]
    public var orderNumber:int;
   
    [Bindable]
    public var orderName:String;
}

By doing this, the Flex framework does the following by generating more code:

  • Turns your class into an "event dispatcher" by implementing IEventDispatcher

  • Implements the methods required of the IEventDispatcher interface (addEventListener, removeEventListener, dispatchEvent, etc)

  • Creates implicit getters and setters for each bindable property

  • Converts [Bindable] to [Bindable(event="propertyChange")]

  • Adds code to your setter method required to dispatch the propertyChange event when the property value changes.

Why does the Flex framework do all of this? In a nutshell, so your class can:

  1. Broadcast an event when a bindable property value changes

  2. Allow interested parties to register an event listener triggered when a bindable property changes

But wait, there's more! When you actually use a bindable property as the source of a data binding (via curly brackets, Binding object or BindingUtils.bindProperty), the Flex Framework generates even more code required to:
  1. Create an array of Binding objects

    One Binding object is created for each data binding association. Each Binding object is responsible for updating a destination property when the corresponding source property changes.

  2. Create an array of PropertyWatcher objects

    One PropertyWatcher object is created for each data binding association. Each PropertyWatcher object is responsible for "listening" for an event to occur that signifies a change in the source property value. The event type is determined by the value defined in the [Bindable] metadata tag. Again, if no type is defined, propertyChange is used.

Failure to specify an event type in the [Bindable] metadata tag forces every public property to dispatch a propertyChange event when their value changes, and, in turn, every PropertyWatcher is "listening" for a propertyChange event. However, only the PropertyWatcher tasked with watching the property that actually changed is required to respond to the event and notify the Binding object responsible for updating the destination property.

The extra work involved with triggering multiple PropertyWatcher objects when only ONE source property changes can be expensive and can be avoided by simply defining a unique event for each bindable property. The following example demonstrates this technique.

// Value object that dispatches unique events for each bindable property

package comps
{
    import flash.events.Event;
    import flash.events.EventDispatcher;
    
    public class Order extends EventDispatcher
    {
        public function Order()
        {
        }
       
        private var _orderNumber:int;
       
        [Bindable(event="orderNumberChange")]
        public function get orderNumber():int {
            return _orderNumber;
        }
       
        public function set orderNumber(value:int):void {
            if _orderNumber !== value ) {
                _orderNumber = value;
                // remember to dispatch the unique event
                dispatchEvent(new Event("orderNumberChange"))
            }
        }
       
        private var _orderName:String;
        
        [Bindable(event="orderNameChange")]
        public function get orderName():String {
            return _orderName;
        }
       
        public function set orderName(value:String):void {
            if _orderName !== value ) {
                _orderName = value;
                // remember to dispatch the unique event
                dispatchEvent(new Event("orderNameChange"))
            }
        }

    }
}

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="vertical">
   
    <mx:Script>
        <![CDATA[
            import comps.Order;
           
            [Bindable]
            private var order:Order = new Order();
        ]]>
    </mx:Script>
   
    <!-- binding #-->
    <mx:Label id="myLabel" text="{'Order #: ' + order.orderNumber}"/>
   
    <!-- binding #-->
    <mx:Text text="{'Order Name: ' + order.orderName}"/>
   
    <mx:HBox>
   
        <mx:Button label="Change Order #" click="order.orderNumber = Math.floor(Math.random()*50)+1"/>
        <mx:TextInput id="ti" text="Enter name..." enter="order.orderName = ti.text" />
       
    </mx:HBox>
   
</mx:Application>

Labels: , ,

Saturday, August 22, 2009

Flex Examples - Creating a Custom Event

Flex applications consist of a component hierarchy starting with the Application root. Components are built-in or custom.



Interaction between components may occur down the component hierarchy (parent to child) or up (child to parent).

When a parent component interacts with a child component, the parent utilizes the child component's interface (API). This interface includes the component's public properties and methods.

For example, a parent component may hide a child component by setting its visible property to false.

<!-- parent component -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

    <mx:Script>
        <![CDATA[
            private function doSomething(event:MouseEvent):void {
                lbl.visible = false// parent (Application) interacting with child (Label)
            }
        ]]>
    </mx:Script>
    
    <!-- child component -->
    <mx:Label id="lbl" text="Hello there"/> 
    
    <!-- child component -->
    <mx:Button label="Hide Label" click="doSomething(event)"/>
    
</mx:Application>
Note: The Label's visible property is actually marked private but is publicly exposed through an implicit getter and setter.
Conversely, when a component wants to interact with a parent (or ancestor) component, it does so by dispatching events. Events are dispatched to signify an occurrence has taken place and include the details of the occurrence wrapped inside an event object.

Components may express an interest in a particular event by registering an event listener. Event listeners map an event to a method. This method is referred to as an event handler and is designed to perform one or more actions in response to an event.

Because an event (built-in or custom) propagates down through the display hierarchy during the Capturing phase of the event flow and back up during the Bubbling phase, ancestor components have two opportunities to register event listeners.

Note: Events do not bubble by default. All events extend the flash.events.Event class which requires 3 constructor arguments (type, bubbles and cancelable). Set bubbles to true and override the clone method to activate event bubbling.
In addition to the details of the event (target, currentTarget, type, eventPhase, etc), custom event objects can carry a "payload" used to transfer data. The payload is nothing more than a storage variable declared as a property of the event object.

In this short video, I will create a custom event designed to carry user information (username / password) captured when the user logs in.



Correction: In the video, I failed to override the clone method. Without overriding the clone method, a custom event will not be capable of bubbling.
View the source code of the custom event example.

After watching the video, it may still not be obvious why this approach is considered a best practice. Following the principles of encapsulation, we want our components to represent a "black box". We interact with a component via its public interface (API) and it broadcasts information about itself using events. If you find the event and the information it contains to be interesting, listen for the event and act on it.

Interaction between components in this fashion promotes loose coupling which ultimately provides greater flexibility.

Labels: , ,