Wednesday, 4 March 2009

Getting Started with OpenFlux 1: the basics

In this example I’ll show you how to create a useful component for social networking websites: a Link to a user’s page with the user’s photo, name or both. Then in part 2 I’ll show you how to organize them into a list and connect them with Cairngorm. The OpenFlux framework is somewhat different to vanilla Flex - forget everything you already know for now - later things will start to reconnect.

These tutorials requires active participation, try writing the code as you read the samples. I recommend you type everything in as FlexBuilder will autocomplete a lot of imports and assign namespaces. Even if you just cut and paste a bit, it’s good practice to make the individual file. The really fascinating thing about OpenFlux is seeing how all the different classes work together - the architecture. After the next release of OpenFlux I’ll post a video version too.

These are 'my' methods of using OpenFlux not 'the' method and certainly not the only one. In particular it is common to use public variables on Component, rather than a VO, as the data model. I prefer this approach as it works smoothly with Cairngorm, as we shall see in part 2... but I'm getting ahead of myself.

The first step is to identify how the component will be used in an application, it’s ’use cases’. There are two kinds of use case for a visual component. They display dynamic information and respond to certain user gestures (i.e. mouse or keyboard events). So we ask ourselves:

  1. what does it show?
  2. what does it do?

Then we write a Value Object (VO) that provides the information necessary for the component to do and show what it will. A VO is simply an actionscript class that has only public properties or getter/setters. A VO will act as Data Model for our component. Link shows a photo and the user’s name. Therefore our component’s VO requires a string for the name and another string for the URL where the image will be found:

public var photoUrl:String;
public var name:String;

We want to show the user’s page when the Link is clicked, so the component also needs to know the user’s ID:

public var userID:uint;

So we create a class called LinkVO that has nothing but these 3 public variables. Eventually this class will hold information received from our server, mapped with remote class metadata to a java equivalent.


Next we need to make a few more files. First the component itself, Link, which holds all the other parts together. Then we will make a two views; one with the user’s name the other with their photo. Being able to readily change the apparence of components is a major strength of OpenFlux, and is far easier to use than messing around with Flex borderSkin assignments etc. etc. Finally we’ll  write a simple controller that will subscribe responses to mouse clicks on the view.


role name extends
componentLink.as ListItem
viewTextLinkView.mxmlFluxView
viewImageLinkView.mxmlFluxView
controllerLinkController.asFluxController

The view is displays information. The controller responds to user gestures. The component stores the VO and sets the default view and controller. I recommend the pictured folder structure. In the downloadable files these are at.newbe.model etc. 

Link - component

First override the getter and setter functions for data from the parent class (ListItem). I’ll talk more about data when we put our Link in List. We’ll runtime type check that a LinkVO has been assigned to this property:

private var _linkVO:LinkVO;

override public function get data():Object { return _linkVO; }

override public function set data(value:Object):void {
if (value is LinkVO) _linkVO = value as LinkVO;
}

ListItem implements IFactory with its newInstance():* method. We must override this so it returns a Link, rather than a ListItem. Implementing IFactory allows a component be a List’s item renderer.

override public function newInstance():* {
var instance:Link = new Link();
return instance;
}

Finally we’ll set the default view and controller. We do this by overriding the protected createChildren() method. If the view or controller are undefined, set values as new instances of the defaults. Mark the class as [Bindable] And that’s it! Check the class you have written is the same as the downloaded file.

override protected function createChildren():void {
if(!controller) controller = new LinkController();
if(!view) view = new TextLinkView();
super.createChildren();
}

TextLinkView - view showing user’s name

Now we’ll make a very simple view that will display the user’s name. Open tags for the property +content, within them place a label with text bound to {component.data.name}:

<flux:content>
<mx:Label text="{component.data.name}"/>
</flux:content>

All visual components that will be displayed by the view are placed within the FluxView’s content variable. Anything else, like degrafa fills/strokes and states go outside. Note that you can access the component with the eponymous variable, and thus we bind the view directly to the VO. I love <10 line classes!

PhotoLinkView - view showing user’s profile photo

Next a more interesting view that shows the user’s profile photo. There are lots of ways to do this, but this is by far the best I have found. We’ll display the photo using a Degrapha image fill, with an image loaded from the photoUrl property of LinkVO:

