Learning Flex – Lesson 11, part II

Advanced Data Grid

The Advanced Data Grid (ADG) is part of the Data Visualization package (not included in the SDK). You can download a trial version of this package from Adobe (it’s on the same page as the SDK download here) but you’ll see a trial watermark across your ADG or charts. To remove this, you’ll need to invest in the professional version of FlexBuilder (or FlashBuilder for V4+). The ADG is larger and potentially more problematic than the regular Data Grid – see this post by Doug McCune for details.

Sorting

The ADG allows for multiple column sorting. The behavior of this feature is determined by the sortExpertMode property. When set to false, clicking the header area of a column makes it the primary sort, additional sort criteria can be set by clicking in the multiple column sort area (the small area on the right of each column). Numbers in this area indicate the order of sort. Resetting the top level sort is accomplished by clicking in the header area of a column.

With the property set to true, the multiple column sort area disappears.  To sort in this mode, click on the first column header and then control click on subsequent headers. The sort order numbers will still appear.

Styling Columns

This is achieved by writing a styling function which should accept two parameters. An Object typed parameter to hold the data for a particular row and an AdvancedDataGridColumn typed parameter which contains information about the column the style is to be applied on.

The function should return an Object which is usually either null, indicating no styling is required or one or more style properties and associated values. An example would be:

public function boldStyle(data:Object, col:AdvancedDataGridColumn):Object{
return {fontWeight:"bold"};
}

This would set all entries of the given column to bold. To specify a style function should be used for a column, set the styleFunction property of the AdvancedDataGridColumn in question to the name of the function you have created.

Styling Rows

To change the style of a particular row (or rows), specify the styleFunction property at the AdvancedDataGrid tag level and code the function to return the style you require based on the row data input.

Styling Cells

To style individual cells, use the styleFunction parameter at the column level and use the data input to determine if the style should be applied.

Grouping Data

The grouping feature allows you to create a grid with an expandable tree to determine what rows of data are visible.  This can be achieved in MXML by using a DataProvider tag with nested grouping elements as shown below:

<mx:AdvancedDataGrid creationComplete="myGroup.refresh()">
<mx:DataProvider>
<mx:GroupingCollection id="myGroup" source="{myData}">
<mx:Grouping>
<mx:GroupingField name="category"/>
</mx:Grouping>
</mx:GroupingCollection>
<mx:DataProvider>
...
</mx:AdvancedDataGrid>

The GroupingField defines the name of the DataProvider‘s attribute the values  should be grouped by. Note the creationComplete call to the refresh method of the GroupingCollection which is required for the grouping to be activated.

This can also be done via creating ActionScript objects and using the creationComplete of the ADG to call the ActionScript setup function.

Display Summary Data

You can only display summary data for data represented by the GroupingCollection class. To do this with MXML, change the GroupingField tag to a tag set with open and closing tags. Add the following tags within it:

<mx:Summaries>
<mx:SummaryRow summaryPlacement="last">
<mx:Fields>
<mx:SummaryField dataField="myField" operation="SUM" label="summary"/>
</mx:Fields>
</mx:SummaryRow>
</mx:Summaries>

The summaryPlacement in SummaryRow defines the position of the summary data within the group and can be first, last or group (which is the row corresponding to the group). You may specify multiple positions if required using a space as a separator.

The dataField in SummaryField defines the column we are summarizing and the operation defines how the summary value is calculated. It can be SUM, MIN, MAX, AVG or COUNT. You may also define your own version and specify it with the summaryFunction property. The label property associates the calculated summary with the column that’s going to display it.

Renderer Providers

Just displaying a summary value may be confusing. To make it more readable, you can also use a renderer provider. Start by defining a custom component (generally you’d base this on Label) and for the text property, provide your message eg “Total Amount is: {data.summary}”. Returning to your ADG, specify the following  (in this case, the custom component has been named SummaryText):

<mx:RendererProvider>
<mx:AdvancedDataGridProvider
dataField="summary"
columnIndex="1"
columnSpan="2"
renderer="SummaryText"/>
<mx:RendererProvider>

The columnIndex specifies what column the renderer is displayed in (zero based index) and columnSpan how many columns it should take up (zero means the whole row).

If you don’t want the document icon displayed in front of each row in a collection, you can remove it by setting the property defaultLeafIcon on the ADG tag to "{null}".

