//////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors. // All Rights Reserved. The following is Source Code and is subject to all // restrictions on such code as contained in the End User License Agreement // accompanying this product. // //////////////////////////////////////////////////////////////////////////////// package com.adobe.flex.extras.controls { import flash.events.KeyboardEvent; import flash.events.Event; import flash.events.FocusEvent; import flash.net.SharedObject; import flash.ui.Keyboard; import mx.core.UIComponent; import mx.controls.ComboBox; import mx.collections.ArrayCollection; import mx.collections.ListCollectionView; //-------------------------------------- // Events //-------------------------------------- /** * Dispatched when the filterFunction property changes. * * You can listen for this event and update the component * when the filterFunction property changes.

* * @eventType flash.events.Event */ [Event(name="filterFunctionChange", type="flash.events.Event")] /** * Dispatched when the typedText property changes. * * You can listen for this event and update the component * when the typedText property changes.

* * @eventType flash.events.Event */ [Event(name="typedTextChange", type="flash.events.Event")] /* :: Added by Badu :: 'valueWritten' is dispatched when we know that the user didn't selected the value, but he wrote it down (so there is no correspondent of it in our data) 'resetCollection' is dispatched when */ [Event(name="valueWritten" , type="flash.events.Event")] [Event(name="resetCollection" , type="flash.events.Event")] //-------------------------------------- // Excluded APIs //-------------------------------------- [Exclude(name="editable", kind="property")] /** * The AutoComplete control is an enhanced * TextInput control which pops up a list of suggestions * based on characters entered by the user. These suggestions * are to be provided by setting the dataProvider * property of the control. * @mxml * *

The <fc:AutoComplete> tag inherits all the tag attributes * of its superclass, and adds the following tag attributes:

* *
	 *  <fc:AutoComplete
	 *    Properties
	 *    keepLocalHistory="false"
	 *    lookAhead="false"
	 *    typedText=""
	 *    filterFunction="Internal filter function"
	 *
	 *    Events
	 *    filterFunctionChange="No default"
	 *    typedTextChange="No default"
	 *  />
	 *  
