Symmetri Developer Blog

June 23, 2009

Nulls in (Advanced)DataGrid column causes a column sort to throw RTE

Filed under: Flash/Flex - Shourov Bhattacharya @ 4:59 am

If an (Advanced)DataGrid has null values in the data provider, then sorting the associated column in the grid throws the following error:

Error: Cannot determine comparator for SortField with name ''XXX''.

Adobe already has a bug report: https://bugs.adobe.com/jira/browse/SDK-13808?actionOrder=desc.

There is a bit of discussion around this, with some people of the opinion that this is not really a bug - that it is the developer’s responsibility to ensure that either a) there are no nulls in the data or b) if there are, write your own sort compare function that handles the nulls. I call it a genuine bug in the Flex SDK; if my grid displays nulls, then it should know how to sort nulls as well - i.e. don’t tell me my data is valid when displaying but then not valid when sorting. Especially as there is an intuitive rule-of-thumb that could be used for sorting nulls of any data type (send them to the top of the ascending sort).

Probably the best workaround is to avoid the nulls in the first place. In my case, I couldn’t do that in the data source, so I did it in the item renderer:

package com.symmetri.skins
{
    import mx.controls.advancedDataGridClasses.*;
	
    public class ReportDataGridItemRenderer extends AdvancedDataGridItemRenderer
    {
        public function ReportDataGridItemRenderer()
        {
            super();
        }
	
         override public function validateNow():void
         {
             if ((listData) && (data))
             {
                  var _fieldName:String = AdvancedDataGridListData(listData).dataField;
                   var _value:String = String(data[AdvancedDataGridListData(listData).dataField]);
	
            //fill out null values with blanks
            if (data[AdvancedDataGridListData(listData).dataField] == null) data[AdvancedDataGridListData(listData).dataField] = ;
             }
	
             super.validateNow();
         }        
	
    }
}

Actually, I just though of something: does this approach have a shortcoming - will it work if not all rows in the data are rendered?

June 22, 2009

Flex bug when rendering AdvancedDataGrid with a small height

Filed under: Flash/Flex - Shourov Bhattacharya @ 1:09 am

I couldn’t find an Adobe bug report for this, so I might go ahead and log one. I have a number of AdvancedDataGrid components in my interface, and I have encountered a run-time error when resizing the interface. It appears to happen whenever one of the AdvancedDataGrid components is resized down to a very small size - less than about 40 pixels height. The error message is:

TypeError: Error #1009: Cannot access a property or method of a null object reference.
at mx.controls::AdvancedDataGridBaseEx/drawColumnBackground()
[C:\work\flex\dmv_automation\projects\datavisualisation\src \mx\controls\AdvancedDataGridBaseEx.as:3483]

For a workaround, I first tried to capture the ResizeEvent.RESIZE event on the AdvancedDataGrid and then test for the height of the grid; I set visible=false and includeInLayout = false whenever the grid was too small:

public function checkGridOnResize(evt:ResizeEvent):void
        {
            var grid:AdvancedDataGrid = AdvancedDataGrid(evt.currentTarget);
	
            grid.visible = (grid.height < 50 ? true: false);
            grid.includeInLayout = grid.visible;
        }

However, I was surprised to see that this didn’t work. The RTE was still being thrown, and the AdvancedDataGridBaseEx.drawColumnBackground() method was still being called, even though the grid was invisible. So I had to extend the AdvancedDataGrid class and catch the error in an overload of the drawColumnBackground method:

package com.symmetri.skins
{
    import mx.controls.AdvancedDataGrid;
    import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn;
    import mx.core.UIComponent;
	
    public class ReportDataGrid extends AdvancedDataGrid
    {
	
        public function ReportDataGrid()
        {
            super();
        }
	
        override protected function drawColumnBackground(s:Sprite, columnIndex:int, color:uint, column:AdvancedDataGridColumn):void
        {
            // this is done to handle a Flex bug
            // RTE thrown when rendering a grid smaller than about 40 pixels in height
            try
            {
                super.drawColumnBackground(s,columnIndex,color,column);
            }
            catch(e:Error) {}
        }
    }
}

Not the ideal solution, but the best we can do at this point. When (if) I get a spare moment, I will post the bug report for Adobe.

May 25, 2009

Open all branches in a Flex tree

Filed under: Flash/Flex - Shourov Bhattacharya @ 9:44 am

I need to create a Tree in Flex which starts off with all the nodes opened. This proves to be much harder than you might think. The solution I have found relies on a bit of hacking to keep track of when the Tree's data provider has been loaded, and then “flattening” the tree data structure into an ArrayCollection and using that as the openItems property of the Tree. In my case I am using an ArrayCollection of Objects as the data source.

