The AS3 ComboBox has always been really irritating in data-driven apps. In short, it just doesn't work the way you expect a dropdown menu to work as far as keyboard accessibility. Any time you hit a key, it jumps to the first label starting with that letter, instead of letting you keep typing a word out. This irritates clients, who might have a combobox with a few thousand usernames in it. If three hundred of them start with "S", they have to type "S" and scroll down or arrow down to find the right one. Worst of all, it's very easy to forget that an app works this way, so there's the repeated aggravation of starting to type and then remembering that AS3 components are stupid. Or, as a client asked me, "why the **** can't it just work like everything else on the web?"
Well, now it can. The following is a drop-in replacement for ComboBox that works the way everything else on the web does. It's fast, fun and free. I'm putting this out for all to enjoy.
Cheers,
Josh
Code:
package {
import flash.utils.Timer;
import flash.ui.Keyboard;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import fl.controls.ComboBox;
import fl.data.DataProvider;
public class StrikeCB extends ComboBox {
private var buffer:String = "";
private var timer:Timer = new Timer(400);
private var dict:Array = new Array();
private var ds:Array;
public function StrikeCB() {
super();
init();
}
public function init():void {
addEventListener(Event.ADDED_TO_STAGE,addListeners,false,0,true);
addEventListener(Event.REMOVED_FROM_STAGE,remListeners,false,0,true);
}
private function addListeners(evt:Event):void {
this.addEventListener(FocusEvent.FOCUS_IN,openListen,false,0,true);
this.addEventListener(FocusEvent.FOCUS_OUT,openListen,false,0,true);
timer.addEventListener(TimerEvent.TIMER,clearBuffer,false,0,true);
buffer = "";
}
private function remListeners(evt:Event):void {
removeEventListener(FocusEvent.FOCUS_IN,openListen);
removeEventListener(FocusEvent.FOCUS_OUT,openListen);
timer.stop();
timer.removeEventListener(TimerEvent.TIMER,clearBuffer);
buffer = "";
}
private function openListen(evt:Event):void {
if (evt.type == "open" || evt.type=="focusIn") {
addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown,false,0,true);
} else {
removeEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
}
}
private function onKeyDown(evt:KeyboardEvent):void {
if (evt.charCode == Keyboard.SPACE) return (void);
buffer += String.fromCharCode(evt.charCode);
if (!ds) ds = dict.slice();
for (var m:int = 0;m<buffer.length;m++) {
for (var k:String in ds) {
if (ds[k] === null) continue;
if ((ds[k].length<buffer.length ||
buffer.substr(m,1).toLowerCase()!=ds[k].substr(m,1).toLowerCase()) &&
ds.length>1) {
ds[k] = null;
}
}
}
for (var u:String in ds) {
if (ds[u]!=null) {
this.selectedIndex = int(u);
this.highlightCell(int(u));
this.dropdown.scrollToIndex(int(u));
break;
}
}
timer.reset();
timer.start();
}
private function clearBuffer(evt:TimerEvent):void {
buffer = "";
ds = null;
}
override public function set dataProvider(arg0:DataProvider):void {
super.dataProvider = arg0;
for (var i:int=0;i<arg0.length;i++) {
dict[i] = arg0.getItemAt(i).label;
}
}
override protected function highlightCell(arg0:int=-1):void {
if (arg0>-1) selectedIndex = arg0;
super.highlightCell(arg0);
}
}
}
Last edited by joshstrike; 04-12-2011 at 02:14 PM.
Reason: fixed what happens when it's closed, but focus is on.
=)
One little note; it still doesn't work if the CB isn't open. That is, the normal behavior takes over if the CB is closed but still has focus on it. With a little tweaking though, it could do that too.
...actually, scratch that, I'm editing it right now to fix it.
let me know when you have an update... also I have a problem for you if you feel up to it, im sure someone with your experience will be able to explain to me what I am doing wrong!
Is there something I'm missing in your code, here? I pasted it as is, but I when i render my movie, I get a flashing comboboxes, and an error that says "Packages cannot be nested" - what am I missing?
There's nothing wrong with the code. It's inside a package. That means it goes in its own actionscript file, which you then import when you want to use it. You're supposed to change package {} to package folder.folder2.whatever {} depending on the relationship of the actionscript file to your actual FLA. You can't copy and paste this into the Actions panel.
There's nothing wrong with the code. It's inside a package. That means it goes in its own actionscript file, which you then import when you want to use it. You're supposed to change package {} to package folder.folder2.whatever {} depending on the relationship of the actionscript file to your actual FLA. You can't copy and paste this into the Actions panel.
What if the .as file is in the same folder as the .fla? I saved the code in a separate .as file and named is StrikeCB.as, but I still get the same Nested Package Error when I play my movie ugh