Thursday, February 19, 2009

The Base Index Thing

Having already converted to MYSQL as a backend database while still using VFP – I have come up against an old xbase peculiarity with the base number for array indexing. In most other languages that I’ve come across (including Actionscript) – array indexing starts at zero – in xbase it starts at 1. Kind of cool for “FOR loops” in xbase, and has workable logic to it, but that’s about it!

A common practice for a finite number of options for a given field value is to tie an integer field to a combobox. Let me give an example: in an application that I’ve been working on there is a pricing option that is stored in a rate table. This defines how a price is calculated – “per person”, “group” or “group + additional”. The application only works with those price calculations, the list is not something that’s customizable by the user, so we don’t need a related table for that list – we just need a 1, 2 or 3 (or in Actionscript 0, 1 or 2) to tell us which pricing option has been assigned.

NB: You could argue that the best practice would be a related table with formulaic data that the application uses to calculate and the user could customise – that’s on the to-do list!

So to return to the problem – values in the database were stored originally in Foxpro but now need to be integrated into Flex. This means we now have an issue with just tying the value to the selectedIndex for a combobox – 1 now = 0, 2 now = 1 etc. The straightforward answer is to “just convert the data for Flex”. I actually adopted that policy previously on a different project and for reasons I won’t bother to explain right now it slowed down development and kept biting me in the nether region. Additionally – I want to use Flex and Foxpro concurrently - replacing sections of the front end in stages - and eventually ending up with an AIR application. In some areas of the application the user is working in Flex, in others it is still the Foxpro GUI.

The answer I came up with was relatively simple – override the getter and setter for selectedIndex, and use a separate private variable for binding the value from the data field. A little bit of plus and minus 1 and it all works. The code is below in the subclass of the combobox.

But then a second issue arose making things just a little more complicated. As soon as I put this extension of the Combobox on a canvas in a TabNavigator that wasn’t the first to be displayed (ie: the second or subsequent tab) strange things happened. Whatever I had the data field set to, I ended up with a blank comboBox when I clicked the tab the very first time. Move to another record and everything starts working and the values start displaying…

I am guessing this is related to some code inside Flex that is doing some delayed jiggery-pokery to enhance performance. Without delving into the code though – that will have to remain just a guess. In the meantime – I tweaked a way round it. The test in the override for selectedIndex checks to see if the combobox is on a tabNavigator – and make sure that if it is - when Flex sets the selectedIndex it doesn’t set it to minus one after it has already been set via the cbSelected variable.

Admittedly this is obscure stuff – but if I’ve helped out just one other person – I’ll be a happier individual for it!

Here’s the extension of the combobox.


package
{
import mx.containers.TabNavigator;
import mx.controls.ComboBox;
import mx.containers.Canvas;


/**
* ...
* @author ...
*/
public class ComboBoxSelectedIndex extends ComboBox
{
private var _cbSelected:int;
private var _selectedIndex:int;

public function ComboBoxSelectedIndex()
{
super();

}
public function get cbSelected():int
{
return _cbSelected;
}
public function set cbSelected(value:int):void
{
_cbSelected = value;

if (value > 0)
{
super.selectedIndex = value -1;
}
}
public override function get selectedIndex():int
{
return _selectedIndex;
}
public override function set selectedIndex(value:int):void
{
var setSelected:Boolean = true;
if (owner is Canvas)
{
if ((owner as Canvas).parent is TabNavigator)
{
if (cbSelected is int)
{
if (selectedIndex < 0 && cbSelected >= 0)
{
setSelected = false;
}
}
}
}
if (setSelected)
{
_cbSelected = value + 1;
super.selectedIndex = value;
_selectedIndex = value;
}
}
}
}

Wednesday, February 18, 2009

Checkbox Renderer Ignores Editable Property in a Grid Column

I've been having problems getting a Flex 3 checkbox to pay attention to a datagrid columns editable status. I finally found a solution using a component I had previously lifted from Alex's Flex Closet. The solution was in the code for the clickHandler - an override with a test of the parent columns editable status seems to have done the trick for me. This took me a while to work out - and even then there may be some issues with it. If anybody has a more efficient working method please let me know. I will come back soon and update this post with a full working example - but for now the code for extending the checkbox class is below.



package
{

import flash.display.DisplayObject;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.text.TextField;
import mx.controls.CheckBox;
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
import mx.controls.DataGrid;

/**
* The Renderer.
*/
public class CentredCheckBox extends CheckBox
{

private var _listData:DataGridListData;

public function CentredCheckBox()
{
focusEnabled = false;
}

override public function set data(value:Object):void
{
super.data = value;
if (listData is DataGridListData)
{
selected = Boolean(data[DataGridListData(listData).dataField]);
}
}
override public function get listData():BaseListData
{
return _listData;
}
override public function set listData(value:BaseListData):void
{
_listData = DataGridListData(value);
}

/* eat keyboard events, the underlying list will handle them */
override protected function keyDownHandler(event:KeyboardEvent):void
{
}

/* eat keyboard events, the underlying list will handle them */
override protected function keyUpHandler(event:KeyboardEvent):void
{
}

/* eat mouse events, the underlying list will handle them */
/* unless we are in edit mode - in which case process them */
override protected function clickHandler(event:MouseEvent):void
{
var allowEdit:Boolean;
if (listData is DataGridListData)
{
allowEdit = DataGrid(DataGridListData(listData).owner).columns[DataGridListData(listData).columnIndex].editable;
}
if (allowEdit)
{
super.clickHandler(event);
}
}

/* center the checkbox if we're in a datagrid */
override protected function updateDisplayList(w:Number, h:Number):void
{
super.updateDisplayList(w, h);

if (listData is DataGridListData)
{
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
var c:DisplayObject = getChildAt(i);
if (!(c is TextField))
{
c.x = (w - c.width) / 2;
c.y = 0;
}
}
}
}
}
}