A bonus is the ability to open the Tree to a particular depth. This comes in handy to stop a very deep tree from running away with your scripting.

<mx:Application xmlns:mx=http://www.adobe.com/2006/mxml>
	
<mx:Script>
    <![CDATA[
	
import flash.events.*;
import mx.collections.ArrayCollection;
import mx.controls.*;
import mx.core.*;
	
public var categoryTreeOpened:Boolean = false;
public var categories:ArrayCollection;
	
// create categories tree data structure as nested ArrayCollections of objects here
	
// label function for components which read from objects
protected function objectLabel(item:Object):String
{
     return item.name;
}
	
// open the category tree first time it is rendered
public function openAllCategories(event:Event = null):void
{
    if ((categoryTreeOpened == false) && (this.categories != null))
    {
        var openItems:ArrayCollection = this.flattenCategoryTree(this.categories, new ArrayCollection(), 4);
        this.trCategories.openItems = openItems;
        categoryTreeOpened = true;
    }
}
	
// flatten the category tree to a certain level
protected function flattenCategoryTree(_categories:ArrayCollection, input:ArrayCollection, level:Number = 5):ArrayCollection
{
    if (level == 0) return new ArrayCollection();
	
    for each (var _category:Object in _categories)
    {
        input.addItem(_category);
	
        input = new ArrayCollection(input.toArray().concat(flattenCategoryTree(_category.children, input, level-1).toArray()));
    }
	
    return input;
	
}
    ]]>
</mx:Script>
	
<mx:Tree  dataProvider={categories} labelFunction=objectLabel id=trCategories render=openAllCategories()/>
	
</mx:Application>

May 22, 2009

Render a bar graph within a Flex (Advanced)DataGrid

Filed under: Flash/Flex - Shourov Bhattacharya @ 4:11 am

A nice visual representation of a variable such as a “percentage growth” is a bar graph that shows the value around a central zero point, with different colour bars for negative and positive growth. To render this in an AdvancedDataGrid, we need to create a custom itemRenderer class that overloads the updateDisplayList method and draws a bar using the UIComponent.graphics object. This will give you a grid which looks something like this:

renderBar

Full source code for the item renderer class below.

package com.symmetri.utils.skins
{
    import flash.display.Graphics;
	
    import mx.controls.advancedDataGridClasses.*;
    import mx.controls.listClasses.*;
    import mx.core.IDataRenderer;
    import mx.core.UIComponent;
    import mx.events.FlexEvent;
    import mx.utils.GraphicsUtil;    
	
    [Event(name=dataChange, type=mx.events.FlexEvent)]
    public class ReportBarItemRenderer extends UIComponent implements IDataRenderer, IDropInListItemRenderer, IListItemRenderer
    {
        private var _data : Object = null;
        private var _listData : BaseListData = null;    
	
        public var BAR_MAX_WIDTH:Number = 17;
        public var BAR_HEIGHT:Number = 13;
        public var BAR_X:Number = 20;
        public var BAR_Y:Number = 1;
	
        public var DATA_MAX_VALUE:Number = 50;
	
        public var COLOR_POSITIVE:Number = 0x0D84A4;
        public var COLOR_NEGATIVE:Number = 0xD00A3D;        
	
        public function ReportBarItemRenderer()
        {
            super();
        }
	
        public function get data():Object
        {
            return _data;
        }
	
        public function set data(value:Object):void
        {
            this._data = value;
            this.invalidateProperties();
	
            dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
        }        
	
        public function get listData():BaseListData
        {
            return _listData;
        }
	
        public function set listData(value:BaseListData):void
        {
            this._listData = value;
            this.invalidateProperties();
	
            dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
        }    
	
         override protected function updateDisplayList(w:Number, h:Number):void
         {
             super.updateDisplayList(w, h);
	
             if (_data != null)
             {
                // draw a horizontal bar to represent value of data
                // scaled between -1.0 and +1.0
                var _fieldName:String = AdvancedDataGridListData(listData).dataField;
                var _value:String = String(data[AdvancedDataGridListData(listData).dataField]);                            
	
                if (isNaN(Number(_value)))
                {
                    // do nothing, data value not numeric
                }
                else
                {
                    // draw the bar
                    var _scaledValue:Number = Number(_value)/DATA_MAX_VALUE;
                    var _width:Number = _scaledValue*BAR_MAX_WIDTH; 
	
                    // keep a minimum width of 1 pixel
                    if ((_width > -1) && (_width < 0)) _width = -1;
                    if ((_width < 1) && (_width > 0)) _width = 1;
	
                      graphics.clear();
                    graphics.lineStyle(1, 0xFFFFFF, 0); // no border
	
                    if (_scaledValue < 0)
                    {
                        // draw a negative bar
                        graphics.beginFill(COLOR_NEGATIVE, 1);
                        GraphicsUtil.drawRoundRectComplex(graphics, BAR_X+_width, BAR_Y, -_width, BAR_HEIGHT, 0, 0, 0, 0);
                    }
                    else
                    {
                        // draw a negative bar
                        graphics.beginFill(COLOR_POSITIVE, 1);
                        GraphicsUtil.drawRoundRectComplex(graphics, BAR_X, BAR_Y, _width, BAR_HEIGHT, 0, 0, 0, 0);
	
                    }
                     graphics.endFill();
	
                     // draw a tooltip with the data value
                     this.toolTip = _value.toString();
                }
             }
	
             super.validateNow();
         }
    }
}

