by Digital Primates
Digital Primates
by Dave Flatley
Dave Flatley

Created

28 June 2010

Requirements

   
Prerequisite knowledge
Some knowledge of ActionScript 3, Flex 4, Flash Builder, and object-oriented programming concepts will be helpful. A basic understanding of BlazeDS, Java, and MySQL will also be helpful in understanding the server-side functionality.
User level
Intermediate
 
Required 
Flash Builder (Download trial)
Sample files
This article is Part 3 of a series of articles and tutorials centered on a sales dashboard application built using Flex and the Adobe Flash Platform. The individual articles in the series are designed to stand on their own, so you can read all of them or just the ones that interest you, in whatever order you like.
 
This part first reviews the basic layout of the application's user interface, and then explains how many of the main building blocks of the application work:
 
For an overview of the application, see Part 1: Overview. For a more in-depth look at the code, see Part 4: Exploring the code in Flash Builder.
 

 
The UI layout

The DigiPri Widgets sales dashboard is comprised of a header at the top of the application, which contains the logo and navigation tabs, and a central reports area below it. When the application starts, the Regional Sales tab is selected, and the corresponding reports are shown below (see Figure 1).
 
The DigiPri Widgets sales dashboard
The report area consists of a TabBar and a ViewStack component, which can display any one of the report groups at a time. The header area contains the tabs that enable the user to select the views shown in the report area. Report groups can be configured as customizable. By default, the My Dashboard group is customizable, so it includes a report list, from which  the user may add reports to the view. This is shown as a drop-down list on the top right-hand side of the reports area (see Figure 2).
 
The list of reports that can be added to a customized view
All the text shown in the application is stored in resource bundles (or stored in the database itself), so the application can be more easily translated to other languages.
 

 
Server-side connectivity

The DigiPri Widgets dashboard application makes use of an ultra-lightweight services framework, which embraces the use of the Responder pattern, as it is implemented in Flex with the IResponder interface. This pattern is the basis for all asynchronous data access in the application.
 
The conduit between the Flex application and the services layer is the command, which implements the IResponder interface. Any data needed by a command is passed to its constructor upon instantiation. The execute method of the command is called with no arguments to tell the command to request the specified data. The IResponder interface requires that the implementing classes have public result and fault methods, which will be called accordingly based on the response from the server.
 
In the execute method of the command, a reference to the service is retrieved through a call to the ServiceFactory. For example, the command that fetches client deals calls the service factory's getClientDealService method, and passes itself as a responder:
 
public override function execute() : void { serviceFactory.getClientDealService( this ).getAllClientDeals(); }
Internal to the ServiceFactory, a RemoteObject instance is created, and given a reference to the destination that matches the destination in the server's remoting-conf.xml file. The newly created RemoteObject and the responder (the command) are passed to the constructor of the ClientDealServiceRemote class.
 
 
RPC services
The ClientDealServiceRemote ActionScript class has methods that mirror the methods of the ClientDealServiceImpl Java class on the server. Each of these methods is capable of calling the correspondingly named method on the server. For example, a call to getAllClientDeals() Flex in turn uses the remote object to call that method (implemented in Java) on the server:
 
public function getAllClientDeals() : AsyncToken { var abstractOp : AbstractOperation = service.getOperation( "getAllClientDeals" ); var token : AsyncToken = abstractOp.send(); token.addResponder( resp ); return token; }
In Flex, a call to a method on a remote object returns an instance of the AsyncToken class, and a responder can be added to the AsyncToken, so that any responses will be heard by the result or fault method of that responder. In this application, the command, which initiated the call, is that responder.
 
 
XML services
Server-side XML configuration files are also loaded through the use of commands, the ServiceFactory, and the remote service implementation. The commands work in the same way as the commands for invoking remote methods, calling the ServiceFactory, which finds the URL for the XML file to load. The ServiceFactory creates an instance of the HTTPService class for calling the XML file, specifies the URL of the service, and passes the HTTPService and Responder (the command) to a remote implementation. The methods in the remote implementation call the send method of the HTTPService, which instructs the service to load the specified file, and applies the responder to the returned token.
 