<paint:BitmapFill id="photo" source="{component.data.photoUrl}" smooth="true" repeatX="{BitmapFill.STRETCH}" repeatY="{BitmapFill.STRETCH}" />

One advantage of this method over the default Flex Image component is advanced anti-aliasing, set by smooth="true". This fill will scale to the size of the shape it’s applied to. Next we’ll make a solid-stroke outline for the photo, and a rectangle for fill
and stroke to be applied to:

<degrafa:SolidStroke id="edge" color="#FF9900" weight="2" caps="round" joints="round" pixelHinting="true" />

<degrafa:GeometryComposition graphicsTarget="{[image]}">

<degrafa:RoundedRectangle width="{width}" height="{height}" fill="{photo}" stroke="{edge}" cornerRadius="2.5" />

</degrafa:GeometryComposition>

The graphicsTarget is a component called image within content. This will simply be another FluxView. Think of FluxView used this way as similar to how you might use Canvas in Flex Component development:

<flux:content><flux:FluxView id="image" width="{width ? width : 54}" height="{height ? height : 54}" /></flux:content>

The number 54 in the width and height setting are the default width and height setting for this component. I’ve tried several other ways of setting defult height, including measure() and setting in the parent tag of the mxml document. It seems that currently there are bugs if you have only degrafa shapes within content. This seems a neat enough solution, thoughts on performance anyone?

LinkController - controller

This  controller is very simple, in part because of some of OpenFlux’s fantastic custom metadata tags. Create a private variable with the same and type as our component, Link. Now add the [ModelAlias] tag before it, and this variable will represent a typed reference to the component at runtime.

[ModelAlias] private var link:Link;

This can save a lot of runtime type checking in more sophisticated controller. Now add the following metadata at the top of the class:

[ViewHandler(event="mouseUp", handler="mouseUpHandler")]

public class LinkController extends FluxController

The ViewHandler tag helps you listen to mouse and keyboard events from the view, and name a handler to respond to them. In this case we’re listening for mouseup with a handler called mouseUpHandler. Let’s define that function:

metadata function mouseUpHandler(event:MouseEvent):void {

trace( "please show me the user’s page" );

}

Note that we define a metadata function, rather than private. This allows the metadata processor to wire things together properly. Next episode we’ll dispatch a Cairngorm Event call from this function... bet you can’t wait! Using the metadata is crucial to getting the most out of OpenFlux, this is a taste but much more is possible. I explain fully how to use these and the other metadata tags in this blog post.

Testing our new component

Open up Main.mxml and create a LinkVO with some dummy information. Then make a PhotoLinkView. Then create a 2 Links in an HBox, and bind it’s data property to the LinkVO:

<vo:LinkVO id="user1" photoUrl="assets/images/01.jpg" name="Flexy Frederick" userID="1" />

<views:PhotoLinkView id="photo" visible="false" />
Then create a 2 Links in a VBox, and bind their data properties to the LinkVO. Set the PhotoLinkView as the view for one of them:
<mx:VBox>
<view:Link data="{user1}" view="{photo}" />
<view:Link data="{user1}" />
</mx:VBox>
Compile as see the user’s photo followed by their name. Click on either of them and the controller will trace. Congratulations you have now mastered the basics architecture of a single OpenFlux component. In part two we’ll use Link as an itemRenderer for the List component, an advanced container supporting dynamic layouts. We’ll also hook things up to Cairngorm, a powerful Adobe supported MVC application archetitecure.

Thursday, 19 February 2009

The Joy of OpenFlux Metadata

Custom Metadata a real strength of the OpenFlux framework, abbreviating a lot of very dull, repetitive and fiddly tasks. Here is as overview of what's currently possible, organized by what I've found to be common use cases.  
Metadata tagged before a variable work equally before an accessor and visa versa. All these metadata are to be used in the controller, apart from [StyleBinding] which can be used in the model, view or the controller. Where I've quoted mxml tags I've used { and } instead of less than/greater than - this is due to I think a bug with blogger, any suggestions about this, or any spelling mistakes please comment. I'm dyslexic, (hence the color!) and value clarity of communication very highly, so if anything doesn't make perfect sense please point it out and I'll try to clarify. 