May 20, 2009

Flex compiler shows some smarts

Filed under: General, Flash/Flex, .NET - Shourov Bhattacharya @ 2:56 am

Developers like to compare IDEs. I use both Adobe Flex Builder 3 and Microsoft Visual Studio regularly. I think both are pretty good; each has its quirks and problems, perhaps Visual Studio marginally more than Flex Builder. One little thing I noticed while developing my Flex project today; Flex Builder compiler generates a warning when a single equal sign (=) is used in place of a double (==) for a equality comparison within a conditional statement. For example, the following statement:

if (x = y) {// do something }

will generate an compiler warning “Assignment within conditional statement (did you mean == instead of =?)”. I appreciated that. This is one of the most common syntax errors for programming newbies, and even us old hands fall prey to it on those late nights when the code is being churned out on autopilot. I think VS2008 would let this statement through the compile without batting an eyelid. Are there any other IDEs which pick up this very common syntax error?

May 14, 2009

Repeating image background in Flex

Filed under: Flash/Flex - Shourov Bhattacharya @ 1:39 am

Repeating an image in the background of a visual component in Flex turns out to be non-standard and not well documented. Given Flex 3 excellent support for CSS, you would probably assume that using background-image and background-repeat would do the trick. However, it turns out that background-repeat is not supported. The way to do it is to use the background-size style and set it to 100%:

.navPrimary
{
    background-image: Embed(../images/nav-background.png);
    background-size:100%;
}

May 11, 2009

ArrayCollection refresh bug

Filed under: Flash/Flex - Shourov Bhattacharya @ 11:22 am

This is a funky bug. I have an ArrayCollection as the dataProvider for a ColumnChart; when I change the data values within the ArrayCollection, the chart fails to update to display the new values. The workaround is easy enough: call ArrayCollection.refresh();. It’s a pretty basic bug though; Adobe really should get it fixed.

See https://bugs.adobe.com/jira/browse/SDK-13247

View MHTML in Flex?

Filed under: General, Flash/Flex, .NET - Shourov Bhattacharya @ 7:45 am

Everything you need for a webpage in one file, including external links to images, Flash files etc? Sounds like a good idea I guess; it would mean that you could easily email a single webpage to someone for offline viewing, for example. This was, I presume, the idea behind the MHTML format, which MIME-encodes a HTML webpage and associated resources into one long binary file. However, the problem is that very few browsers support the display of MHTML files natively. Internet Explorer does, but not Firefox, Safari or Google Chrome.

In my particular case, I have a bunch of reports generated by a third-party which are saved as MHTML files which need to be displayed within a Flex iFrame component (which is basically a floating browser container). I could display directly in the browser frame, in which case only users on IE will see the display. I could get the client to use a MHTML to HTML converter software. Or I could write my own server-side code to do the conversion on-the-fly, maybe using this code as a starting point.

April 29, 2009

Download Flex UI Component as JPG

Filed under: Flash/Flex - Shourov Bhattacharya @ 11:02 am

So I finally got this working. What I wanted to do was to add a "Save As Image" option to charts and grids which are part of a client’s reporting suite that is being developed as an online Flex application; the idea being that this would work just like it does with images in a browser’s HTML pages, allowing the user to save an image to the desktop or anywhere else on the local filesystem.

Getting the image data is easy - the new ImageSnapshot class in Flex 3 allows you to easily take a Bitmap "snapshot" of any DisplayObject and keep it as a ByteArray. However, getting the captured image back to the user is a little trickier. Flash Player 9 and below has a security sandbox model that disallows your Flex application from sending a file straight back through the browser, so what we have to do is "reflect" the image data off a server page - in other words, get our user’s browser to handle a new request that uploads the image data back to the server and then downloads that exact same data again, with the right headers, back in the browser client.

On the Flex side, I wrote up a little class which handles all the mechanics of taking the snapshot and launching the "reflected" page:

	
package com.symmetri.Tools
{
    import flash.display.IBitmapDrawable;
    import flash.events.*;
    import flash.net.*;
    import flash.net.navigateToURL;
    import flash.utils.ByteArray;
	
    import mx.controls.*;
    import mx.core.*;
    import mx.graphics.ImageSnapshot;
    import mx.graphics.codec.JPEGEncoder;
    import mx.utils.*;    
	
    public class ImageExporter
    {
        private var _reflectUrl:String;
	
        private var _snapshot:ImageSnapshot;
        private var _rawData:ByteArray;
	
        public function ImageExporter(reflectUrl:String)
        {
            _reflectUrl = reflectUrl;
        }
	
        // take a snapshot of a UI component as a JPG image
        // pass dpi of zero to take it at native screen resolution
        public function snapshotObjectAsJPGByteArray(source:IBitmapDrawable, dpi:Number = 0):ByteArray
        {
            // take the snapshot
            _snapshot = ImageSnapshot.captureImage(source, dpi, new JPEGEncoder());
            return (_snapshot.data as ByteArray);
	
        }
	
        // take a snapshot of a UI component as a JPG image
        // and send it to the browser as a download file
        // pass dpi of zero to take it at native screen resolution
        public function downloadObjectAsJPG(source:IBitmapDrawable, dpi:Number = 0):void
        {
            _rawData = this.snapshotObjectAsJPGByteArray(source, dpi);
	
            var _request:URLRequest = new URLRequest(_reflectUrl);
            _request.method = URLRequestMethod.POST;
            _request.contentType = application/octet-stream;
            _request.data = _rawData;
	
            navigateToURL(_request, _self);
        }
    }
}

On the server side, we have a blank ASPX page with the following code-behind (C#):

using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.IO;
	
public partial class ReflectJPG : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // method “attachment” or “inline”
        string method = (Request.QueryString[method] == null ? attachment: Request.QueryString[method].ToString());
	
        // filename
        string filename = (Request.QueryString[filename] == null ? image.jpg : Request.QueryString[filename].ToString());
	
        byte[] data = readPostedFile();
	
        Response.ContentType = image/jpg;
        Response.AddHeader(Content-Length, data.Length.ToString());
        Response.AddHeader(Content-disposition, method + ; filename= + filename);
        Response.BinaryWrite(data);
        Response.End();
    }
	
    // read the posted file
    private byte[] readPostedFile()
    {
        if (Request.ContentLength > 0)
        {
            byte[] buffer = new byte[Request.ContentLength];
            using (BinaryReader br = new BinaryReader(Request.InputStream))
                br.Read(buffer, 0, buffer.Length);
            return buffer;
        }
        else
        {
            return null;
        }
    }
}