The commands that load the XML files handle all of the XML parsing in their result method. An example of this functionality can be seen in the GetNavItems ActionScript class. The execute() method invokes the getNavService() of the ServiceFactory class, which makes the call to XMLDataLoaderRemote() and returns an instance of the IXMLHttpLoader class. The result() method is the responder for the call and handles the XML parsing:
 
public override function result( data : Object ) : void { var appModel : ApplicationModel = ApplicationModel.getInstance(); var navItems : ArrayCollection = new ArrayCollection(); for each( var item : XML in data.result.report ) { var reportItem : ReportItem = appModel.getReportDataById( item.@id ); navItems.addItem( reportItem ); } appModel.navItems = navItems; }

 
Configuring the dashboard application

The DigiPri Widgets sales dashboard uses four XML configuration files: nav.xml reports.xml, reportGroups.xml, and services.xml. The services.xml file contains nodes listing the URLs of the XML files to be loaded by the application. The other XML files define reports and their relationships to the application.
 
 
How reports are defined
As described in the previous section, commands are used to retrieve the data from the XML configuration models. The result methods of the command parse that data into strongly typed beans, which are stored in the ApplicationModel. The ApplicationModel can be accessed as a singleton, allowing consumers of this data to retrieve it whenever needed.
 
This approach makes the sales dashboard a dynamic application. By simply defining new reports in the associated XML file, a new report is created that can be instantiated in the application. With a few simple lines of XML, new reports can be defined for use in the application, and since these XML files are not embedded within the application, there is no need to recompile to add the new reports. Likewise, new report groups can be created by editing XML, without having to rebuild the application itself.
 
 
Configuring reports in reports.xml
The reports.xml file defines each of the individual reports that can be used in the application. Each report node represents a single report and is parsed into a ReportItem bean.
 
For example, reports.xml includes the following node:
 
<report id="201" name="salesForecastsChart" type="components.reports.ColumnChartReportView" command="commands.GetAllSalesForecastsCommand" . . . </report>
The id property is a unique identifier for each report and is the property that the application uses to specify which report to instantiate. The type property specifies the class of report to instantiate; for example, to use a column chart, the type value would be components.reports.ColumnChartReportView. This also contributes to the dynamic capabilities of the application, because it enables developers to add a report of a new class definition  to change how data is represented by simply changing that property.
 
Among the other optional elements that can be specified for each report in the reports.xml file are the series and axis details for each report. This makes it easy to change existing reports from one type of chart to another. For example, a ColumnChart can easily become a BarChart by changing <cartesianSeries type="mx.charts.series.ColumnSeries"... to <cartesianSeries type="mx.charts.series.BarSeries."...
 
The data used to populate each chart within the report is specified by providing the name of a command class to use to retrieve that data. This command will be called automatically when the report is instantiated.
 
 
Configuring report groups in reportgroups.xml
Each reportGroup node in the reportgroups.xml file specifies one or more report IDs to be associated with that group. The file is parsed and made into an array of ReportGroup beans, which is used to create the views for the ViewStack.
 
Here is the default reportgroups.xml file:
 
<?xml version="1.0" encoding="UTF-8"?> <reportGroups> <reportGroup id="101" label="Regional Sales"> <report id="221" /> <report id="207" /> <report id="222" /> <report id="205" /> <report id="220" /> </reportGroup> <reportGroup id="102" label="Client Deals" rearrangeable="true"> <report id="222" /> <report id="205" /> <report id="215" /> <report id="216" /> <report id="217" /> <report id="218" /> </reportGroup> <reportGroup id="103" label="My Dashboard" rearrangeable="true" addRemoveEnabled="true"> </reportGroup> </reportGroups>
In addition to containing the IDs for the reports to be contained in each report group (and therefore view), each of these XML nodes and associated beans may contain two additional properties for the view: addRemoveEnabled and rearrangeable . These properties are used in the ExtendedDragDropTileGroup container to determine if reports can be added or removed from the view or if they can be moved around.
 
 
Nav.xml
The navigation XML file contains the ID of reports to be displayed in the ReportsDrawer—that is, the list of reports that can be added to customizable report groups (see Figure 2). The bean created by parsing this data is used as the data provider for the ReportsDrawer. By separating the list of reports that can be added from the list of all available reports, developers can set up two general classes of reports: those that can be seen only in specific report groups, and those that can be added to the application in a customizable view.
 

 
The report area

