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().