tips & tricks
Tips & Tricks : AdvancedDataGrid – grouping & summary rows
Jun 14th
This is the first among the series of articles that I intend to write about the new AdvancedDataGrid control of Flex 3.0 (moxie). You would require Flex 3 beta 1 to compile and run this example. If you dont have moxie yet, download it here
Displaying grouped (hierarchical) data & summary rows in AdvancedDataGrid
One of the key features of the new AdvancedDataGrid (will be referred to as ADG from hereafter) is its inbuilt capablility to display hierarchical data. This is made possible through the new set of collections classes that have been added. The following example helps you learn two things:
- How to create a grouped data from the flat data that is supplied to ADG
- Create summary rows based on the data.
Lets now look at the example
The example in this article has an ADG which display the quarterly revenue details of companies and the number of licences sold by each one of them stored as an XMLList. This XMLList is supplied as the dataProvider for the ADG. (This is all similar to what you would do in the DataGrid).
Now, Lets try and group this data based on the field quarter. Lets look at the code that will let us do that
//Make a grouping collection
var mygroup:GroupingCollection=new GroupingCollection();
//Set the source to the array collection (this case dataProvider)
mygroup.source = colgrouped_adg.dataProvider;
//Create a new grouping
var group:Grouping = new Grouping();
//Group on Quarter
var gf:GroupingField = new GroupingField(”Quarter”);
//Set the fields to the grouping
group.fields = [gf];
//Finally, Set the group to the grouping property of groupedCollection
mygroup.grouping = group;
//Refresh the group
mygroup.refresh();
//Set the dataProvider to the grouping collection
colgrouped_adg.dataProvider = mygroup
//Call validateNow() to redraw the dg.
colgrouped_adg.validateNow()
The steps are as follows:
- create a grouping collection
- set the source to a flat collection (this case the ADG DataProvider)
- create an instance of grouping
- create a grouping field and pass the field that you want the data to be grouped on
- set the fields property of the grouping to the groupField
- set the grouping property of the grouping collection to grouping instance
- refresh the grouping collection
- set the dataProvider of ADG to the grouping collection
- call validateNow() on ADG to update the display
Now, Lets write some code to calculate the summary of the total number of licences sold by *ALL* companies. Here we go!
var sr:SummaryRow = new SummaryRow();
//Set the summaryObjectFunction
//This function tells ADG to add a summary row in addition to the existing data
sr.summaryObjectFunction = objFunc;
//Set the summary field. The field on which the summary will be calculated.
var sf:SummaryField = new SummaryField(”Licenses”);
//Summary Function – the function that calculates the summary row display
sf.summaryFunction = func;
//set the summary rows fields property to summaryField
sr.fields = [sf];
//Place the summary row (Last means data First, summary Last!)
sr.summaryPlacement = “last”;
//Group on Quarter
var gf:GroupingField = new GroupingField(”Quarter”);
//set the summary row(s) to groupingField
gf.summaries=[sr]
Thats fairly straight forward. Here are the steps you need to follow to get a summary row in your ADG.
- Create a summary row instance
- Set the summaryObjectFunction to include a summary in your ADG data
- Create a summary field, the field on to which the summary calculation formula needs to be applied.
- Write a summary function which contains the logic to calculate the summary
- set the summaryField to the fields propety of summary row
- Define summaryPlacement “First / Last” First will put summary before data, last will add it after the data
- Finally set the summary rows to the groupingField.
The SummaryObjectFunction looks like this: (This method returns a SummaryObject)
private function objFunc():SummaryObject
{
var obj:SummaryObject = new SummaryObject();
obj.summary = true;
return obj;
}
Finally, the summary function. summaryFunction accepts 3 parameters (its a callback, you may never have to pass params to it, but be sure that you write the signature correctly) an iterator,a field and an operation. (operations include build-in methods like ADD).
private function func(itr:IViewCursor,field:String, str:String=null):Object
{
var sum:Number=0
while(!itr.afterLast)
{
//trace(itr.current)
var value:Number = Number(itr.current.Licenses);
if(!isNaN(value))
{
sum+= value
}
itr.moveNext()
}
return sum
}
Thats it! We are done and what we get will look like this ![]()
I have included the sample application as well as full source code for this example. Do take a look at it. I hope this one was useful for all those who are exploring the new ADG. Do look out for more tutorials on this topic.
Do check out my Simple Gantt chart built on AdvancedDataGrid
Tips & Tricks – adding a combobox to a datagrid header as headerRenderer
Jun 7th
I am sure many of you would have tried dropping a ComboBox as a headerRenderer to a DataGrid. Well, if you try to directly drop in a ComboBox you will get an RTE and the ComboBox just doesn’t work! I got this request from one of the flex users here that they need to drop a ComboBox on a datagridcolumn to provide a UI for filtering the data in the datagrid.
After looking at the code for a while, I figured out that the set data method of the combobox checks if it is dropped in as an itemRenderer and what’s more, it sets the data irrespective of whether the value recieved in the set data is a datagrid column or not. In the case of dropping the ComboBox, the data setter sets the data to the DataGridColumn and when the program progresses it throws an RTE which says it couldnt find the property name on the headerRenderer instance.
The solution:
I subclassed the ComboBox control to make MyComboBox and wrote an override for the data Setter. I just added the following type check before the data setter in the super is called
if(value is DataGridColumn)
{
//do not call super; that is it!!
}
else
{
super.data = value
}
Thats pretty much it! The ComboBox will now correctly display in the header. I have written a simple example that has a ComboBox on the Datagrid column header which can be used to filter data. Note: I have turned off sortable columns and draggable columns in this example.
Disclaimer: This sample contains a small example of the concept, the application in itself has not been tested for bugs. People copy pasting the whole code and urged to exercise Download Source
Tips & Tricks – ItemEditors – III
Jun 4th
In the earlier articles (I, II) , we saw how to use itemEditors on a flex datagrid and also how to validate data that is entered in the itemEditor. Lets take the example in the previous article and improve it a little further.Lets look at the doValidation() method again: We were comparing if the entered value is greater than the upperBound and throw an error message as an Alert in the example. Flex comes with some really cool built in validators that provide a much more usable user experience than throwing an Alert box to the user when an error has occured. In this example, lets try to incorporate a NumberValidator to do the same validation that we were doing earlier using the if conditional.
The code
Well, things havent changed too much from our earlier example, except for the following method – doValidation()
//Validate the data entered / stepped in the itemEditor
private function doValidation(event:DataGridEvent):void
{//Storing the selection made in the itemEditor to a variable
var selectedData:Object = CustomNumericStepper(datagrid.itemEditorInstance).value
//Store the upper bound to compare
var upperBound:Number = event.target.selectedItem.value;
//create a number validator instance. This can be extended to any validators
nv = new NumberValidator()
//specify the source – in this case the itemEditor instance
nv.source = CustomNumericStepper(datagrid.itemEditorInstance);
//Set the property to validate against. NOTE: always set this, or the validator will throw RTE
nv.property = “value”
//Set the maxvalue to the upperBound
nv.maxValue = upperBound;
//Call validate and take the return value. NOTE: validate() method sends two events valid / invalid based on the validation.
//val contains an event reference
var val:* = nv.validate(selectedData);
//check if validator returned invalid.
if(val.type == “invalid”)
{
//Prevent default action.
event.preventDefault();
}}
So, whats different here? We are creating a new instance of a NumberValidator (nv) here. Then the source property of the validator is assigned to the control, whose data we are interested in validating. In our case, its the itemEditorInstance – the custom numeric stepper. Important point to note here – once you have set the source property, always set the “property” property (this is NOT redundant typing, there is a property called property for a validator
) of the validator to the property that we are validating against. Set the maxValue of the validator to the upperBound and call validate(value:Object) method on the validator. The validate() method accepts the value that we are validating.
When you run the example, you can see that the validator puts a red box around the wrongly entered field and when you move the mouse over, you can see that the error message is displayed as a red tooltip. Isn’t that cool? Do look up the documentation for the validators, there are many more properties that can be used to customize the validation parameters, error messages etc.
Tips & Tricks – ItemEditors – II
May 10th
We saw in the earlier article about using itemEditors in a flex datagrid. Now, Lets dive a little deeper into doing some more complex things with itemEditors in a flex datagrid. Before we get started, let us get introduced to the events that are most critical when we are performing an edit operation in a flex datagrid.
(Description of each of these events are available in the livedocs, readers are requested to check up)
itemEditBegin – Fired when you click on an editable cell of the datagrid (also when the editedItemPosition is set on an editable DataGrid)
itemEditBeginning - Fired when the mouse is released
itemEditEnd - Fired when the edit is committed / editor is destroyed there by terminating the edit session.
Now, Lets get back to our scenario. Let us take a simple example – We have a datagrid with a numeric stepper used as an itemEditor similar to the example stated in the part I of this article. Now, let us add an additional functionality of adding a validation to the data that gets entered in the itemEditor. Take a look at the example attached along with this post to see this in action.
Let us dig into the code now:
<mx:DataGrid editable=”true” id=”datagrid” x=”105″ y=”85″ height=”176″ width=”317″ dataProvider=”{arr}” itemEditEnd=”doValidation(event)”>
<mx:columns>
<mx:DataGridColumn headerText=”Data” dataField=”disp” editable=”false”/>
<mx:DataGridColumn headerText=”Available” dataField=”value” editable=”false”/>
<!– itemEditor is set to our class CustomNumericStepper. Always remember to set the editorDataField to the dataField on which the operation is performed–>
<mx:DataGridColumn headerText=”Order Qty” dataField=”order” itemEditor=”CustomNumericStepper” editorDataField=”value”/></mx:columns>
</mx:DataGrid>
when the itemEditEnd event of the DataGrid fires, we invoke a method call doValidation() which performs the validation of rhe data entered in the editor. So,whats the logic for validation? We validate if the new value that is getting entered in the column is less than or equal to the value in the available column. on fail, we will throw an alert indicating the error to the user. showError() method is used to achieve this.
//Validate the data entered / stepped in the itemEditor
private function doValidation(event:DataGridEvent):void
{//Storing the selection made in the itemEditor to a variable
var selectedData:Object = CustomNumericStepper(datagrid.itemEditorInstance).value
//Store the upper bound to compare
var upperBound:Number = event.target.selectedItem.value;
//Compare if the entered value is > upper bound
if(selectedData > upperBound)
{
//Destroy the instance of the itemEditor by calling destroyEditor()
datagrid.destroyItemEditor()
//Call preventDefault() so that the new value is NOT committed
event.preventDefault();
//Invoke a method to display an error message
showError(upperBound);
}
}
//Display the error message
private function showError(value:Number):void
{
//Show the error message in an Alert Box
Alert.show(”Please Enter a value less than “+value)
}
So, whats doValidation() doing?
doValidation() receives the datagrid event as its parameter. The first step is to store the Object corresponding the edited item into a variable – selectedData
Now we find out what is the upper bound to validate against. This is the value in the available column for the current record that we are editing. Now we store that value to another variable – upperBound
Lets check if selectedData > uppedBound. On this condition being satisfied, we do the following steps.
Destroy the editor instance – destroyItemEditor() is called. This is to close the editor
Prevent committing of the data – event.preventDefault() is called. This prevents the default action that a particular event triggers. In this case, the committing of the data is prevented.
Finally, showError() method is called to alert the error message to the user.
There we go! We have a simple working sample of applying a validation to an itemEditor of a flex datagrid up and running.
Tips & Tricks : itemEditors – I
Apr 9th
Alex has written two wonderful posts in his blog about working with itemRenderers. Along with itemRenderers another extremely useful feature in flex is itemEditors. itemEditors provide a highly efficient and clean way of providing custom editors for editing data in components like List and DataGrid.
Take this example: You have a Datagrid with two columns (Product, Available and Order Qty). Avaliable & Order Qty columns contain numeric data. You want to allow the user to make an order by selecting the Order Qty column by entering a value for the number of pieces he wants to order in the column.
Option 1: You can simply pop the default itemEditor of a Datagrid / List, which is a TextInput where the user can key in the number.
Option 2: You can pop a custom itemEditor.In this case, using a NumericStepper makes a lot of sense because it lets the user increment / decrement a value and also allow him to type in just like in a textinput.
I have used Option 2, to implement the solution.
So, Why itemEditors and not itemRenderers?
You can set a NumericStepper as an itemRenderer and set the rendererIsEditor property to true. But in that case the renderers are pre-created, they are *always* displayed evcen when you are not editing it and lastly, you have to write your own logic to persist the data after editing the cell. (That surely seems like more work than using itemEditors for our current problem.)
Lets Get back to itemEditors - Take a look at the example attached along with this post which uses a NumericStepper as its itemEditor. You can see that the NumericStepper is popped when you click on the cell in the Order Qty column and the editor is destroyed when you click on anywhere else / change the focus. Also note that the value that you entered / stepped is now committed and the DataGrid display is updated.
Lets do a little digging into the code
As you can see, there is *very little* code that is written to implement this solution.
Step 1 – Set up the data: (see inline code comments)
//DataGrid event, fired when the itemEditing is performed
import mx.events.DataGridEvent;
//Used for populating the datagrid
import mx.collections.ArrayCollection;
//Used to display the Alert
import mx.controls.Alert;
//ArrayCollection that will hold the data
[Bindable]private var arr:ArrayCollection
private function populateData():void
{
//Create an array with dummy product names
var prodarray:Array = ['Apple','Orange','Peach','Grapes','Guava','Mango','Raspberry','Strawberry','Watermelon','Passionfruit']
//Create an arraycollection
arr = new ArrayCollection()
//Loop to create 10 records
for(var i:int=0;i<10;i++)
{
//create a random number
var rnd:Number = Math.round(Math.random()*100);
//Create an object to insert into the array collection
var obj:Object = {value:rnd, disp:prodarray[i],order:0}
//Add the object to array collection
arr.addItem(obj)
}}
Step 2: Create the Datagrid and set up the itemEditor.
The following lines of code create an instance of Datagrid with 3 columns, and we set the itemEditor and editorDataField properties to the third column. (See inline comments)
One important point to be remembered here. The DataGrid or List always looks for the default property text on the itemEditors unless you specify the editorDataField property to the appropriate value. In this case editorDataField will be value since we will be altering the value property when we increment / decrement a NumericStepper. If we were to use a TextArea, the property should be set to text. If you forget to set this property, then prepare yourself for some erratic behavior
<!– DataGrid Instance, dataProvider is bound to ‘arr’- the arraycollection. –>
<mx:DataGrid editable=”true” id=”datagrid” x=”56″ y=”104″ height=”176″ width=”317″ dataProvider=”{arr}”>
<mx:columns>
<mx:DataGridColumn headerText=”Product” dataField=”disp”/>
<mx:DataGridColumn headerText=”Available” dataField=”value”/>
<!– itemEditor is set to our class CustomNumericStepper. Always remember to set the editorDataField to the dataField on which the operation is performed–>
<mx:DataGridColumn headerText=”Order Qty” dataField=”order” itemEditor=”CustomNumericStepper” editorDataField=”value” />
</mx:columns>
</mx:DataGrid>
Step 3: Create the custom itemEditor
I have created a CustomNumericStepper class which extends from NumericStepper. (CustomNumericStepper.as). This class contains just two properties, the maximum and the stepSize. The maximum is set to 150, assuming that we will *not* be stocking more than 150 fruits of each type (well, we will change this assumption soon, accommodating a more realistic calculation, but lets keep it this way for the sake of simplicity
)
package
{
import mx.controls.NumericStepper;public class CustomNumericStepper extends NumericStepper
{
public function CustomNumericStepper():void
{
super();
//Set the properties
stepSize=1;
maximum=150;
}
}
}
Note: I chose to write this as a .as file, you could very well use an MXML component to achieve the same results.
View the Sample | Download Source
and right click on the application to view / download the full source.
Coming up next: Doing more with itemEditors – applying formatters & validators to itemEditors
drag & drop example
Mar 15th
This is in response for a query on flexcoders by dave. This example demonstrates drag and drop an image from a list to a canvas where the image is added at the position where the mouse is released on the canvas. This also provides the capability to drag and position the dropped images anywhere in the canvas. Making this example I found this rather strange thing, when you are creating a custom dragImage, for an image control, it requires that you call setActualSize() on the dragImage to make it display correctly. surprisingly, thats not the case when the images are embedded, it works fine even without calling setActualSize().
Usage:
Drag and drop from the list of image names to the grey colored area which is a canvas. Observe that the images are drawn at the location where you released the mouse. Drop a few images, now re-position them in the canvas by dragging and dropping them around.
Enjoy!
Download Source (zip)
FormattedStepper – NumericStepper with formatting support
Mar 14th
Saw a post on flexcoders yesterday regarding some one wanting formatter support on NumericStepper. Played around with NumericStepper code base for around an hour yesterday night and this is what I got – <FormattedStepper>, an extended control from <mx:NumericStepper> with a formatter property which can be set to NumberFormatter or CurrencyFormatter. The control can be used as a regular NumericStepper too. I have attached a sample application and full source code also. This is version 0.0.1, may have a lot of bugs, but for starters, it works!
Sample Usage:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:xm="com.*" backgroundGradientColors="[#000000, #000000]"> <mx:CurrencyFormatter id="formatter" currencySymbol="$" alignSymbol="left"/>
<xm:FormattedStepper id="fs" formatter="{formatter}" minimum="1000" maximum="2000" stepSize="2"/>
</mx:Application>
Sample Application | Download Source (zip)
Small textarea enhancement
Mar 13th
I met Avinash, Kislay and Amrit from sungard last sunday at the flex users meet organized by Mrinal and Abdul. John Koch, the APAC develper relations manager of Adobe was the main guest. Raghu, I and Sameer joined in from the flex team. Amidst a conversation, Amrit pointed out that he couldnt drop a text into a specific charecter location while doing a drag drop to a text area from a list or a similar source. The dropped text gets appended to the beginning or the end of the text. Seemed to me that some simple tricks would solve this issue.
There is a UITextField instance inside the TextArea control which is protected, Hence I had to extend the TextArea control to actually prototype this capability. So, here is a hacky first draft of the drop-at-cursor-location capability into a TextArea. Currently doesnt work with htmlText, that will come soon though. Thanks Sameer for the drag and drop tips.
How does it work?
Simple! After subclassing TextArea, on mouseUp, call the getCharIndexAtPoint() method of UITextField to find out the charecter index at the cursor position. Now, appending a text at that position is very simple.
Sample Application | Dowload Source
ForthComing: Cursor locations to update on keyBoard navigation, HTMLText support [:)]







