Categories
Articles

Basic Parsley Framework Flex Example Explained

I decided to break down the Parsley framework for Flex and Flash projects. This is my own minimalist example. I am not going to extol virtues or evils with using Parsley and assume you just want to get a basic example that explains the pieces.

This does not tie into web server but I added a basic example that uses Zend AMF, PHP, MySQL, Flex Remote Objects.

Also if you want to look at a very basic Flash Actionscript example see Parsley Hello World For A Flash Builder Actionscript Project.

It was hard to find a good minimalist starter example that takes the time to explain the details. The Sitronnier (blogginglemon) examples had other technologies or unneeded features cluttering the code and was devoid of any substantive elaboration. The Sitronnier examples are linked out of the Parsley documentation and are worthwhile bookmarking to review once you get your feet wet.

The best I have found is Arturo Alvarado’s posts at Art’s Flex Notes. The 4 part series includes a comparative framework analysis and why he prefers Parsley. Art breaks his Parsley example down with one more level of granularity than I do here. I hope Art continues to add to his series as he communicates very clearly.

The “Hello World” getting started example in the Parsley documentation is also a great starting point except there is no explanation outside of comments to soothe your into it. But the comments are great and I added my own coding style to this example and simplified further along with a few changes to better illustrate some items.

My example focuses on the main structure and foundational features in Parsley. There is much more available in Parsley to learn and explore.

Learn More About Flex 4 From Adobe

Parsley 2.3 is used and the SWCs are included in the example Flex 4 project download I have here. I did appreciate these SWCs also being included in the downloads from the Parsley download page. Lets you get to learning faster.

You can build this with the free Flex SDK by using the code in the src folder and be sure to include a path to the Parsley and Spicelib library.

Application Class – ParsleyFramework_HelloWorld.mxml
This is the main MXML file. Line 12 links another MXML file, ParselyFramework_HelloWorld_Configuration, that configures Parsley framework. Other than that, this appears to be a basic Flex MXML file with two UI components.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:hw="com.alh.parsely.example.helloworld.*"
			   xmlns:parsley="http://www.spicefactory.org/parsley"
			   xmlns:mx="library://ns.adobe.com/flex/mx" 
			   minWidth="955" minHeight="600">
	<fx:Declarations>
		<!-- 
		Parsley configuration MXML file
		-->
		<parsley:ContextBuilder config="ParsleyFramework_HelloWorld_Configuration" />
	</fx:Declarations>
	<!-- 
		UI
	-->
	<s:VGroup width="100%" height="98%"  gap="10" horizontalAlign="center">
		<hw:InputPanel width="50%" height="100%" title="Input Panel"/>
		<hw:OutputPanel width="50%" height="100%"  title="Output Panel"/>
	</s:VGroup>

</s:Application>

Parsley Configuration – ParsleyFramework_HelloWorld_Configuration.mxml
This is a Parsley configuration file. Parsley reads this file.

There are three model files in this application. One, InputModel, is for the InputPanel, one, OutputModel, for the OutputPanel and a shared model, SharedModel.

This configuration file identifies these for the Parsley framework to use.

<?xml version="1.0" encoding="utf-8"?>
<Objects 
	xmlns:fx="http://ns.adobe.com/mxml/2009"
	xmlns="http://www.spicefactory.org/parsley"
	xmlns:hw="com.alh.parsley.example.helloworld.*">
	<fx:Declarations>
		<!--
			Parsley defined objects slated for injection where [Inject] metatag appears.
		-->
		<hw:SharedModel/>
		<hw:InputModel/>
		<hw:OutputModel/>
	</fx:Declarations>
</Objects>

[ad name=”Google Adsense”]
InputPanel.mxml
This is a UI component for our MVC framework. It lets a user enter text and press a button to submit. It validates that the entry contains input.

Line 13 registers this UI component with Parsley.

Lines 23-25 inject or we could say include the contents of InputModel, the data for a MVC framework.

InputPanel is self contained in its messaging but broadcasts a message via the InputModel model sendInputMessage (…) method on line 44.

Line 35 was my way of demonstrating the Parsley event to notify that a model has loaded properly. I do not see it necessary here other from a demonstration point of view. More about this when we review InputModel.

I decided to use the model to store the localization language rather than Flex resource manager for simplicity. I do not like localization language embedded in UI code so this is fine for demonstration and also small apps where manual language change has a low chance of being needed. You see the language references on lines 64 and 70.

