Programatically Select Multiple [Flex] List/Data Grid Items From ActionScript
I needed to display to the users a List component with checkboxes;
- I needed to do this to remove the need for CTRL & CTRL+Shift commands that might have been 'above' the technical level of my users.
- I needed the visual effect they provided.
- I wanted the items to be highlighted and apear mostly as they do in a standard list.
- And lastly I wanted to be able to access what was choosen by the [myList.selectedItems] and not a custom holder.
I'm sure there are a few ways to make this happen, so I welcome any alternative advice. "View Source" is enabled on this flex app.
<flash source="/FlexApps/ProgramaticMultipleSelect/ProgramaticMultipleSelect.swf" width="386" height="163" quality="high" scriptAccess="none" align="center" />
My start page sets up the array for testing and calls on my extended List component "CheckBoxList.<mx:Application ...>
<mx:Script>
/> <![CDATA[
private static const people:Array = [
"Christina Coenraets", "Joanne Wall", "Maurice Smith", ...
];
]]>
</mx:Script>
/>
<local:CheckBoxList id="listOfPeople" dataProvider="{people}" width="200" height="140" />
<mx:List dataProvider="{this.listOfPeople.selectedItems}" width="154" height="140" />
</mx:Application>
My custom List component overrides the mouse events, doing nothing with them. Instead I listen for itemChecked and itemUnchecked events to fire off my selection methods.
The part I struggled with in doing this was getting multiple options to be selected. It turns out that even though the List.selectedItems is an array, you can't just push to it. You have to do a replacement. The explains the concat and splice code you see below.<?xml version="1.0" encoding="utf-8"?>
<mx:List
xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="onCreationComplete();"
itemRenderer="ItemRenderer"
>
<mx:Script>
/> <![CDATA[
private function onCreationComplete():void {
this.addEventListener("ItemChecked", onItemChecked);
this.addEventListener("ItemUnchecked", onItemUnchecked);
}
private function onItemChecked(event:ListItemEvent):void {
event.stopPropagation();
this.selectedItems = this.selectedItems.concat([event.targetItem]);
this.dispatchEvent(new Event(Event.CHANGE));
}
private function onItemUnchecked(event:ListItemEvent):void {
event.stopPropagation();
var pos:int = this.selectedItems.indexOf(event.targetItem);
this.selectedItems = this.selectedItems.filter(
function (item:*, index:int, array:Array):Boolean {
return item != this.targetItem;
},
event
);
this.dispatchEvent(new Event(Event.CHANGE));
}
override protected function mouseUpHandler(event:MouseEvent):void {}
override protected function mouseDownHandler(event:MouseEvent):void {}
override protected function focusInHandler(event:FocusEvent):void {}
]]>
</mx:Script>
/>
</mx:List>
My ItemRenderer handles throwing the ItemChecked and ItemUnchecked events. Notice my checkbox selection check in my "set date" function. This makes sure that the correct boxes are always checked when the user scrolls.<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" horizontalScrollPolicy="off">
<mx:Script>
/> <![CDATA[
import mx.controls.List;
[Bindable] private var displayName:String;
override public function set data(value:Object):void {
super.data = value;
displayName = value as String;
var list:List = this.parent.parent as List;
checkbox.selected = (list.selectedItems.indexOf(value) > -1);
}
private function flipSelectedSwitch():void {
this.checkbox.selected = !this.checkbox.selected;
this.onSelectedChange();
}
private function onSelectedChange():void {
if (checkbox.selected)
dispatchEvent(new ListItemEvent("ItemChecked", this.data, true, true));
else
dispatchEvent(new ListItemEvent("ItemUnchecked", this.data, true, true));
}
]]»
</mx:Script>
/>
<mx:Label width="100%" text="{displayName}" click="flipSelectedSwitch();" />
<mx:CheckBox id="checkbox" change="onSelectedChange();" paddingRight="10" />
</mx:HBox>
Sam wrote on 08/14/09 6:34 AM
Instead of rewriting the selection code, I decided to leverage the existing code. This has the added advantage of working with keyboard input as well.
BetterList.as:
package my.views.components
{
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import mx.controls.List;
public class BetterList extends List
{
//when true, simply clicking on the list renderer will toggle selected
public var selectMultipleOnClick:Boolean;
public function BetterList()
{
super();
selectMultipleOnClick = false;
}
override protected function keyDownHandler(event:KeyboardEvent):void
{
if (selectMultipleOnClick)
event.ctrlKey = true;
super.keyDownHandler(event);
}
override protected function mouseDownHandler(event:MouseEvent):void
{
if (selectMultipleOnClick)
event.ctrlKey = true;
super.mouseDownHandler(event);
}
}
}
To use,
allowMultipleSelection="true" selectMultipleOnClick="true">
import mx.events.ListEvent;
import mx.controls.List;
private function creationComplete():void
{
var list:List = this.parent.parent as List;
//keep the list renderer in sync
list.addEventListener(ListEvent.CHANGE, listChanged, false, 0, true);
}
private function listChanged(e:ListEvent=null):void
{
var list:List = this.parent.parent as List;
this.selected = (list.selectedItems.indexOf(data) > -1);
}
override protected function clickHandler(event:MouseEvent):void
{
//stop the click handler for the checkbox otherwise it will override the selected
//the checkbox will be updated via the listChanged method
//also stops the keyboard handler (that just calls this)
event.stopImmediatePropagation();
event.updateAfterEvent();
}
override public function set data(value:Object):void
{
super.data = value;
//needed because otherwise when list is reloaded, checkboxes are selected as per
//previous list, and not in the current list (out of sync with selectedItems)
listChanged();
}
]]>
Hope the code formats ok, and that it works - had to extract the code from a bigger MXML file.