I'm attempting to search a combobox based on text entered via a keyboard event. The search is working and the correct result is being selected but I can't seem to get the scrollToIndex to find the correct item which should be the found result (i). It's scrolling to the last letter entered which I believe is the default behavior of a combobox. I think I'm referring to the event target incorrectly. Newbie tearing my hair out. Can you help? Thank you. Here's the function:
private function textin(event:KeyboardEvent):void
{
var combo:ComboBox = event.target as ComboBox;
var source:XMLListCollection = combo.dataProvider as XMLListCollection;
str += String.fromCharCode(event.charCode);
if (str=="") {
combo.selectedIndex = 0;
}
for (var i:int=0; i<source.length; i++) {
if ( source[i].#name.match(new RegExp("^" + str, "i")) ) {
combo.selectedIndex = i;
event.target.scrollToIndex(i);
break;
}
}
}
Control:
<mx:ComboBox keyDown="textin(event);" id="thislist" change="processForm();" dataProvider="{xmllist}"/>
If event.target is a mx.control.ComboBox then it doesn't have a scrollToIndex method, which is a method defined in mx.controls.ListBase, which the ComboBox doesn't inherit from. Check the api reference for the ComboBox. What exactly is the result you a you are trying to achieve here? If you set the selected index of a ComboBox it should display the item at that index.
EDIT: Try getting replacing event.target.scrollToIndex(i) (which should throw an error anyway) and replace it with event.stopImmediatePropagation(). This should prevent whatever the default key handler is from firing and overriding your event handler.
Here is a solution, based on Kerri's code and Ryan Lynch's suggestions. The credit goes to then.
It's working for me, so I will leave the complete code here for the future generations. :)
import com.utils.StringUtils;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.utils.Timer;
import mx.collections.ArrayCollection;
import mx.controls.ComboBox;
public class ExtendedComboBox extends ComboBox
{
private var mSearchText : String = "";
private var mResetStringTimer : Timer;
public function ExtendedComboBox()
{
super();
mResetStringTimer = new Timer( 1000 );
mResetStringTimer.addEventListener( TimerEvent.TIMER, function() : void { mResetStringTimer.stop(); mSearchText = ""; } );
}
override protected function keyDownHandler( aEvent : KeyboardEvent ):void
{
if( aEvent.charCode < 32 )
{
super.keyDownHandler( aEvent );
return;
}
var lComboBox : ComboBox = aEvent.target as ComboBox;
var lDataProvider : ArrayCollection = lComboBox.dataProvider as ArrayCollection;
mSearchText += String.fromCharCode( aEvent.charCode );
if ( StringUtils.IsNullOrEmpty( mSearchText ) )
{
lComboBox.selectedIndex = 0;
aEvent.stopImmediatePropagation();
return;
}
if( mResetStringTimer.running )
mResetStringTimer.reset();
mResetStringTimer.start();
for ( var i : int = 0; i < lDataProvider.length; i++ )
{
if ( lDataProvider[i].label.match( new RegExp( "^" + mSearchText, "i") ) )
{
lComboBox.selectedIndex = i;
aEvent.stopImmediatePropagation();
break;
}
}
}
}
This solution expects an ArrayCollection as the dataProvider and a field named "label" to do the searching. You can create a variable to store the name of the field, and use it like this:
lDataProvider[i][FIELD_NAME_HERE].match( new RegExp( "^" + mSearchText, "i") )
Have fun!
Related
Let's say I have a DropDownList with the 50 states in it. I would like to type in the letters "C + O + L" to jump to Colorado, like Firefox and most application do.
Right now, it's jumping from California to Ohio to end with Louisiana... Anybody know an easy way to do that?
Thanks a lot!
You could try to create a custom component that extends the DropDownList and override the offending function to add your own functionality that you want. It's the only way I can think of changing the default functionality.
Like #J_A_X proposed, I modified the DropDownList class, adding a timer that keeps the string that the user is typing for ¾ seconds and then, reset it. Here's my solution :
package MyComps
{
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.utils.setTimeout;
import mx.core.mx_internal;
import spark.components.DropDownList;
use namespace mx_internal;
public class DropDownListKeyboardSelection extends DropDownList
{
private var _duration:Number = 750; // Time in milliseconds before the _str is resetted
private var _timer:Timer;
private var _str:String = '';
public function DropDownListKeyboardSelection()
{
super();
}
override mx_internal function findKey(eventCode:int):Boolean
{
if (!dataProvider || dataProvider.length == 0)
return false;
if (eventCode >= 33 && eventCode <= 126)
{
var matchingIndex:Number;
var keyString:String = String.fromCharCode(eventCode);
// Freshly instantiated or resetted by timerEnded(). In that case, we start the timer
if (_str == '') {
startTimer();
} else {
_timer.reset();
startTimer();
}
// Building the string to find
_str += keyString;
matchingIndex = findStringLoop(_str, 0, dataProvider.length);
// We didn't find the item, loop back to the top
if (matchingIndex == -1)
{
matchingIndex = findStringLoop(keyString, 0, 0);
}
if (matchingIndex != -1)
{
if (isDropDownOpen)
changeHighlightedSelection(matchingIndex);
else
setSelectedIndex(matchingIndex, true);
return true;
}
}
return false;
}
// Let's start the _timer
private function startTimer():void
{
_timer = new Timer(_duration);
_timer.addEventListener(TimerEvent.TIMER, timerEnded);
_timer.start();
}
// Timer ended, let's reset the _str variable
private function timerEnded(event:TimerEvent):void
{
_str = '';
_timer.reset();
}
}
}
I believe this is dependant on the browser (if using the standard list element). You could create an autocomplete field through jquery (although this isn't the same as a drop down list).
I have a combobox that act as autosuggestion for a search application. Search function is getting triggered by a search button. I also want to trigger the search function either when the item in combobox is double or single clicked. Code:
//for triggering search function from combobox(search_complex) it will be
something like that but i am not sure
search_complex.addEventListener(Event.CHANGE, search);
search(event:Event):void{//something will come hereto use "selctedItem" to
trigger search function}
//search function which is working fine by pressing search button
bt_search.addEventListener(MouseEvent.CLICK, search);
function search(MouseEvent):void{
currentUserbase = [];
for (var n:int = 0; n<allUserbase.length; n++)
{
for (var k:int = 0; k<allUserbase[n].complex.length; k++)
{
if ((allUserbase[n].complex[k].value.toLowerCase() ==
search_complex.text.toLowerCase() || search_complex.text==""))
{
currentUserbase.push(allUserbase[n]);
}
}
}
updateList();
}//end search
I don't understand what you exactly want.
Is it rue that you have a search function, that will work fine.
Now you don't need for each event seperate handler. It is enough to use one for all events. As functionparameter use type "Event" because all other events inherit from this base class.
Check my Code. cd is my combobox. This example is written in flex3
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable] private var arr:ArrayCollection = new ArrayCollection([
{name:"Alexander"},
{name:"Bernd"},
{name:"Carl"}
]);
private function init():void
{
cb.addEventListener(MouseEvent.CLICK,search);
cb.addEventListener(MouseEvent.DOUBLE_CLICK,search);
cb.addEventListener(Event.CHANGE,search);
}
private function search (event:Event) :void
{
trace (event.type);
}
]]>
</mx:Script>
I believe you're on the right track. Try:
search_complex.addEventListener(Event.CHANGE, search);
bt_search.addEventListener(MouseEvent.CLICK, search);
function search(event:Event):void
{
currentUserbase = [];
for (var n:int = 0; n<allUserbase.length; n++)
{
for (var k:int = 0; k<allUserbase[n].complex.length; k++)
{
if ((allUserbase[n].complex[k].value.toLowerCase() == search_complex.text.toLowerCase() || search_complex.text==""))
{
currentUserbase.push(allUserbase[n]);
}
}
}
updateList();
}//end search
You should be able to get the selected item in your combobox using search_complex.selectedItem.label or search_complex.selectedItem.label depending on which property you need to use.
I am trying to duplicate a text field. First I get the text with a mc.getChildAt(0) and then copy all the contents into a new textfield. The problem I am having is that getChildAt removes the textfield from the movieclip it is in. How to I get the properties of the textfield without moving it? Or maybe it is something else and what I am doing is fine. Any insight would be a huge help...
var dupeTField:MovieClip = duplicateTextField($value.sourceImg.getChildAt(0));
private function duplicateTextField($textField):MovieClip
{
var currTextField:TextField = $textField;
var dupeTextHolder:MovieClip = new MovieClip();
var dupeTextField:TextField = new TextField();
dupeTextField.text = currTextField.text;
dupeTextField.textColor = currTextField.textColor;
dupeTextField.width = $textField.width;
dupeTextField.height = $textField.height;
dupeTextHolder.addChild(dupeTextField);
return dupeTextHolder;
}
Use something like this:
package com.ad.common {
import flash.text.TextField;
import flash.utils.describeType;
public function cloneTextField(textField:TextField, replace:Boolean = false):TextField {
var clone:TextField = new TextField();
var description:XML = describeType(textField);
for each (var item:XML in description.accessor) {
if (item.#access != 'readonly') {
try {
clone[item.#name] = textField[item.#name];
} catch(error:Error) {
// N/A yet.
}
}
}
clone.defaultTextFormat = textField.getTextFormat();
if (textField.parent && replace) {
textField.parent.addChild(clone);
textField.parent.removeChild(textField);
}
return clone;
}
}
I think you'll find your problem is somewhere else. getChildAt does not remove its target from its parent, and the function you posted works as advertised for me, creating a duplicate clip without affecting the original.
private var dupeTField:MovieClip;
private function init():void
{
//getChildAt will return a DisplayObject so you
//should cast the return DisplayObject as a TextField
var tf:TextField = $value.sourceImg.getChildAt(0) as TextField;
dupeTField = duplicateTextField(tf);
//don't forget to add your duplicate to the Display List
//& make sure to change the x, y properties so that
//it doesn't sit on top of the original
addChild(dupeTField );
}
private function duplicateTextField(textField:TextField):MovieClip
{
var dupeTextHolder:MovieClip = new MovieClip();
var dupeTextField:TextField = new TextField();
//if you pass a TextField as a parameter, you don't need to
//replicate the instance inside the function, simply access the
//parameter directly.
//You may consider copying the TextFormat as well
dupeTextField.defaultTextFormat = textfield.defaultTextFormat;
dupeTextField.text = textField.text;
dupeTextField.textColor = textField.textColor;
dupeTextField.width = textField.width;
dupeTextField.height = textField.height;
dupeTextHolder.addChild(dupeTextField);
return dupeTextHolder;
}
My goal is to create a generic function that selects a value in a combobox accoring to a value.
(My comoBox holds arrayCollection as dataProvider.)
The difficulty is infact to get a propertyname in runtime mode
public function selectComboByLabel(combo:ComboBox , propetryName:String, value:String):void {
var dp:ArrayCollection = combo.dataProvider as ArrayCollection;
for (var i:int=0;i<dp.length;i++) {
if (dp.getItemAt(i).propertyName==value) {
combo.selectedIndex = i;
return;
}
}
}
the line if (dp.getItemAt(i).propertyName==value)
is of course incorrect.
It should be arther something like: dp.getItemAt(i).getPropertyByName(propertyName)
Any clue on how to that ?
Don't use Object Property notation. Do this:
dp.getItemAt(i)[propertyName]
In addition to what Flextras said, you could also redo your for loop to make it easier to read:
for each(var item:Object in dp) {
if(item[propertyName] == value) {
combo.selectedItem = item;
return;
}
}
I want to check in my function if a passed argument of type object is empty or not. Sometimes it is empty but still not null thus I can not rely on null condition. Is there some property like 'length'/'size' for flex objects which I can use here.
Please help.
Thanks in advance.
If you mean if an Object has no properties:
var isEmpty:Boolean = true;
for (var n in obj) { isEmpty = false; break; }
This is some serious hack but you can use:
Object.prototype.isEmpty = function():Boolean {
for(var i in this)
if(i != "isEmpty")
return false
return true
}
var p = {};
trace(p.isEmpty()); // true
var p2 = {a:1}
trace(p2.isEmpty()); // false
You can also try:
ObjectUtil.getClassInfo(obj).properties.length > 0
The good thing about it is that getClassInfo gives you much more info about the object, eg. you get the names of all the properties in the object, which might come in handy.
If object containes some 'text' but as3 doesn't recognize it as a String, convert it to string and check if it's empty.
var checkObject:String = myObject;
if(checkObject == '')
{
trace('object is empty');
}
Depends on what your object is, or rather what you expect it to have. For example if your object is supposed to contain some property called name that you are looking for, you might do
if(objSomeItem == null || objSomeItem.name == null || objSomeItem.name.length == 0)
{
trace("object is empty");
}
or if your object is actually supposed to be something else, like an array you could do
var arySomeItems = objSomeItem as Array;
if(objSomeItem == null || arySomeItems == null || arySomeItems.length == 0)
{
trace("object is empty");
}
You could also use other ways through reflection, such as ObjectUtil.getClassInfo, then enumerate through the properties to check for set values.... this class help:
import flash.utils.describeType;
import flash.utils.getDefinitionByName;
public class ReflectionUtils
{
/** Returns an Array of All Properties of the supplied object */
public static function GetVariableNames(objItem:Object):Array
{
var xmlPropsList:XMLList = describeType(objItem)..variable;
var aryVariables:Array = new Array();
if (xmlPropsList != null)
{
for (var i:int; i < xmlPropsList.length(); i++)
{
aryVariables.push(xmlPropsList[i].#name);
}
}
return aryVariables;
}
/** Returns the Strongly Typed class of the specified library item */
public static function GetClassByName($sLinkageName:String):Class
{
var tObject:Class = getDefinitionByName($sLinkageName) as Class;
return tObject;
}
/** Constructs an instance of the speicified library item */
public static function ConstructClassByName($sLinkageName:String):Object
{
var tObject:Class = GetClassByName($sLinkageName);
//trace("Found Class: " + tMCDefinition);
var objItem:* = new tObject();
return objItem;
}
public static function DumpObject(sItemName:String, objItem:Object):void
{
trace("*********** Object Dump: " + sItemName + " ***************");
for (var sKey:String in objItem)
{
trace(" " + sKey +": " + objItem[sKey]);
}
}
//}
}
Another thing to note is you can use a simple for loop to check through an objects properties, thats what this dumpobject function is doing.
You can directly check it as follow,
var obj:Object = new Object();
if(obj == null)
{
//Do something
}
I stole this from a similar question relating to JS. It requires FP 11+ or a JSON.as library.
function isEmptyObject(obj){
return JSON.stringify(obj) === '{}';
}
can use use the hasProperty method to check for length
var i:int = myObject.hasProperty("length") ? myObject.length: 0;