In the same way collections can be defined using ActionScript, a summary can too by creating matching ActionScript objects and setting their properties to match. This would then be called from the creationComplete within the ADG as shown previously.

Learning Flex – Lesson 11, Using DataGrids and Item Renderers

A DataGrid organizes data in a table format with rows and columns that can then be extensively manipulated. This versatility however,  comes at a price in both size and performance.

If you don’t define columns for your DataGrid, it will create them based on the element names in your data. The downside to this is that the order is not guaranteed and using code element names may not result in useful display column names.

Individual columns are defined within an <mx:Columns> tag using <mx:DataGridColumn>.  For each column, you can define headerText (for the column name), dataField (for the element within the dataProvider this column represents) and editable which defines (true or false) if the data can be edited. The DataGrid tag also has an editable attribute which is for the whole grid, individual columns can then be set to false if required. If the dataField itself is a complex object, you can access an element of that object using standard dot notation (object.element)

Manipulating  a DataGrid

The default editing control for a column is textField. It is possible to change this by using the itemEditor and editorDataField attributes. The  editorDataField specifies the attribute of the editor control that will be used to change the value for the cell (for example for a NumericStepper editor, this would probably be value). The following is a list of built in controls you can specify (full package names required):

Button, CheckBox, ComboBox, DateField, Image, label, NumericStepper, Text, TextArea, TextInput.

You may also specify a custom control you have constructed as long as it implements IDropInListItemRenderer. When the grid implements a column with an itemRenderer specified, it creates an instance of the renderer for each row. The implicit data variable is available within the renderer to allow access to the values of the row itself.

DataGridColumn has a labelFunction attribute which can be used to call a function to format data for display. Multiple columns could share the same labelFunction so the function should expect parameters of the data class used by the DataGrid and an object of class DataGridColumn to determine the column used. The function should also return a String.

The attribute variableRowHeight on DataGrid allows it to re-size columns to display the data they contain and could also be used for effects such as “exploding” details from a summary data cell.

Get and Set Functions

Custom class properties can be hidden behind special get and set methods so that other work may be undertaken if a client attempts to read or write to that property.

These functions use the keyword get or set followed by the property name. The property must then be declared private and by convention, is normally declared with a leading underscore. An example would be:

private var _myProp:String;

public function set myProp(propVal:String):void{
_myProp = propVal;
do other work here
}

public function get myProp():String{
do other work here
return _myProp;
}

It puts the lotion on its skin…

If the previous post about wizards & skinning has you itching to try writing your own rather than just taking and modifying someone else’s, you should check outDan Orlando’s Blog specifically the post he wrote for IBM Developerworks on Flex and CSS. It’s a great introduction into styling your app. You should also consider playing with the Adobe Flex Style explorer which allows you to play with styling components graphically and provides you the resulting CSS.

Learning Flex – Lesson 10, Creating Custom Components with ActionScript 3.0

Creating a custom component in ActionScript is very similar to how it’s done using MXML. First choose the class your new class will extend (if any) and add any properties or methods you require. If your component is a displayable container, you will probably want to override the methods createChildren() and updateDisplayList() to define the creation, sizing and positioning of any children.

In the same way you use @Embed to add images in MXML, you can add them to ActionScript using [Embed("path to image")] followed directly below by a variable of type Class which will hold a reference to it. Note that in this case, the path to the image is relative to the location of the component file.

Overriding createChildren()

This method is called automatically at initialization and is used to add child components to a container. The initialization sequence is:

  • the class constructor
  • createChildren()
  • commitProperties()
  • measure()
  • updateDisplayList()

The last four methods are defined in mx.core.UIObject.

The commitProperties() method is used to set properties based on other values that are already set or to explicitly pass in new properties to be set in newly created components. As it’s called after all the children are created, it’s guaranteed that they have been successfully initialized so any complex property work can proceed. This method is also called after invalidateProperties() is called so you can use that as a kind of “dirty” flag and then use commitProperties() to decide based on the change, if you need to do any complex processing to update particular values or not.

The measure() method is used to calculate the the height and width required for the child components.

Within the createChildren() method, we can add components by creating objects of the required class and then using this.addChild() or this.addChildAt() to add them to the component (although they are not displayed at this stage). Just using addChild() will add children in the order they are declared, addChildAt() allows you to specify a zero based index into the display list.

Chrome and Raw Children