Line 80 demonstrates the use of a nested model. The InputModel contains the SharedModel. More on that when we get to look at InputModel.

<?xml version="1.0" encoding="utf-8"?>
<s:Panel 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark"
	xmlns:sf="http://www.spicefactory.org/parsley"
	enabled="false"
	updateComplete="updateCompleteHandler(event)"
	>
	<fx:Declarations>
		<!-- 
		Parsley will manage this component.
		-->
		<sf:Configure/>
	</fx:Declarations>
	<fx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.events.FlexEvent;
			/** 
			 * Inject the model.
			 * The [Inject] metadata searches context for an object of matching type.
			 */
			[Inject]
			[Bindable]
			public var model:InputModel;
			/** 
			 * Handler from the UpdateComplete event
			 */
			protected function updateCompleteHandler(event:FlexEvent):void
			{
				// Demonstrates ways to test injection occured. See InputModel.
				// Model is injected: model != null
				// and
				// model init() was completed: model.initialized
				enabled =  model != null && model.initialized;
			}
			/** 
			 * Send a message to Parsley framework.
			 */
			private function sendMessage():void
			{
				if (input_ti.text.length > 0)
				{
					model.sendInputMessage(input_ti.text);
					input_ti.text = "";
				}
				else
				{
					Alert.show("Please enter a message.", "Attention");
				}
			}
		]]>
	</fx:Script>
	<!--
		UI
	-->
	<s:layout>
		<s:VerticalLayout gap="10" paddingLeft="12" paddingRight="12" paddingBottom="12" paddingTop="12"/>
	</s:layout>
	<s:HGroup verticalAlign="middle">
		<!--
			Reference to model
		-->
		<s:Label text="{model.lang_typeMessageHere + ' '}"/>
		<s:TextInput id="input_ti"/>
		<!--
			Reference to model
		-->
		<s:Button id ="send_button"
			label="{model.lang_send}" 
			click="sendMessage()"/>
	</s:HGroup>
	<!--
		Filler
	-->
	<s:Rect height="100%"/>
	<!--
		Use of a nested dependency. The sharedModel is a SharedModel object.
	-->
	<s:Label text="{model.sharedModel.lang_numberOfMessagesSent + ' ' +  model.sharedModel.numberOfMessagesSent }"/>
</s:Panel>

InputModel.mxml
This is data and messaging. By its name it says the model is more localized to the “input” UI component, but that is not a restriction.

Remember that the Parsley configuration mxml file, discussed above, registered this file.

Lines 20 and 21 show how to create a dispatcher method with Parsley. The dispatcher event is called on line 41.

Line 41 contains the Event, or in Parsley we say message, named InputMessage. We will look at this after we done with InputModel.

Line 39 shows the sendInputMessage(…) method used in InputPanel to broadcast the button click. This decouples the InputMessage event from the UI and places control with the model. Changing the event or adding additional logic or events leaves the UI unchanged. The UI is thus linked by the method sendInputMessage(…).

You may need to know if the model loaded properly. Parsley will call a method on its objects. You use the [Init] meta tag to identify the method and here it is shown on line 34. The init() method on line 35 sets the initialized variable in the model. The initialized variable then can be used by code dependent on the model to know it is loaded and firing. You see that back in the InputPanel code on line 34.

Lines 13 to 15 show how to inject another model. In this case the SharedModel file by its name implies having data shared by more than one area of the framework. Depending on your application structure you may find one model for all is sufficient or as demonstrated in this example having the model distributed for a modular approach.

package com.alh.parsley.example.helloworld
{
	public class InputModel
	{
		/**
		 * State of the init method executing or not. 
		 * */
		[Bindable]
		public var initialized:Boolean;
		/**
		 * Nest another model using injection. 
		 * */
		[Inject]
		[Bindable]
		public var sharedModel:SharedModel;
		/**
		 * Define a message in the model
		 * The [MessageDispatcher] metatag adds function to Parsley for sending (dispatch) messages.
		 */ 
		[MessageDispatcher]
		public var sendMessage:Function;
		/**
		 * Language
		 */ 
		[Bindable]
		public var lang_send:String = "Send";
		[Bindable]
		public var lang_typeMessageHere :String = "Type your message here:";
		
		/**
		 * The [Init] metadata tells Parsley to call the annotated method after 
		 * an instance of this object is created and configured. Used in InputPanel.
		 */ 
		[Init]
		public function init() : void 
		{
			initialized = true;
		}
		public function sendInputMessage(text:String) : void 
		{
			sendMessage(new InputMessage(InputMessage.SEND_MESSAGE,text));
		}
	}
}

