Blog

List’s Custom Cell Renderer and RadioButton

In the application I’m developing I need a panel with a list, listing companies.
I have created custom cell renderer for this list. Each custom cell renderer has an instance of a RadioButton…

So the first problem:
Saying you have this list displaying 3 rows ( but have more ). You have selected the first one ( so in the custom cell renderer the radiobutton.selected is set to true ). Now you are scrolling down… and you will see the 5 items in the list having the RadioButton selected as well… What the heck?
The problem is List is trying to reuse the cell renderers, instead of destroying / re-creating new ones, but the RadioButtons in those cell renderer are the same ( they are not recreated ). So the list will reuse the cell renderer you have used for the first row ( you have selected ) and will display it with new data, but with the RadioButton still selected. Has you can’t unselect a RadioButton, you have two solutions:
– check if the row displayed are the one you have selected. If true, select the radio button, if not select a radio button of a cell renderer not displayed in the List ( there is at least 1). I haven’t gone this way, so dunno if it works but it should.
– delete the old radio button and recreate a new one when Invalidation.DATA is true on the cell renderer. If the row created is not selected the cell renderer will have just the data invalidate. If the row was the selected one then the Invalidate.SELECTED will be true. So here you could put the radioButton.selected = true;

So sweet, you can have a list with radio buttons, that works.

The second problem:
I realized that, if I have a list displaying X rows, having a total of X or X+N rows, then refresh the dataprovider and now have a list with X-N rows, I will get a funky RangeError like
DataProvider index (2) is not in acceptable range (0 – 1).
After some test I found out that this line of code was responsible
radioButton.selected = true;
Digging a bit further, I found out that:
– RadioButton dispatch a Event.CHANGE when the selected value change. ( this event is a bubble event )
– SelectableList ( parent of List ) dispatch an Event.CHANGE when an row (custom cell renderer ) item is clicked ( selected ).
– List listen to the Event.CHANGE and call the handleCellRendererChange function.
So you probably got it, the RadioButton dispatch a bubble event, the same the one the the List is listening too that will mess things up :)

In our example we had 6 rows. The list is displaying 3 rows, so actually 5 cell renderer are created ( the way the list is working, reusing instead of creating, the dataprovider could have had 10.000 rows, only 5 cell renderer would have been created ).
Now we have only 2 rows. So actually the list will need just 2 cell renderer as the number of row is lesser than the number of rows the list can display.
Now we know that those extra cell renderer left even if deleted( the list when the dataprovider change, is clearing the whole list and deleting all the cell renderers ), we have no power on WHEN they will be deleted .

Now, you are giving this new dataprovider to the list, and select the first row. When selecting a row in our case it selects the radiobutton in the custom cell renderer. So the radiobutton dispatch a Event.CHANGE.
The list listen to this event, and execute the handleCellRenderer, which is fine but not correct for the row displayed, but will failed in the extra cell renderer no more in the index of the list/dataprovider ( removed but not yet garbage collected ).

1
2
3
4
5
6
protected function handleCellRendererChange(event:Event):void
{
  var renderer:ICellRenderer = event.currentTarget as ICellRenderer;
  var itemIndex:uint = renderer.listData.index;
  _dataProvider.invalidateItemAt(itemIndex);
}

The fix for this one is easy. Just catch the Event.CHANGE of radio button in your cell renderer and stop the propagation like

1
2
3
4
5
radioButton.addEventListener( Event.CHANGE, __block );
private function __block( event : Event ) : void
{
  event.stopPropagation();
}

this will make that no Event.CHANGE will be dispatch to the list by the radio button, thus eveything will be fine :)

And don’t forget to remove the event listener when you delete the radio button ( bug 1 )

Hope this will help some trying to figure this out.
Take care

[Updated: needed to refine the explanation]