The container that tiles the report components and lays them out accordingly is a custom-built component named ExtendedDragDropTileGroup (which is covered in detail in The ExtendedDragDropTileGroup container). The properties set on the container from the ReportGroup bean control what the user is allowed to do with the reports. If the rearrangeable property is true , then the user can reposition the report components within the container. (The application will provide  visual feedback to show where the chart currently will be placed relative to the other charts.)
 
 
Reports
The reports are designed to facilitate access to the information at a glance, as well as the ability to focus on a specific report for more clarity.
 
To see a larger view of each report, the user can maximize it. Reports can also optionally be configured to allow users to drill down on data points within them, thus narrowing the scope and focusing on a specific aspect of the chart. As an example, a user can drill down on the sales forecast for a year, and see the forecast for individual months. The user can then return to the yearly forecast chart by clicking on the back button.
 
 
Charts
The charts in the component are created by passing in a ReportItem bean. As described earlier, this bean is created by parsing report nodes from the reports.xml file. Whenever the reportBean property is changed, invalidateProperties() is called, which ultimately results in a call to the createNewChart() method.
 
The createNewChart method in the DrillDownReport class removes any existing chart from the component, and then calls getDefinitionByName with the type property of the ReportItem bean to get the class implementation of the chart. A new instance of this class, a chart implementation, is created and the ReportItem is passed to it to instantiate it. The chart object is then added to the component and set as the currently selected view in report's the ViewStack.
 
 
Tabular view
If the report has both a tabular column as well as a reportSeries defined, the report will be instantiated with a ViewStack that contains both a chart and a data grid, and buttons to allow the user to toggle between the two views of the data (see Figure 3).
 
A report with a chart, a data grid, and buttons to switch between the two
The DrillDownReport component displays the two buttons for the tabular view toggle. Clicking either button selects that chart implementation in the ViewStack and displays it.
 
The tabular view is a tabular representation of the top-level data in the component.
 
 
Drill down
The drill-down feature in DrillDownReport takes advantage of the reportBean changed event to drill down to the next chart in the series. Setting the reportBean property in the component triggers the creation of the chart component(s). In this case, when a user clicks one of the hit points on the chart, the current chart's drillDown property is passed into the command to retrieve the report associated with the ID of the hit point. The getDefinitionByName method is called and the new chart implementation is instantiated. The new chart is passed as the new report, which consequently invokes the createNewChart method.
 
 
Drill-down history
The back button enables the user to return to a previous chart after drilling down. When no history is available, the button is disabled. Once a user drills down into a chart, the previous chart data is stored in an array, and the back button is enabled. Clicking this button restores the previous chart's data, thereby returning the user a level up from the current chart. The history is maintained regardless of whether the chart is in a normal or maximized state.
 

 
The DragDropTileGroup container

To allow the user to freely drag and drop the reports within the application, additional functionality above and beyond the layout of the TileGroup was required. As such, the TileGroup component was subclassed to create DragDropTileGroup, which contains logic to allow users to reorder reports via drag and drop operations.
 
 
Dragging within the container
The DragDropTileGroup container allows a child to be repositioned after checking to see if dragging is enabled for that container. When the child is dropped at a specific point in the container, the children are reindexed.
 
 
The findIndex method
The findIndex method is used to figure out the new index of the dragged child. This method is passed the current location of the mouse relative to the content pane of the container.
 
