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.

view plain print about
1package
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.

view plain print about
1<?xml version="1.0" encoding="utf-8"?>
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:

Related Blog Entries

TweetBacks
Comments (Comment Moderation is enabled. Your comment will not appear until approved.)