Referencing the Component with [ModelAlias]

Simply add the [ModelAlias] tag before a variable declaration. Now you can use this variable to reference to the component anywhere in your controller.

[ModelAlias] public var typedComponent:CustomComponent;

If the component can be typed as CustomComponent at runtime, then the variable typedComponent will reference the component, otherwise it will be null.

Note: although the FluxController's component:IFluxComponent variable also references the component, you will need to do a lot of tedious type casting to make use of it e.g.

var c:CustomComponent = component as CustomComponent;
c.customProperty = value;

Fortunately [ModelAlias] provides a very neat shortcut, use it every time.


Referencing children of the view with [ViewContract]

Suppose that your view contains a child element that you'd like to reference in the controller for some reason:

{ flux:FluxButton id="submit" /}

Simply add the [ViewContract] tag before a variable with the same name and type in the controller.

[ViewContract]
private var _submit:FluxButton
public fucntion get submit():FluxButton { return _submit; }
public function set submit(value:FluxButton
):void {
_submit = value;
}

If the name and type match, submit will reference the button, otherwise null.

Note: you can also reference children of the view by typing component.view:IFluxView and accessing it's custom properties. This can get very messy so don't bother - [ViewContract] is a fantastic shortcut.

Adding event listeners to the view with [ViewHandler]

Your view may dispatch mouse of keyboard events that need to be handled. There are two steps.

  1. Add a [ViewHandler] tag at the top of the controller class specifying the names of the event you want to listen for, and the function that will handle it.
  2. Define a metadata function with a matching name (metadata functions are written in exactly the same way as public/private/protected functions).

[ViewHandler (event="click", handler="clickHandler")]
public class CustomController extends FluxController

metadata function clickHandler(event:MouseEvent):void { trace("clicked the view"); }

now when the view is clicked, mouseUpHandler() will fire, easy huh? Especially useful for simple controls like buttons. See ButtonController for an example.


Adding event listeners to children of the view with
[EventHandler] and [ViewContract]

Whilst [ViewHandler] works fine for events from simple controls like buttons, it is not suitable for something like a button bar where you need to know which particular subcomponent of the view dispatched the event.

{ flux:FluxButton id="button1" /}
{ flux:FluxButton id="button2" /}

  1. Define a variable with the same name and type as the subcomponent you are interested in.
  2. Add a [ViewContract] tag, now the variable references the subcomponent.
  3. Add an [EventHandler] tag with the name of the event to listen for and the name of the handler.
  4. Define a handler metadata function with the same name

[EventHandler (event="click", handler="button1ClickHandler")]
[ViewContract] public var button1:FluxButton ;

[EventHandler(event="click", handler="button2ClickHandler")]
[ViewContract] public var button2:FluxButton ;

metadata function button1ClickHandler(event:MouseEvent):void { trace("clicked button 1"); }

metadata function button2ClickHandler(event:MouseEvent):void { trace("clicked button 2"); }

In general tagging a variable with [EventHandler] adds the named handler function as a listener to the named event dispatched by the variable.

Adding event listeners to the component with [EventHandler]

When [EventHandler] appears at the top of a class it behaves somewhat differently to when it is appears before a variable. At the top of a class [EventHandler] adds an event listener for an event dispatched from the component, for example when a property changes.

[EventHandler(event="propertyChange", handler="propertyChangeHandler")]
public class CustomController extends FluxController

metadata function propertyChangeHandler(event:PropertyChangeEvent):void { trace("component property changed"); }

Using [StyleBinding] to synchronize styles across the component, view and controller

[StyleBinding] tags can appear before any variable in the component, the view or the controller. If a style that has the same name and type as the [StyleBinding] tagged variable can be resolved for the component, then the variable will be bound to that style at runtime.

[StyleBinding] public var selectable:Boolean;
[StyleBinding] public var layout:ILayout;

This way you can specify all the styles required by all three classes within the CSS style declaration for the component, try it out - works like magic! If you want to style the component inline you need to add the vanilla Flex [Style] tag at the top of the component class,  a good example can be found in ButtonController
Also note that [StyleBinding] allows you to treat style values in the same way as normal bound variables. This is far simpler than esoteric flex methods like getStyle() and overriding styleChanged().