The method assigns bounded areas to each tile (and therefore each index) based on the calculated maximum number of rows and columns given the size of the container and its tiles. Using the X and Y positions of the mouse pointer, findIndex then calculates the associated row and column position and the index is found. As a visual representation of the dragging operation, a glowing outlined rectangle of the same size as the component being dragged is created in the showFeedback method that calls drawFeedback. This method then either adds the child (or sets the index of the child) at the index returned by the findIndex method. The effect can be changed simply by overriding the drawFeedback method. This also applies to the dragProxy with the createDragProxy method.
 
 
Handling child mouse events
The application must be able to discern a drag operation from a simple mouse click. On the mouse down event, listeners for the mouse up and mouse move events are added to the child. When the user depresses the mouse button without a move of the mouse, the application assumes a mouse  click occurred. A mouse move, on the other hand, before releasing the button, is considered to be a drag operation.
 
Note, however, that these events are ignored in certain areas because mouse clicks and mouse drags on a child are only acted on when they occur on the chrome of the child.
 

 
The ExtendedDragDropTileGroup container

The DragDropTileGroup is a generic component that enables users to perform drag and drop reordering of children of any TileGroup. The My Dashboard report group in the DigiPri Widgets sales dashboard needed additional functionality including support for maximizing reports, as well as adding and removing reports. This functionality was implemented in a new subclass, ExtendedDragDropTileGroup.
 
 
Rearrangeable flag
The rearrangeable flag is set from the ReportGroup bean when the container is created. This flag enables or disables the dragging of children within the container. If this property is set to true in reportGroups.xml, an arrow image is visible in the chrome, and hovering the mouse over the chrome of one of the children will display a tooltip, indicating that the component can be dragged. If, however, the property is not set, the  tooltip and arrow image will not appear and the drag will not register on the component.
 
The ExtendedDragDropTileGroup uses the rearrangeable flag specifically to determine if a child object can be repositioned on screen. The actual drag functionality is handled in the DragDropTileGroup class.
 
By default, in the DigiPri Widgets sales dashboard the Client Deals report group is rearrangeable.
 
 
AddRemoveEnabled flag
The addRemoveEnabled flag, much like the rearrangeable flag, is read from the ReportGroup bean when this container is instantiated. The addRemoveEnabled flag specifies whether users can add and remove reports from the reports area (that is, the container).
 
If the flag is set to true , then the close button on non-maximized reports is enabled. This allows the reports to be removed from the container, and thus, the view. In addition, when set, this flag results in the ReportsDrawer component being added to the ViewStack (and the view).
 
 
Maximizing
Upon receipt of a reportViewEvent event, the ExtendedDragDropTileGroup resizes all of its children for a 3D zoom effect, so the report that generated the event is maximized. The other child elements in the view are moved out of the way during the effect and have their alpha properties set lower to fade them out. The gap properties of the container also increase horizontally and vertically to enhance the 3D effect.
 
In its maximized state, the handler method for the report's maximize button will dispatch a ReportViewEvent.MINIMIZE event telling the application to return its original view and minimize the chosen report. The child elements of the ExtendedDragDropTileGroup will return to their original coordinates and the gaps between them will resize to normal.
 
 
setupAndPlayMoveEffect
The setupAndPlayMoveEffect method performs the transitions that occur when the mouse is moved around in the container during a drag.
 
The move effect is called whenever there is a change to the index of the mouse location. A visual representation of the location relative to the children in the container is displayed. When the location of this representation changes, the effect plays to show the respective change. As a result, the you will see instant visual feedback of the reports switching views while you drag a given report to a new position in the tile layout.
 

 
Where to go from here

Part 4 of this series shows you how to set up the DigiPri Widgets sales dashboard sample application in Flash Builder 4, and provides a more in-depth look at the code. If you want to set up a full development environment locally, you'll also want to read Part 2, which explains how to set up the server-side software.