Mate is an event- and tag-based Flex framework. The API is in MXML tags. Mate-based applications are built using implicit invocation caused by dispatching and dependency injection of the results into views.
With implicit invocation, any interested object can listen to the
events that are listed (with their handlers) in one or more MXML
components of type <EventMap>
.
Any important action in the application should generate one of the events
listed in this map. In Mate, as opposed to Cairngorm, an application
developer can configure multiple handlers for each event and specify the
sequence in which they should be invoked by assigning priorities in the
event handler.
This section walks you through the Mate framework by analyzing its version of Café Townsend, created by the Mate team, which we encourage you to download from http://mate.asfusion.com/page/examples/cafe-townsend.
The data flow between Mate components while displaying a list of Café employees is depicted in Figure 1-6.
Mate is a much less intrusive framework than Cairngorm, as it does not force developers to add lots of boilerplate code in their applications. Figure 1-7 shows the project structure of the Café. The folder maps contains objects added to the Café project because it’s written using Mate (at least one event map is required). These objects are included in the main application as follows:
<maps:MainEventMap /> <maps:ModelMap />
All events that bubble up in Café will reach these map objects, which will process them according to the event handlers defined in these event maps.
Cairngorm relies on central repositories of events, services, and
models; Mate promotes decoupling among business logic, events, and
services. Mate does not force you to extend any classes. Just create an
<EventMap>
in your application
object, define <EventHandler>
tags there, and
declare the services required for processing these events inside the
handlers, i.e., <RemoteObjectInvoker>
, <HTTPServiceInvoker>
, or <WebServiceInvoker>
. When your
application grows, consider creating multiple EventMap
objects to keep them manageable.
Example 1-10 depicts about half of the code of the MainEventMap.mxml from Café Townsend.
Example 1-10. Fragment of MainEventMap.mxml
<?xml version="1.0" encoding="utf-8"?> <EventMap xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="http://mate.asfusion.com/"> <mx:Script> <![CDATA[ import mx.events.*; import com.cafetownsend.events.*; import com.cafetownsend.business.*; ]]> </mx:Script> <!-- FlexEvent.PREINITIALIZE --> <EventHandlers type="{FlexEvent.PREINITIALIZE}"> <ObjectBuilder generator="{AuthorizationManager}" constructorArguments="{scope.dispatcher}" /> </EventHandlers> <!-- FlexEvent.APPLICATION_COMPLETE --> <EventHandlers type="{FlexEvent.APPLICATION_COMPLETE}"> <HTTPServiceInvoker instance="{employeesService}"> <resultHandlers> <MethodInvoker generator="{EmployeeParser}" method="loadEmployeesFromXML" arguments="{resultObject}" /> <MethodInvoker generator="{EmployeeManager}" method="saveEmpoyeeList" arguments="{lastReturn}" /> </resultHandlers> </HTTPServiceInvoker> </EventHandlers> <!-- LoginEvent.LOGIN --> <EventHandlers type="{LoginEvent.LOGIN}"> <MethodInvoker generator="{AuthorizationManager}" method="login" arguments="{[event.username, event.password]}" /> <!-- Because there is no server request, we just send the response right away. Normally, we would do this inside the resultSequence --> <ResponseAnnouncer type="loginResultResponse"> <Properties loginResult="{lastReturn}"/> </ResponseAnnouncer> </EventHandlers> <!-- EmployeeEvent.SAVE --> <EventHandlers type="{EmployeeEvent.SAVE}"> <MethodInvoker generator="{EmployeeManager}" method="saveEmployee" arguments="{event.employee}"/> <!-- assume everything was ok, make employee list show up --> <EventAnnouncer generator="{NavigationEvent}" type="{NavigationEvent.EMPLOYEE_LIST}"/> </EventHandlers> ... <mx:HTTPService id="employeesService" url="assets/data/Employees.xml" resultFormat="e4x" /> </EventMap>
In the example code, note the declaration of the handler of the
system Flex event APPLICATION_COMPLETE
with nested HttpServiceInvoker
to get
the data from Employees.xml via
employeesService
, which is defined at
the very end of this map using the familiar <mx:HTTPService>
tag.
EventHandler
objects match the type of the received
event with the one specified in the type attribute in the map
file.
When your application receives the result of the call to employeesService
, it invokes the functions
defined in the resultHandlers
nested
inside the service invoker. In our case, two methods listed in the result
handler section are called sequentially: EmployeeParser.loadEmployeesForXML()
and
EmployeeManager.saveEmployeeList()
:
<resultHandlers> <MethodInvoker generator="{EmployeeParser}" method="loadEmployeesFromXML" arguments="{resultObject}" /> <MethodInvoker generator="{EmployeeManager}" method="saveEmpoyeeList" arguments="{lastReturn}" /> </resultHandlers>
The first method, loadEmployeeList()
, gets the resultObject
returned by the HTTPService
. The second one, saveEmployeeList()
, gets the value returned by
the first method via a predefined Mate variable called lastReturn
. This way you can chain several
method calls if needed.
Example 1-11 shows that the method loadEmployees()
converts XML into an ActionScript Array
object and returns it to Mate, which,
according to the event map, forwards it to the method saveEmployeeList()
for further processing (see
Example 1-12). The name saveEmployeeList()
is a bit misleading, because
this method does not persist data, but rather stores it in memory in an
ArrayCollection
object.
Example 1-11. EmployeeParser.as
package com.cafetownsend.business{ import com.cafetownsend.vos.Employee; public class EmployeeParser { public function loadEmployeesFromXML(employees:XML):Array { var employeeList:Array = new Array(); for each( var thisEmployee:XML in employees..employee ){ var employee:Employee = new Employee(); employee.email = thisEmployee.email; employee.emp_id = thisEmployee.emp_id; employee.firstname = thisEmployee.firstname; employee.lastname = thisEmployee.lastname; employee.startdate = new Date(Date.parse(thisEmployee.startdate)); employeeList.push(employee); } return employeeList; } } }
The EmployeeManager
plays the
role of the model here—it stores employees in the collection employeeList
and information about the
selected/new employee in the variable
employee
.
Example 1-12. The model: EmployeeManager.as
package com.cafetownsend.business{ import com.cafetownsend.vos.Employee; import flash.events.Event; import flash.events.EventDispatcher; import mx.collections.ArrayCollection; public class EmployeeManager extends EventDispatcher { private var _employeeList:ArrayCollection; private var _employee:Employee; [Bindable (event="employeeListChanged")] public function get employeeList():ArrayCollection{ return _employeeList; } [Bindable (event="employeeChanged")] public function get employee():Employee{ return _employee; } public function saveEmpoyeeList(employees:Array):void { _employeeList = new ArrayCollection(employees); dispatchEvent(new Event('employeeListChanged')); } public function selectEmployee(employee:Employee):void { _employee = employee; dispatchEvent(new Event('employeeChanged')); } public function deleteEmployee (employee:Employee) : void { _employeeList.removeItemAt(_employeeList.getItemIndex(employee)); selectEmployee(null); } public function saveEmployee (employee:Employee) : void { var dpIndex : int = -1; for ( var i : uint = 0; i < employeeList.length; i++ ) {// does the the incoming emp_id exist in the list
if ( employeeList[i].emp_id == employee.emp_id ) {// set our ArrayCollection index to that employee position
dpIndex = i; } } if ( dpIndex >= 0 ) {// update the existing employee
(employeeList.getItemAt(dpIndex) as Employee).copyFrom(employee); } else {// add the employee to the ArrayCollection
var tempEmployee:Employee = new Employee(); tempEmployee.copyFrom(employee); employeeList.addItem(tempEmployee); }// clear out the selected employee
selectEmployee(null); } } }
So far, so good. The array of employees will be passed to the
saveEmployee
List()
function and placed for storage in the employeeList
collection. But where’s the link
between the Model and the View?
EmployeeList.mxml, located in the package view, has the fragment shown in Example 1-13.
Example 1-13. Fragment from the View: EmployeeList.mxml
[Bindable] public var employees:ArrayCollection = null; ... <mx:List id="employees_li" dataProvider="{employees}" labelFunction="properName" change="updateEmployee()" width="100%" />
And now let’s take a peek at the content of the second mapping
object, called ModelMap.mxml, shown in Example 1-14. It uses Mate’s PropertyInjector
object, which “injects” the
value into the variable EmployeeList.employee
from EmployeeManager.employeeList
(there is one more
PropertyInjector
, which is irrelevant
for our discussion).
Example 1-14. ModelMap.mxml
<?xml version="1.0" encoding="utf-8"?> <EventMap xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="http://mate.asfusion.com/"> <mx:Script> <![CDATA[ import com.cafetownsend.business.*; import com.cafetownsend.views.*; ]]> </mx:Script> <Injectors target="{EmployeeDetail}" > <PropertyInjector targetKey="selectedEmployee" source="{EmployeeManager}" sourceKey="employee" /> </Injectors> <Injectors target="{EmployeeList}"> <PropertyInjector targetKey="employees" source="{EmployeeManager}" sourceKey="employeeList" /> </Injectors> </EventMap>
If you sense a Dependency Injection design pattern, you’re right.
This pattern really helps you create loosely coupled components. Let’s revisit the code fragment of the view shown in Example 1-13. It’s written “assuming” that some outsider object will populate the variable employees. This code does not reach out for another specific component, demanding, “Give me the data!” It waits until someone injects the data.
And this someone is declared in ModelMap.mxml as follows:
<PropertyInjector targetKey="employees" source="{EmployeeManager}" sourceKey="employeeList" />
At this point, software developers familiar with Java Spring framework should feel at home. It’s the same concept. Objects never reach out for other object’s data—the plumbing is done in third-party declarative components (XML in Spring and MXML in Mate). The benefits are obvious: components don’t depend on one another. Just write the mapping file like ModelMap.mxml and specify the source and target for the data.
Another benefit is simplified testing—if the real data feed is not
ready, create a mock model object and use it in the PropertyInjector
tag. Switching to a real data
model is just a matter of changing a couple of properties in this
injector.
Creators of the Mate version of the Café Townsend application have
decided to use EmployeeParser
and EmployeeManager
objects, but the Mate framework
does not force you to separate parsing or any other business logic from
the model. In this case, the parser could have injected the data directly
to the View without even performing this loop converting XML into an
array.
In the case of Cairngorm, a view that needs some data would reach
out for the model by making a call like ModelLocator.getModelLocator().employeeList
,
which means that the view is tightly coupled with a ModelLocator
object.
In the case of Mate injectors, the view waits to receive employeeList
without making any remote procedure
calls (RPCs).
Mate is a nonintrusive MXML framework that offers flexible
separation of the application views and processing logic. The
application developers are not forced to do all of their plumbing
exclusively via Mate and are free to use standard Flex event processing
along with the EventMap
object
offered by Mate. Because it is tag-based, Flex developers will find it
easy to program with. The learning curves of Mate and Cairngorm are
comparable. Here’s the report card.
The pros are:
Mate is nonintrusive—Mate-specific code can be encapsulated in a handful of objects.
It’s MXML-based and allows you to keep using the Flex event model.
It promotes loose coupling between components by implementing dependency injection.
It’s well documented.
The cons are:
It hasn’t been officially released yet.
It doesn’t support working with Data Management Services offered by LCDS, and because of this you’d need to code this part manually.
As opposed to Cairngorm, using Mate in your application does not require developers to create many additional classes or components just to support the life cycle of the framework itself. This explains why the Mate version of the released Café Townsend SWF is about 10 percent smaller.
Mate promotes loose coupling between components by implementing a Dependency Injection design pattern. But loose coupling comes at a price—all communications in Mate are done via events, which have more overhead compared to direct function calls. Events require additional object instances to be created, as you don’t just call a function on some component, but have to create an instance of some event and dispatch it to that component. The receiving party has to create additional event listeners, which may become a source of memory leaking.
Function calls do not have these issues and offer additional benefit-type checking of arguments and returned values.
Mate also uses singletons, but they do not have to be instantiated
by application developers. Application components are also instantiated
by the framework as per MXML tags included in the EventMap
object, which also performs the role
of a class factory with lazy instantiation—if the event that required an
instance of EmployeeManager
was never
triggered, the instance is not created. A special Boolean
attribute cache on Method
Invoker
and ObjectBuilder
ensures that the instance will
be garbage-collected.
Currently, Mate offers over 30 MXML tags, but this number can be
increased by application developers. For example, by subclassing Mate’s
AbstractServiceInvoker
class, you can
create a new tag that implements a service that’s specific to your
application and can be invoked from EventMap
, the same way other services
can.
If your application uses Flex modules, Mate documentation suggests
that you can place EventMap
objects
in the main application as well as in modules. But as with any framework
that uses global objects (EventMap
in
this case), you can run into conflicts between events defined in the
module’s map and the main application’s map. Of course, if modules are
created to be used with only one application, you can come up with some
naming conventions to ensure that every event has a unique name, but
this may cause issues if you’d like to treat modules as functional black
boxes that can be reused in multiple applications.
Mate does not offer UI controls; it does not include code generators to automate the development process. It does not support automatic data synchronization between the client and the server (LCDS Data Management Service) and would require manual programming in this area.
Mate is the youngest of all frameworks reviewed in this chapter. But even though (at the time of this writing) Mate hasn’t been released yet, it’s well documented.
Get Agile Enterprise Application Development with Flex now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.