How to… add FlashVars to Parsley context

It is not uncommon when developing flash/flex application to pass runtime data from the surrounding environment. One very common way of doing this is to define FlashVars properties inside your HTML wrapper and access their values using either the FlexGlobals.topLevelApplication.parameters or LoaderConfig.parameters objects. If you are using an application framework with IOC Container capabilities – in this case Parsley – there is also a good chance that you’d want your FlashVars inside the container/context to be able to inject them in different places.

Below is the sample object which will be passed to the test flex application named ParsleyFlashVars:

var flashvars = {
	name: "user1",
	type: "2",
	loggedIn: "true",
	host: "172.0.0.1",
	port: "8080"
};

and a couple of mechanisms for getting the FlashVars properties loaded into Parsley context at runtime.

1. Runtime configuration:

The good: Parsley allows runtime configuration e.g. you can specify instances that should be part of the container at runtime. Applied to flashvars this translates into something like:

<!-- NO ID -->
<parsley:ContextBuilder>
    <parsley:RuntimeConfig instances="{[parameters]}"/>
</parsley:ContextBuilder>
<!-- or -->
<parsley:ContextBuilder>
    <parsley:RuntimeConfig instances="{[FlexGlobals.topLevelApplication.parameters]}"/>
</parsley:ContextBuilder>

<!-- SPECIFYING ID ID -->
 <parsley:RuntimeConfig>
        <parsley:Instance id="params" instance="{parameters}"/>
</parsley:RuntimeConfig>
<!-- or -->
 <parsley:RuntimeConfig>
        <parsley:Instance id="params" instance="{FlexGlobals.topLevelApplication.parameters}"/>
</parsley:RuntimeConfig>

And an attempt to configure some objects may look like:

Object type="{SomeClass}">
    <Property name="config" idRef="{params}"/>
</Object>
 
<Object type="{SomeServiceClass}">
    <Property name="host" idRef="{params.host}"/>
    <Property name="port" idRef="{params.port}"/>
</Object>
 
...
[Inject(id="params")]
public var params:Object;

The bad: it won’t work. Since parameters is a plain Object, it will never trigger the binding. So the tag will never pick up on the parameters value, trying to add null instead. First the classic binding warning will pop in the console, followed by a nice error message:

warning: unable to bind to property 'parameters' on class 'ParsleyFlashVars'
...
Error: Instance must not be null

The good (again): it is fixable. Simply by wrapping the parameters instance with an ObjectProxy:

[Bindable]
private var flashVarsProxy:ObjectProxy;
...

private function onPreinitialize(event:FlexEvent):void
{
	flashVarsProxy = new ObjectProxy(parameters);
}

...
<!-- NO ID -->
<parsley:ContextBuilder>
    <parsley:RuntimeConfig instances="{[flashVarsProxy]}"/>
</parsley:ContextBuilder>

<!-- SPECIFYING ID ID -->
 <parsley:RuntimeConfig>
        <parsley:Instance id="params" instance="{flashVarsProxy}"/>
</parsley:RuntimeConfig>

or even nicer with a custom class:

package domain
{
[Bindable]
	public class User
	{
		public var name:String;
		public var type:Number;
		public var loggedIn:Boolean;
	}
}
...

[Bindable]
private var user:User;
...

private function onPreinitialize(event:FlexEvent):void
{
	user = = createUserFromObject(parameters);
}

private function createUserFromObject(obj:Object):User
{
	var newUser:User = new User();
	newUser.name = obj.name;
	newUser.type = obj.type;
	newUser.loggedIn = obj.loggedIn;
	return newUser;
}

...
<!-- NO ID -->
<parsley:ContextBuilder>
    <parsley:RuntimeConfig instances="{[user]}"/>
</parsley:ContextBuilder>

<!-- SPECIFYING ID ID -->
 <parsley:RuntimeConfig>
        <parsley:Instance id="params" instance="{user}"/>
</parsley:RuntimeConfig>

The ugly: you end up having a cached variable in the main file just for the sake of firing a binding. The bindable flashVarsProxy property does not really justify it’s existence.  One can argue that the overhead is minimal since the RuntimConfig already requires an mxml binding, and perhaps using the word ugly is too much, but it’s definitely not clean.

2. Configuration Properties – e.g. the use of properties, either loaded from external files at runtime or declared inline.
It comes in 3 flavors, but the the interest here is only the PropertyObject tag:

<parsley:PropertiesObject ref="{parameters}">

while and dependency will be provided via:

<Object type="{SomeServiceClass}">
    <Property name="host" idRef="{properties.host}"/>
    <Property name="port" idRef="{properties.port}"/>
</Object>

The same good-bad-ugly discussion as above holds here leading to the same conclusion.

Enter Configuration DSL or a more elegant way:

“Added in version 2.3 this configuration option allows to specify most of the features you usually specify with tags (MXML, XML, Metadata) in code with a fluent syntax that allows to add logic to the configuration. The DSL spans multiple levels of configuration, it can be used to create an entire Context from scratch, but it can also be used to create object definitions in code and then combine those with the other configuration mechanisms. Since it is useful for many types of configuration tasks it is now also getting used by nearly all major extension points. Thus when building a custom metadata tag for example, you can use this convenient DSL for adding functionality to an object.”

So for creating the context and specifying specifying configuration class will transform from

<parsley:ContextBuilder/>

to:

ContextBuilder.newBuilder()
    .config(FlexConfig.forClass(FlashVarsContext))
    .build();

An object will be added at runtime like this:

var flashVarsProxy:ObjectProxy = new ObjectProxy(parameters);

ContextBuilder.newBuilder()
    .config(FlexConfig.forClass(FlashVarsContext))
    .object(flashVarsProxy, "flashVars")
    .build();

and using PropertyObject (order matters here):

ContextBuilder.newSetup()
	.viewRoot(this)
	.newBuilder()
	.config(Properties.forObject(parameters))
	.config(FlexConfig.forClass(FlashVarsContext))
	.build();

And you can hook a listener for the preinitialize event and place the code inside, since the parameters are available by the time it is dispatched. Full snippet below:

private function buildContext():void
{
	var flashVarsProxy:ObjectProxy = new ObjectProxy(parameters);
	var user:User = createUserFromObject(parameters);

	ContextBuilder.newSetup()
		.viewRoot(this)
		.newBuilder()
			.config(Properties.forObject(parameters))
			.config(FlexConfig.forClass(FlashVarsContext))
			.object(flashVarsProxy, "flashVars")
				.object(user, "user")
				.object(parameters.host, "host")
				.object(parameters.port, "port")
				.build();
}

VIEW working example  and DOWNLOAD .FXP.

Feedback also welcome!

Leave a Reply

Your email address will not be published. Required fields are marked *

*