* * @includeExample ../../../../../../docs/com/adobe/flex/extras/controls/example/AutoCompleteCountriesData/AutoCompleteCountriesData.mxml * * @see mx.controls.ComboBox * */ public class AutoComplete extends ComboBox { //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. */ public function AutoComplete() { super(); //Make ComboBox look like a normal text field editable = true; if(keepLocalHistory) addEventListener("focusOut",focusOutHandler); setStyle("arrowButtonWidth",0); setStyle("fontWeight","normal"); setStyle("cornerRadius",0); setStyle("paddingLeft",0); setStyle("paddingRight",0); rowCount = 7; } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private */ private var cursorPosition:Number=0; /** * @private */ private var prevIndex:Number = -1; /** * @private */ private var removeHighlight:Boolean = false; /** * @private */ private var showDropdown:Boolean=false; /** * @private */ private var showingDropdown:Boolean=false; /** * @private */ private var tempCollection:Object; /** * @private */ private var usingLocalHistory:Boolean=false; /** * @private */ private var dropdownClosed:Boolean=true; //-------------------------------------------------------------------------- // // Overridden Properties // //-------------------------------------------------------------------------- //---------------------------------- // editable //---------------------------------- /** * @private */ override public function set editable(value:Boolean):void { //This is done to prevent user from resetting the value to false super.editable = true; } /** * @private */ override public function set dataProvider(value:Object):void { super.dataProvider = value; if(!usingLocalHistory) tempCollection = value; } //---------------------------------- // labelField //---------------------------------- /** * @private */ override public function set labelField(value:String):void { super.labelField = value; invalidateProperties(); invalidateDisplayList(); } //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // filterFunction //---------------------------------- /** * @private * Storage for the filterFunction property. */ private var _filterFunction:Function = defaultFilterFunction; /** * @private */ private var filterFunctionChanged:Boolean = true; [Bindable("filterFunctionChange")] [Inspectable(category="General")] /** * A function that is used to select items that match the * function's criteria. * A filterFunction is expected to have the following signature: * *
f(item:~~, text:String):Boolean
* * where the return value is true if the specified item * should displayed as a suggestion. * Whenever there is a change in text in the AutoComplete control, this * filterFunction is run on each item in the dataProvider. * *

The default implementation for filterFunction works as follows:
* If "AB" has been typed, it will display all the items matching * "AB~~" (ABaa, ABcc, abAc etc.).

* *

An example usage of a customized filterFunction is when text typed * is a regular expression and we want to display all the * items which come in the set.

* * @example *
		 *  public function myFilterFunction(item:~~, text:String):Boolean
		 *  {
		 *     public var regExp:RegExp = new RegExp(text,"");
		 *     return regExp.test(item);
		 *  }
		 *  
* */ public function get filterFunction():Function { return _filterFunction; } /** * @private */ public function set filterFunction(value:Function):void { //An empty filterFunction is allowed but not a null filterFunction if(value!=null) { _filterFunction = value; filterFunctionChanged = true; invalidateProperties(); invalidateDisplayList(); dispatchEvent(new Event("filterFunctionChange")); } else _filterFunction = defaultFilterFunction; } //---------------------------------- // filterFunction //---------------------------------- /** * @private * Storage for the keepLocalHistory property. */ private var _keepLocalHistory:Boolean = false; /** * @private */ private var keepLocalHistoryChanged:Boolean = true; [Bindable("keepLocalHistoryChange")] [Inspectable(category="General")] /** * When true, this causes the control to keep track of the * entries that are typed into the control, and saves the * history as a local shared object. When true, the * completionFunction and dataProvider are ignored. * * @default "false" */ public function get keepLocalHistory():Boolean { return _keepLocalHistory; } /** * @private */ public function set keepLocalHistory(value:Boolean):void { _keepLocalHistory = value; } //---------------------------------- // lookAhead //---------------------------------- /** * @private * Storage for the lookAhead property. */ private var _lookAhead:Boolean=false; /** * @private */ private var lookAheadChanged:Boolean; [Bindable("lookAheadChange")] [Inspectable(category="Data")] /** * lookAhead decides whether to auto complete the text in the text field * with the first item in the drop down list or not. * * @default "false" */ public function get lookAhead():Boolean { return _lookAhead; } /** * @private */ public function set lookAhead(value:Boolean):void { _lookAhead = value; lookAheadChanged = true; } //---------------------------------- // typedText //---------------------------------- /** * @private * Storage for the typedText property. */ private var _typedText:String=""; /** * @private */ private var typedTextChanged:Boolean; [Bindable("typedTextChange")] [Inspectable(category="Data")] /** * A String to keep track of the text changed as * a result of user interaction. */ public function get typedText():String { return _typedText; } /** * @private */ public function set typedText(input:String):void { _typedText = input; typedTextChanged = true; invalidateProperties(); invalidateDisplayList(); dispatchEvent(new Event("typedTextChange")); } //-------------------------------------------------------------------------- // // Overridden methods // //-------------------------------------------------------------------------- /** * @private */ override protected function commitProperties():void { super.commitProperties(); if(!dropdown) selectedIndex=-1; if(dropdown) { if(typedTextChanged) { cursorPosition = textInput.selectionBeginIndex; updateDataProvider(); //In case there are no suggestions there is no need to show the dropdown if(collection.length==0 || typedText==""|| typedText==null) { dropdownClosed=true; showDropdown=false; } else { /* :: Added by Badu :: dispatching event that the collection has been modified, so we can reset any details were previously filled due to selection of something */ dispatchEvent(new Event('resetCollection')); showDropdown = true; selectedIndex = -1; //showDropdown = true; //selectedIndex = 0; } } } } /** * @private */ override protected function focusOutHandler(event:FocusEvent):void { super.focusOutHandler(event) if(keepLocalHistory && dataProvider.length==0) addToLocalHistory(); } /** * @private */ override public function getStyle(styleProp:String):* { if(styleProp != "openDuration") return super.getStyle(styleProp); else { if(dropdownClosed) return super.getStyle(styleProp); else return 0; } } /** * @private */ override protected function keyDownHandler(event:KeyboardEvent):void { super.keyDownHandler(event); if(!event.ctrlKey) { //An UP "keydown" event on the top-most item in the drop-down //or an ESCAPE "keydown" event should change the text in text // field to original text if(event.keyCode == Keyboard.UP && prevIndex==0) { textInput.text = _typedText; textInput.setSelection(textInput.text.length, textInput.text.length); selectedIndex = -1; } else if(event.keyCode==Keyboard.ESCAPE && showingDropdown) { textInput.text = _typedText; textInput.setSelection(textInput.text.length, textInput.text.length); showingDropdown = false; dropdownClosed=true; } else if(event.keyCode == Keyboard.ENTER) { if(keepLocalHistory && dataProvider.length==0) addToLocalHistory(); /* :: Added by Badu :: we are dispatching that the value is written by user, not choosen */ if (textInput.text.length == 0) return; if (selectedIndex == -1) { dispatchEvent(new Event('valueWritten')); event.stopPropagation(); } } else if(lookAhead && event.keyCode == Keyboard.BACKSPACE || event.keyCode == Keyboard.DELETE) removeHighlight = true; } else if(event.ctrlKey && event.keyCode == Keyboard.UP) dropdownClosed=true; prevIndex = selectedIndex; } /** * @private */ override protected function measure():void { super.measure(); measuredWidth = mx.core.UIComponent.DEFAULT_MEASURED_WIDTH; } /** * @private */ override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { super.updateDisplayList(unscaledWidth, unscaledHeight); //An UP "keydown" event on the top-most item in the drop //down list otherwise changes the text in the text field to "" if(selectedIndex == -1) textInput.text = typedText; if(dropdown) { if(typedTextChanged) { //This is needed because a call to super.updateDisplayList() set the text // in the textInput to "" and thus the value //typed by the user losts if(lookAhead && showDropdown && typedText!="" && !removeHighlight) { var label:String = itemToLabel(collection[0]); var index:Number = label.toLowerCase().indexOf(_typedText.toLowerCase()); if(index==0) { textInput.text = _typedText+label.substr(_typedText.length); textInput.setSelection(textInput.text.length,_typedText.length); } else { textInput.text = _typedText; textInput.setSelection(cursorPosition, cursorPosition); removeHighlight = false; } } else { textInput.text = _typedText; textInput.setSelection(cursorPosition, cursorPosition); removeHighlight = false; } typedTextChanged= false; } else if(typedText) //Sets the selection when user navigates the suggestion list through //arrows keys. textInput.setSelection(_typedText.length,textInput.text.length); } if(showDropdown && !dropdown.visible) { //This is needed to control the open duration of the dropdown super.open(); showDropdown = false; showingDropdown = true; if(dropdownClosed) dropdownClosed=false; } } /** * @private */ override protected function textInput_changeHandler(event:Event):void { super.textInput_changeHandler(event); //Stores the text typed by the user in a variable typedText=text; } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /* :: Added by Badu :: clears the component due to bug of selecting the first value when the ArrayCollection changes from null to something Can be called manually for each AutoComplete component or you can addEventListener for COLLECTION_CHANGE and update all components there */ public function clearThis():void{ _typedText = ''; selectedIndex = -1; } /** * @private * If keepLocalHistory is enabled, stores the text typed * by the user in the local history on the client machine */ private function addToLocalHistory():void { if (id != null && id != "" && text != null && text != "") { var so:SharedObject = SharedObject.getLocal("AutoCompleteData"); var savedData : Array = so.data.suggestions; //No shared object has been created so far if (savedData == null) savedData = new Array(); var i:Number=0; var flag:Boolean=false; //Check if this entry is there in the previously saved shared object data for(i=0;i