Flex Type Ahead List Boxes
When a user types a character the default behaviour of a list box is to jump to the first item that starts with that character. This is fine for small lists but for a long list of items it's not very useful. A better solution would be if the list box remembered what the user typed and keep looking for matches on the second character, the third character, etc etc.
Here how you can do this via an Actionscript class that extends the List Flex class.
2{
3 import mx.controls.List;
4 import flash.events.KeyboardEvent;
5 import flash.events.FocusEvent;
6
7 public class TypeList extends List
8 {
9 public function TypeList()
10 {
11 super();
12
13 // set up event handlers
14 this.addEventListener(KeyboardEvent.KEY_DOWN, jumpToItem);
15 this.addEventListener(FocusEvent.FOCUS_IN, clear);
16 this.addEventListener(FocusEvent.FOCUS_OUT, clear);
17 }
18
19 private var userTyped:String = "";
20
21 //clear memory of what user typed
22 private function clear(event:FocusEvent):void {
23 userTyped = "";
24 }
25
26 // Stop default behaviour
27 protected override function findKey(eventCode:int):Boolean {
28 return false;
29 }
30
31 // Try and find an item that matches what the user types if it matches jump to it
32 private function jumpToItem(event:KeyboardEvent):void {
33
34 // ignore any out of range characters
35 if (event.keyCode < 33 || event.keyCode > 126) {
36 return;
37 }
38
39 // build up string of what user typed
40 userTyped += String.fromCharCode(event.keyCode);
41
42 var length:Number = this.dataProvider.length;
43 var found:Boolean = false;
44 var start:Number = 0;
45 var match:String = userTyped.toLowerCase();
46
47 // start at the selected index
48 if (this.selectedIndex > 0) {
49 start = selectedIndex;
50 }
51
52 for varr i:Number = start; i < length; i++) {
53 var item:String = '';
54
55 if (this.labelFunction != null) {
56 item = this.labelFunction(this.dataProvider[i]).toLowerCase();
57 }
58 else if (this.labelField) {
59 item = this.dataProvider[i][this.labelField].toLowerCase();
60 }
61
62 if (item.indexOf(match) == 0) {
63 // set the selected index and scroll to it
64 this.selectedIndex = i;
65 this.scrollToIndex(i);
66 found = true;
67 return;
68 }
69 }
70
71 // if not found start again at the beginning
72 for (i = 0; i < start; i++) {
73 if (this.labelFunction != null) {
74 item = this.labelFunction(this.dataProvider[i]).toLowerCase();
75 }
76 else if (this.labelField) {
77 item = this.dataProvider[i][this.labelField].toLowerCase();
78 }
79
80 if (item.indexOf(match) == 0) {
81 // set the selected index and scroll to it
82 this.selectedIndex = i;
83 this.scrollToIndex(i);
84 found = true;
85 return;
86 }
87 }
88
89 // if not found clear what the user has typed
90 if (!found) {
91 userTyped = "";
92 }
93
94 return;
95 }
96
97 }
98
99}
Two things to notice:
- You need to override the findKey method of the List class for this to work.
- The code looks at both the labelFunction and labelField to match with what the user has typed.
And here' an MXML file using the above Actionscript class.
2<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*" layout="vertical" creationComplete="init()" viewSourceURL="srcview/index.html">
3 <mx:Form>
4 <mx:FormHeading label="Select Suburb" />
5 <mx:FormItem label="Suburbs">
6 <local:TypeList dataProvider="{locations}" rowCount="10" width="300" labelFunction="locationPostcode" />
7 </mx:FormItem>
8 </mx:Form>
9
10 <mx:Script>
11 <![CDATA[
12 import mx.collections.*;
13
14 [Bindable] private var locations:ArrayCollection = new ArrayCollection();
15
16 private var loader:URLLoader;
17
18 public function init():void {
19 // load postcode csv file
20 this.loader = new URLLoader();
21 this.loader.dataFormat = "text";
22 this.loader.addEventListener("complete",this.loaded);
23 this.loader.load(new URLRequest("postcodes.csv"));
24
25 // sort by location
26 var sort:Sort = new Sort();
27 sort.fields = [new SortField("location")];
28 this.locations.sort = sort;
29 this.locations.refresh();
30 }
31
32 // called when postcode file loaded
33 private function loaded(evt:Event):void {
34 var file:String = this.loader.data;
35 var lines:Array = file.split('\r');
36
37 // covert file into an array collection
38 for (var i:Number = 1; i < lines.length; i++)
39 {
40 var data:Array = lines[i].split(',');
41 var item:Object = new Object();
42
43 item.postcode = data[0];
44 item.location = data[1];
45
46 this.locations.addItem(item);
47 }
48 }
49
50 // display suburb and postcode
51 private function locationPostcode(data:Object):String {
52 return data.location + " (" + data.postcode +")";
53 }
54 ]]>
55 </mx:Script>
56</mx:Application>
This may not be the most efficient way of matching items as it looks through the entire list to find a match rather than just looking through a sublist once more than once character is typed. However it does make for simpler code and works quickly for large lists. If you had 50,000+ items in the list you might want to do it another way.
Here's the running sample:
There are no comments for this entry.
[Add Comment] [Subscribe to Comments]