Recently, I needed a way to export data from Adobe Flex’s AdvancedDataGrid control to CSV-formatted files for use in Microsoft Excel. Since Flex did not provide a native method, I looked into some existing third-party libraries. Those did not work out, so I ended up building my own AdvancedDataGrid CSV export utility class.
I first experimented with the excellent AlivePDF library. I had used AlivePDF in the past to…well…create PDFs. One of the things I remembered from using it was that there was CSV export functionality. But after spending some time experimenting, I was disappointed to find it required quite a bit of overhead and would not be able to fully satisfy my needs. Next, I turned my attention towards the AS3XLS library. From what I could gather, the library seems like what you’d want if you were trying to read & write native Excel files, but it didn’t provide CSV support.
So, I started searching the intertubes for another option. I found a great post by Abdul Qabiz, demonstrating a home baked solution he had come up with. The code he presented was quite dated (it was written for Flex 2, targeting the DataGrid control) and was missing some features I needed. However, it was exactly what I needed as a starting point. Learning from what he provided, I rolled my own solution that provided everything I was looking for.
Below is the AdvancedDataGrid CSV export utility class I came up with. It compiles in Flex 3.5, Flex 3.6 and Flex 4.5, handles grouped column headers, can double-quote values, and is capable of saving the CSV data of any language to a file (through the use of UTF-16 encoding). I am posting this simple class in case somebody else might find it useful. If you do, please leave a comment. Also, let me know if you find ways to improve it!
Note: The final version of the code uses a tab as the default CSV delimiter. This is due to a limitation in Excel when dealing with a comma-separated value file in UTF-16/Unicode encoding. Despite the use of a tab character, the format of the file is still viewed as a “CSV”. :-)
CSVUtil.as:
package net.onyxmueller.util{ import flash.net.FileReference; import flash.utils.ByteArray; import mx.collections.ICollectionView; import mx.collections.IViewCursor; import mx.controls.AdvancedDataGrid; import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn; import mx.controls.advancedDataGridClasses.AdvancedDataGridColumnGroup; public class CSVUtil{ private var _csvSeparator:String; private var _lineSeparator:String; private var _doubleQuoteValues:Boolean; private var _doubleQuoteRexExPattern:RegExp = /\"/g; private var _encoding:String; public function CSVUtil(csvSeparator:String = "\t", lineSeparator:String = "\n", doubleQuoteValues:Boolean = true){_csvSeparator = csvSeparator;_lineSeparator = lineSeparator;_doubleQuoteValues = doubleQuoteValues;} public function formatAsCSVString(items:Array):String{ if (_doubleQuoteValues){// escape any existing double quotes then place double quotes around values for (var i:int = 0; i < items.length; i++){ items[i] = items[i] ? items[i].replace(_doubleQuoteRexExPattern, "\"\"") : ""; items[i] = "\"" + items[i] + "\"";}} return items.join(_csvSeparator) + _lineSeparator;} public function advancedDataGridToCSVString(dg:AdvancedDataGrid):String{ var headerCSV:String = ""; var headerItems:Array; var dataCSV:String = ""; var dataItems:Array; var columns:Array = dg.groupedColumns ? dg.groupedColumns : dg.columns; var column:AdvancedDataGridColumn; var headerGenerated:Boolean = false; var cursor:IViewCursor = (dg.dataProvider as ICollectionView).createCursor();// loop through rows while (!cursor.afterLast){ var obj:Object = null; obj = cursor.current; dataItems = new Array(); headerItems = new Array();// loop through all columns for the row for each (column in columns){// if the column is not visible or the header text is not defined (e.g., a column used for a graphic),// do not include it in the CSV dump if (!column.visible || !column.headerText) continue;// depending on whether the current column is a group or not, export the data differently if (column is AdvancedDataGridColumnGroup){ for each (var subColumn:AdvancedDataGridColumn in (column as AdvancedDataGridColumnGroup).children){// if the sub-column is not visible or the header text is not defined (e.g., a column used for a graphic),// do not include it in the CSV dump if (!subColumn.visible || !subColumn.headerText) continue; dataItems.push(obj ? subColumn.itemToLabel(obj) : ""); if (!headerGenerated) headerItems.push(column.headerText + ": " + subColumn.headerText);}}else{ dataItems.push( obj ? column.itemToLabel(obj) : ""); if (!headerGenerated) headerItems.push(column.headerText);}}// append a CSV generated line of our data dataCSV += formatAsCSVString(dataItems);// if our header CSV has not been generated yet, do so; this should only occur once if (!headerGenerated){ headerCSV = formatAsCSVString(headerItems); headerGenerated = true;}// move to our next item cursor.moveNext();}// set references to null: headerItems = null; dataItems = null; columns = null; column = null; cursor = null;// return combined string return headerCSV + dataCSV;} public function saveAdvancedDataGridAsCSVFile(dg:AdvancedDataGrid, fileName:String, encoding:String = "utf-16"):void{ var csvString:String = advancedDataGridToCSVString(dg); var bytes:ByteArray = new ByteArray();// if using UTF-16, prefix file with BOM (little-endian) if (encoding == "utf-16"){ bytes.writeByte(0xFF); bytes.writeByte(0xFE); bytes.writeMultiByte(csvString, encoding);}else bytes.writeMultiByte(csvString, encoding);// prompt the user with a save location// note: FileReference requires a minimum flash player version of 10 var fileReference:FileReference = new FileReference(); fileReference.save(bytes, fileName); fileReference = null;}}}