[ad name=”Google Adsense”]
InputMessage.as
InputMessage is a custom Actionscript event class. Can be used in other frameworks or no framework. However the InputModel makes this a Parsley message.

package com.alh.parsley.example.helloworld
{
	import flash.events.Event;
	/** 
	 * Defines an input message for application.
	 */
	public class InputMessage extends Event
	{
		public static const SEND_MESSAGE:String = "com.alh.parsley.example.helloworld.send_message";
		/** 
		 * The message text
		 */
		public var messageText:String = "";
		/** 
		 * Constructor
		 */
		public function InputMessage(type:String, messageText:String, bubbles:Boolean = true, cancelable:Boolean = true)
		{
			super(type, bubbles, cancelable);
			this.messageText = messageText;
		}
	}
}

OutputPanel.mxml
Repeats many of the similarities of the InputPanel. There is no messaging included here. Basic binding to the Output model, that we will look at next, is set on lines 21 to 23.

Lines 32 and 40 have the bound values.

<?xml version="1.0" encoding="utf-8"?>
<s:Panel 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark"
	xmlns:sf="http://www.spicefactory.org/parsley"
	>
	<fx:Declarations>
		<!-- 
		Parsley will manage this component.
		-->
		<sf:Configure/>
	</fx:Declarations>
	<fx:Script>
		<![CDATA[
			/** 
			 * The [Inject] metadata tells Parsley to inject a dependency by 
			 * searching the context for an object of matching type.
			 */
			[Inject]
			[Bindable]
			public var model:OutputModel;
		]]>
	</fx:Script>
	<!--
		UI
	-->
	<s:layout>
		<s:VerticalLayout gap="10" paddingLeft="12" paddingRight="12" paddingBottom="12" paddingTop="12"/>
	</s:layout>
	<s:Label text="{model.lang_messagesTitle + ' '}"/>
	<!--
		Using model to bind values
	-->
	<s:TextArea width="100%" height="100%" text="{ model.messages }"/>
	<!--
		Use of a nested dependency. The sharedModel is a SharedModel object.
	-->
	<s:Label text="{model.sharedModel.lang_numberOfMessagesSent + ' ' +  model.sharedModel.numberOfMessagesSent }"/>
</s:Panel>

[ad name=”Google Adsense”]
OutputModel.as
This is another local model that by its name associates with InputPanel.

The meta tag [MessageHandler] registers the sendInputMessageHandler(…) method to receive InputMessage events. You recall the InputModel is dispatching InputMessage events.

The sendInputMessageHandler(…) basically updates the messages model value.

The practice of nesting the SharedModel is repeated on lines 13 -15.

package com.alh.parsley.example.helloworld
{
	public class OutputModel
	{
		/** 
		 * Hold the appended list of text messages sent.
		 */
		[Bindable]
		public var messages:String = "";
		/**
		 * Nest another model using injection. 
		 * */
		[Inject]
		[Bindable]
		public var sharedModel:SharedModel;
		/**
		 * Language
		 */ 
		[Bindable]
		public var lang_messagesTitle:String = "Messages Received:";
		/**
		 * Route InputMessage types to this method
		 * The [MessageHandler] metadata tells Parsley to make the routing happen.
		 */
		[MessageHandler]
		public function sendInputMessageHandler( message:InputMessage ) : void 
		{
			// Increment the message count.
			sharedModel.numberOfMessagesSent++;
			// Prepend a line number.
			message.messageText = sharedModel.numberOfMessagesSent + ". " + message.messageText;
			// Update model.
			messages += message.messageText + "\n";
		}
	}
}

SharedModel.as
Model data assumed by the file name shared throughout the application. You can see this above in InputModel and OutputModel that in turn allow InputPanel and OutputPanel to use this data.

package com.alh.parsley.example.helloworld
{
	public class SharedModel
	{
		[Bindable]
		public var numberOfMessagesSent:Number = 0;
		
		[Bindable]
		public var lang_numberOfMessagesSent :String = "Number of messages sent:";

	}
}