Finally, to use the ImageExporter class in Flex, I use the following pattern to download a JPG snapshot of the component viewReport:

var exporter:ImageExporter = new ImageExporter(”http://www.symmetri.com/ReflectJPG.aspx”);
    exporter.downloadObjectAsJPG(viewReport);

April 20, 2009

Export Flex UI as image?

Filed under: Flash/Flex - Shourov Bhattacharya @ 3:42 am

I have been doing some research into how it might be possible to implement a "Right Click –>Save As Image" type functionality within a Flex application. This may run up against some problems with the Flash Player security sandbox, which will generally stop your local Flex application from saving anything to the local file system unless it is first bounced off a server (Flash Player 9-).

It appears that a solution that works across all version of Flash player will definitely the help of a server page - essentially, we need to take a snapshot of a bitmap image, save to the server and then use the server as a download location for the client. There is also a "server-less" solution, but that will only work in Flash player 10.

 The table below summarizes the options:

Solution Flash Player Server? Effort? Notes
Create Bitmap data from Flex UI and bounce off a server page e.g. see this article All versions Yes High Probably best, most robust solution but will need server side development and will take time
Use FusionCharts and supported SAI functionality All versions Yes Medium/Low Good solution but needs server side development
Use new capabilities of Flex for Flash Player 10 to save image (FileReference for local file) Flash Player 10+ No Low Quickest but will not work on Flash Player 9 or less without upgrade ( about 40% of clients - see stats)
Use “hacked” solution that uses JavaScript to save on local filesystem - see example All versions but won’t work in IE! No Medium/Low Not suitable as won’t work in IE and may not work for larger images

 

Get free blog up and running in minutes with Blogsome
Theme designed by Janis Joseph