Tuesday, June 23, 2009

Dynamic columns in datagrid in Adobe Flex

When we set the dataprovider property of the datagrid, it automatically renders the columns with the default values. For instance, the column title is set as the node name if the dataprovider is XMLListCollection. But we would like to have more control on each column so that we can set it's title, width, and other properties as per our need. For this, we need to create each column dynamically at the run time by taking a first record from the dataprovider. In my same project, as mentioned in the last post, I was using XML output from PHP module to bind with the datagrid. However, this XML is also very dynamic in nature that it has variable numbers of nodes with variable node name based on the parameters selected. Depending on the output, I would also require to set some of the columns as read only when the grid is rendered. The overall idea here is to get the first child node of the XML and generate the array of an Objects with the desired properties set. And then an array of DataGridColumn objects is created and set the property as in the Object created in the first step. Below is the code sample;

private function bindData():void
{
    myGrid.dataProvider = xmlListCollection;
    myGrid.columns = getColumns(getColumnDef(xmlListCollection));
}
        
//get the first item from the XML to define the columns
private function getColumnDef(xmlData:XMLListCollection):Array
{
    var arrColDef:Array = new Array();
    var node:XML = xmlData[0]//get the first node of the XMLListCollection
    var childNodes:XMLList = node.children()//get its child nodes as an XMLList
    var objColDef:Object;
            
    for each(var xmlColumn:XML in childNodes//loop over the XMLList
    {
        objColDef = new Object();
        objColDef.dataField = xmlColumn.localName();
                
        switch(objColDef.dataField)
        {
            case "name":
                objColDef.headerText = "Full Name";
                objColDef.width = 120;
                objColDef.editable = true;
                break;
                    
            case "address":
                objColDef.headerText = "Permanent Address";
                objColDef.width = 120;
                objColDef.editable = true;
                break;
                        
            case "hour":
                objColDef.headerText = "Hour";
                objColDef.width = 120;
                objColDef.editable = false;
                break;
                        
            case "min":
                objColDef.headerText = "Minute";
                objColDef.width = 120;
                objColDef.editable = false;
                break;
                
            default :
                objColDef.width = 120;
                objColDef.editable = true;
                break;
        }
                
        arrColDef.push(objColDef);
    }

    return arrColDef;                    
}

//Generate the actual datagrid columns
private function getColumns(colDef:Array):Array
{
    var dataGridColumn:DataGridColumn;
    var arrColumns:Array = new Array();
            
    for each (var objColDef:Object in colDef)
    {
        dataGridColumn = new DataGridColumn();
        dataGridColumn.dataField =  objColDef.dataField;
        dataGridColumn.headerText = objColDef.headerText;
        dataGridColumn.editable = objColDef.editable;
        dataGridColumn.width =  objColDef.width;
                
        arrColumns.push(dataGridColumn);                
    }
    
    return arrColumns;
}

Friday, June 19, 2009

Deleting multiple records in datagrid in Adobe Flex

Although it sound simple, I had a really hard time finding the solution to delete multiple records in datagrid in one of my projects. I was using XMLListCollection as a dataprovider and a single record could be deleted with removeItemAt(index) method. However, for the multiple selection, Flex stores the indices of the selected rows in an array. If I iterate through these indices and start removing item using the index, as soon as the first record is removed, XMLListCollection re-arrange itself and remaining items get the new index which is not valid against the existing indices in the array. Also, there is no way that we can find if the row is selected just by iterating through the collection of rows. The frustrating part was that Google was not able to provide any answer. So, after spending few hours with couple of failed attempts, something suddenly came to my mind. What-if I start removing item from the bottom of the collection, instead from the top. This will not change the indices of existing items. This worked for items selected in an order. However, if I select items in the datagrid randomly, it fails. Flex pushes the indices of the items to an array as they are selected. That means the indices are not in order. So, there is a method Array.sort() to sort an array. Also, this method accept sortOption to consider the values in the array as numeric. Finally it worked. Below is the sample code;

//get the selected indices in the array
var sIndices:Array = myGrid.selectedIndices; 
                
//since the indices are pushed as they selected, we need to sort them in ascending order
sIndices.sort(Array.NUMERIC)
                
//get the highest index first and remove the item
// i.e. start removing items from the bottom so that change in index won't give any problem
for(var index:int = sIndices.length-1; index>=0; index--
    xmlListCollection.removeItemAt(sIndices[index]);


What amazed me the most is that I didn't find a single discussion in any of the websites, forums, blogs about this. Is it only me who want to have this kind of feature in the datagrid or the solution was so simple that people easily implemented it? :-)