Flex containers have two types of display area, the layout area where children are drawn and the chrome which is the border, background, scrollbars etc area. The base class flash.display.DisplayObjectContainer does not distinguish between the two. They are both accessible via getChildAt() and count as part of the numChildren property. The mx.core.Container class (which is the superclass for all Flex containers) overrides these elements, only showing the child components. To gain access to the chrome as well, you need to use the rawChildren property and the associated method, rawChildren.addChild()

Sizing and Positioning

Every component that does not have an explicit or relative size specified must be allocated a size before it can be displayed. Components must be able to provide a recommended size. The method that is used to do this is measure().

When the LayoutManager needs to determine the layout of the application, it asks all the components starting with the most deeply nested to provide an explicit or recommended size. Once this is done, it works from the Application container back down, assigning sizes and moving components into place on the screen.

If the recommended sizes add up to more space than is available, the LayoutManager informs the Application container of the usable width and height and it decides how much space to give it’s immediate children  and where they should be placed. Each of these in turn goes through the process for it’s children and so on down the line.

The method inside each component that is responsible for the sizing and positioning of it’s children is called updateDisplayList().

This process takes place each time the application re-sizes so it makes sense not to get too carried away with large numbers of containers.

The measure() method

Each implementation of measure() is different as each has different constraints in layout. All however must provide measuredHeight, measuredWidth, measuredMinHeight and measuredMinWidth.

The first two are the ideal size requested by this component when there’s enough room available. The min versions are the smallest size the component needs to display correctly. The flex layout containers will never size a component smaller than these values. A custom component is free to ignore these values if it overrides updateDisplayList().

The updateDisplayList() method

This method requires two arguments; unscaledWidth and unscaledHeight both of type Number.  To ensure that the custom container itself is correctly displayed, you should call the superclass version within your override method (super.updateDisplayList() ) with the same parameters. These parameters provide the exact pixel width and height available. The size of the child components can be set using their setActualSize() methods passing in a width and height and their position using the move() method with an x and y of type Number (0,0) being top left.

Learning Flex – Lesson 9, Using Custom Events

To broadcast an event from a component, you use the dispatchEvent() method which is declared in the flash.events.EventDispatcher class. This is a class that UIComponent inherits from and is therefore available to all components.

The dispatchEvent method takes one argument which is the event object to be broadcast. When an event is dispatched, anything listening for it is notified.

Declaring Events for a Component

Every component must declare the events it can dispatch. Components that are subclasses of other components can also dispatch events that their parents have declared. Events are declared within metadata tags. In MXML, an event declaration looks like:

<mx:Metadata>
[Event(name="somethingHappened" type="flash.events.Event")]
</mx:Metadata>

The event type provided here is the default so it could be omitted. The metadata tag declares that it’s child elements are metadata and any metadata declarations use square brackets to define their bounds and parentheses to define attributes.

In addition to simple notifications, sometimes data also needs to be passed. The default event class does not support this but you can create a custom subclass to get around this limitation. (As dispatchEvent requires an Event instance, any custom event needs to be a subclass of Event.) You can add any extra properties or methods you require to your event class but you are required to override the clone() method. Overriding a method allows you to change the behavior provided by the superclass. When you override a method you need to match the signature of the superclass method and provide the override keyword. The clone method returns a new copy of the event object with the same values. Typically, you will define clone to return an instance of your new event class.

Event Flow and Bubbling

If the target of an event is not a visible element on the screen (eg HTTPService ) FlashPlayer can dispatch the event directly to the target. Otherwise, the event travels through the container hierarchy  from the outermost Application container to the target component and then back up.

As all components are descendants of event.EventDispatcher, they can all listen for events using the addEventListener() method. They will only be notified however, if they are part of the event flow. This flow is divided into three parts:

  • The Capture phase – moving from the base container to the one containing the target for the event.
  • The Target phase – solely the target node itself.
  • The Bubbling phase – the return trip to the base container.

You cannot intercept an event in the capture phase, only in the bubbling phase.

All instances of the Event class have a bubbles property (Boolean) that indicates if that event object will take part in the bubbling phase of the flow. This property is set to false by default for newly created events (but some built in events such as click have it set true).  The flash.events.Event class takes an optional second argument to it’s constructor to indicate if the event should bubble. If bubbling is set true, ancestor containers of the target can listen for the event. Remember that even if the target element does not listen for an event leaving it for an ancestor via bubbling, if it’s a custom event, the target must declare it via a metadata tag.

Follow

Get every new post delivered to your Inbox.