All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scaffold.libs_as.feathers.controls.AutoComplete.as Maven / Gradle / Ivy

/*
Feathers
Copyright 2012-2015 Bowler Hat LLC. All Rights Reserved.

This program is free software. You can redistribute and/or modify it in
accordance with the terms of the accompanying license agreement.
*/
package feathers.controls
{
	import feathers.controls.popups.DropDownPopUpContentManager;
	import feathers.controls.popups.IPopUpContentManager;
	import feathers.core.PropertyProxy;
	import feathers.data.IAutoCompleteSource;
	import feathers.data.ListCollection;
	import feathers.events.FeathersEventType;
	import feathers.skins.IStyleProvider;

	import flash.events.KeyboardEvent;
	import flash.ui.Keyboard;
	import flash.utils.getTimer;

	import starling.core.Starling;
	import starling.events.Event;
	import starling.events.EventDispatcher;
	import starling.events.KeyboardEvent;

	/**
	 * Dispatched when the pop-up list is opened.
	 *
	 * 

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe Object that defines the * event listener that handles the event. For example, if you use * myButton.addEventListener() to register an event listener, * myButton is the value of the currentTarget.
datanull
targetThe Object that dispatched the event; * it is not always the Object listening for the event. Use the * currentTarget property to always access the Object * listening for the event.
* * @eventType starling.events.Event.OPEN */ [Event(name="open",type="starling.events.Event")] /** * Dispatched when the pop-up list is closed. * *

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe Object that defines the * event listener that handles the event. For example, if you use * myButton.addEventListener() to register an event listener, * myButton is the value of the currentTarget.
datanull
targetThe Object that dispatched the event; * it is not always the Object listening for the event. Use the * currentTarget property to always access the Object * listening for the event.
* * @eventType starling.events.Event.CLOSE */ [Event(name="close",type="starling.events.Event")] /** * A text input that provides a pop-up list with suggestions as you type. * *

The following example creates an AutoComplete with a * local collection of suggestions:

* * * var input:AutoComplete = new AutoComplete(); * input.source = new LocalAutoCompleteSource( new ListCollection(new <String> * [ * "Apple", * "Banana", * "Cherry", * "Grape", * "Lemon", * "Orange", * "Watermelon" * ])); * this.addChild( input ); * * @see ../../../help/auto-complete.html How to use the Feathers AutoComplete component * @see feathers.controls.TextInput */ public class AutoComplete extends TextInput { /** * @private */ protected static const INVALIDATION_FLAG_LIST_FACTORY:String = "listFactory"; /** * The default value added to the styleNameList of the pop-up * list. * * @see feathers.core.FeathersControl#styleNameList */ public static const DEFAULT_CHILD_STYLE_NAME_LIST:String = "feathers-auto-complete-list"; /** * The default IStyleProvider for all * AutoComplete components. If null, falls * back to using TextInput.globalStyleProvider instead. * * @default null * @see feathers.core.FeathersControl#styleProvider */ public static var globalStyleProvider:IStyleProvider; /** * @private */ protected static function defaultListFactory():List { return new List(); } /** * Constructor. */ public function AutoComplete() { this.addEventListener(Event.CHANGE, autoComplete_changeHandler); } /** * The default value added to the styleNameList of the * pop-up list. This variable is protected so that * sub-classes can customize the list style name in their constructors * instead of using the default style name defined by * DEFAULT_CHILD_STYLE_NAME_LIST. * *

To customize the pop-up list name without subclassing, see * customListStyleName.

* * @see #customListStyleName * @see feathers.core.FeathersControl#styleNameList */ protected var listStyleName:String = DEFAULT_CHILD_STYLE_NAME_LIST; /** * The list sub-component. * *

For internal use in subclasses.

* * @see #listFactory * @see #createList() */ protected var list:List; /** * @private */ protected var _listCollection:ListCollection; /** * @private */ override protected function get defaultStyleProvider():IStyleProvider { if(AutoComplete.globalStyleProvider) { return AutoComplete.globalStyleProvider; } return TextInput.globalStyleProvider; } /** * @private */ protected var _originalText:String; /** * @private */ protected var _source:IAutoCompleteSource; /** * The source of the suggestions that appear in the pop-up list. * *

In the following example, a source of suggestions is provided:

* * * input.source = new LocalAutoCompleteSource( new ListCollection(new <String> * [ * "Apple", * "Banana", * "Cherry", * "Grape", * "Lemon", * "Orange", * "Watermelon" * ])); * * @default null */ public function get source():IAutoCompleteSource { return this._source; } /** * @private */ public function set source(value:IAutoCompleteSource):void { if(this._source == value) { return; } if(this._source) { this._source.removeEventListener(Event.COMPLETE, dataProvider_completeHandler); } this._source = value; if(this._source) { this._source.addEventListener(Event.COMPLETE, dataProvider_completeHandler); } } /** * @private */ protected var _autoCompleteDelay:Number = 0.5; /** * The time, in seconds, after the text has changed before requesting * suggestions from the IAutoCompleteSource. * *

In the following example, the delay is changed to 1.5 seconds:

* * * input.autoCompleteDelay = 1.5; * * @default 0.5 * * @see #source */ public function get autoCompleteDelay():Number { return this._autoCompleteDelay; } /** * @private */ public function set autoCompleteDelay(value:Number):void { this._autoCompleteDelay = value; } /** * @private */ protected var _minimumAutoCompleteLength:int = 2; /** * The minimum number of entered characters required to request * suggestions from the IAutoCompleteSource. * *

In the following example, the minimum number of characters is * changed to 3:

* * * input.minimumAutoCompleteLength = 3; * * @default 2 * * @see #source */ public function get minimumAutoCompleteLength():Number { return this._minimumAutoCompleteLength; } /** * @private */ public function set minimumAutoCompleteLength(value:Number):void { this._minimumAutoCompleteLength = value; } /** * @private */ protected var _popUpContentManager:IPopUpContentManager; /** * A manager that handles the details of how to display the pop-up list. * *

In the following example, a pop-up content manager is provided:

* * * input.popUpContentManager = new CalloutPopUpContentManager(); * * @default null */ public function get popUpContentManager():IPopUpContentManager { return this._popUpContentManager; } /** * @private */ public function set popUpContentManager(value:IPopUpContentManager):void { if(this._popUpContentManager == value) { return; } if(this._popUpContentManager is EventDispatcher) { var dispatcher:EventDispatcher = EventDispatcher(this._popUpContentManager); dispatcher.removeEventListener(Event.OPEN, popUpContentManager_openHandler); dispatcher.removeEventListener(Event.CLOSE, popUpContentManager_closeHandler); } this._popUpContentManager = value; if(this._popUpContentManager is EventDispatcher) { dispatcher = EventDispatcher(this._popUpContentManager); dispatcher.addEventListener(Event.OPEN, popUpContentManager_openHandler); dispatcher.addEventListener(Event.CLOSE, popUpContentManager_closeHandler); } this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ protected var _listFactory:Function; /** * A function used to generate the pop-up list sub-component. The list * must be an instance of List. This factory can be used to * change properties on the list when it is first created. For instance, * if you are skinning Feathers components without a theme, you might * use this factory to set skins and other styles on the list. * *

The function should have the following signature:

*
function():List
* *

In the following example, a custom list factory is passed to the * AutoComplete:

* * * input.listFactory = function():List * { * var popUpList:List = new List(); * popUpList.backgroundSkin = new Image( texture ); * return popUpList; * }; * * @default null * * @see feathers.controls.List * @see #listProperties */ public function get listFactory():Function { return this._listFactory; } /** * @private */ public function set listFactory(value:Function):void { if(this._listFactory == value) { return; } this._listFactory = value; this.invalidate(INVALIDATION_FLAG_LIST_FACTORY); } /** * @private */ protected var _customListStyleName:String; /** * A style name to add to the list sub-component of the * AutoComplete. Typically used by a theme to provide * different styles to different AutoComplete instances. * *

In the following example, a custom list style name is passed to the * AutoComplete:

* * * input.customListStyleName = "my-custom-list"; * *

In your theme, you can target this sub-component style name to provide * different styles than the default:

* * * getStyleProviderForClass( List ).setFunctionForStyleName( "my-custom-list", setCustomListStyles ); * * @default null * * @see #DEFAULT_CHILD_STYLE_NAME_LIST * @see feathers.core.FeathersControl#styleNameList * @see #listFactory * @see #listProperties */ public function get customListStyleName():String { return this._customListStyleName; } /** * @private */ public function set customListStyleName(value:String):void { if(this._customListStyleName == value) { return; } this._customListStyleName = value; this.invalidate(INVALIDATION_FLAG_LIST_FACTORY); } /** * @private */ protected var _listProperties:PropertyProxy; /** * An object that stores properties for the auto-complete's pop-up list * sub-component, and the properties will be passed down to the pop-up * list when the auto-complete validates. For a list of available * properties, refer to * feathers.controls.List. * *

If the subcomponent has its own subcomponents, their properties * can be set too, using attribute @ notation. For example, * to set the skin on the thumb which is in a SimpleScrollBar, * which is in a List, you can use the following syntax:

*
list.verticalScrollBarProperties.@thumbProperties.defaultSkin = new Image(texture);
* *

Setting properties in a listFactory function * instead of using listProperties will result in better * performance.

* *

In the following example, the list properties are passed to the * auto complete:

* * * input.listProperties.backgroundSkin = new Image( texture ); * * @default null * * @see #listFactory * @see feathers.controls.List */ public function get listProperties():Object { if(!this._listProperties) { this._listProperties = new PropertyProxy(childProperties_onChange); } return this._listProperties; } /** * @private */ public function set listProperties(value:Object):void { if(this._listProperties == value) { return; } if(!value) { value = new PropertyProxy(); } if(!(value is PropertyProxy)) { var newValue:PropertyProxy = new PropertyProxy(); for(var propertyName:String in value) { newValue[propertyName] = value[propertyName]; } value = newValue; } if(this._listProperties) { this._listProperties.removeOnChangeCallback(childProperties_onChange); } this._listProperties = PropertyProxy(value); if(this._listProperties) { this._listProperties.addOnChangeCallback(childProperties_onChange); } this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ protected var _ignoreAutoCompleteChanges:Boolean = false; /** * @private */ protected var _lastChangeTime:int = 0; /** * @private */ protected var _listHasFocus:Boolean = false; /** * @private */ protected var _isOpenListPending:Boolean = false; /** * @private */ protected var _isCloseListPending:Boolean = false; /** * Opens the pop-up list, if it isn't already open. */ public function openList():void { this._isCloseListPending = false; if(this._popUpContentManager.isOpen) { return; } if(!this._isValidating && this.isInvalid()) { this._isOpenListPending = true; return; } this._isOpenListPending = false; this._popUpContentManager.open(this.list, this); this.list.validate(); if(this._focusManager) { this.stage.addEventListener(starling.events.KeyboardEvent.KEY_UP, stage_keyUpHandler); } } /** * Closes the pop-up list, if it is open. */ public function closeList():void { this._isOpenListPending = false; if(!this._popUpContentManager.isOpen) { return; } if(!this._isValidating && this.isInvalid()) { this._isCloseListPending = true; return; } if(this._listHasFocus) { this.list.dispatchEventWith(FeathersEventType.FOCUS_OUT); } this._isCloseListPending = false; this.list.validate(); //don't clean up anything from openList() in closeList(). The list //may be closed by removing it from the PopUpManager, which would //result in closeList() never being called. //instead, clean up in the Event.REMOVED_FROM_STAGE listener. this._popUpContentManager.close(); } /** * @inheritDoc */ override public function dispose():void { this.source = null; if(this.list) { this.closeList(); this.list.dispose(); this.list = null; } if(this._popUpContentManager) { this._popUpContentManager.dispose(); this._popUpContentManager = null; } super.dispose(); } /** * @private */ override protected function initialize():void { super.initialize(); this._listCollection = new ListCollection(); if(!this._popUpContentManager) { this.popUpContentManager = new DropDownPopUpContentManager(); } } /** * @private */ override protected function draw():void { var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES); var listFactoryInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_LIST_FACTORY); super.draw(); if(listFactoryInvalid) { this.createList(); } if(listFactoryInvalid || stylesInvalid) { this.refreshListProperties(); } this.handlePendingActions(); } /** * Creates and adds the list sub-component and * removes the old instance, if one exists. * *

Meant for internal use, and subclasses may override this function * with a custom implementation.

* * @see #list * @see #listFactory * @see #customListStyleName */ protected function createList():void { if(this.list) { this.list.removeFromParent(false); //disposing separately because the list may not have a parent this.list.dispose(); this.list = null; } var factory:Function = this._listFactory != null ? this._listFactory : defaultListFactory; var listStyleName:String = this._customListStyleName != null ? this._customListStyleName : this.listStyleName; this.list = List(factory()); this.list.focusOwner = this; this.list.isFocusEnabled = false; this.list.isChildFocusEnabled = false; this.list.styleNameList.add(listStyleName); this.list.addEventListener(Event.CHANGE, list_changeHandler); this.list.addEventListener(Event.TRIGGERED, list_triggeredHandler); this.list.addEventListener(Event.REMOVED_FROM_STAGE, list_removedFromStageHandler); } /** * @private */ protected function refreshListProperties():void { for(var propertyName:String in this._listProperties) { var propertyValue:Object = this._listProperties[propertyName]; this.list[propertyName] = propertyValue; } } /** * @private */ protected function handlePendingActions():void { if(this._isOpenListPending) { this.openList(); } if(this._isCloseListPending) { this.closeList(); } } /** * @private */ override protected function focusInHandler(event:Event):void { //the priority here is 1 so that this listener is called before //starling's listener. we want to know the list's selected index //before the list changes it. Starling.current.nativeStage.addEventListener(flash.events.KeyboardEvent.KEY_DOWN, nativeStage_keyDownHandler, false, 1, true); super.focusInHandler(event); } /** * @private */ override protected function focusOutHandler(event:Event):void { Starling.current.nativeStage.removeEventListener(flash.events.KeyboardEvent.KEY_DOWN, nativeStage_keyDownHandler); super.focusOutHandler(event); } /** * @private */ protected function nativeStage_keyDownHandler(event:flash.events.KeyboardEvent):void { if(!this._popUpContentManager.isOpen) { return; } var isDown:Boolean = event.keyCode == Keyboard.DOWN; var isUp:Boolean = event.keyCode == Keyboard.UP; if(!isDown && !isUp) { return; } var oldSelectedIndex:int = this.list.selectedIndex; var lastIndex:int = this.list.dataProvider.length - 1; if(oldSelectedIndex < 0) { event.stopImmediatePropagation(); this._originalText = this._text; if(isDown) { this.list.selectedIndex = 0; } else { this.list.selectedIndex = lastIndex; } this.list.scrollToDisplayIndex(this.list.selectedIndex, this.list.keyScrollDuration); this._listHasFocus = true; this.list.dispatchEventWith(FeathersEventType.FOCUS_IN); } else if((isDown && oldSelectedIndex == lastIndex) || (isUp && oldSelectedIndex == 0)) { event.stopImmediatePropagation(); var oldIgnoreAutoCompleteChanges:Boolean = this._ignoreAutoCompleteChanges; this._ignoreAutoCompleteChanges = true; this.text = this._originalText; this._ignoreAutoCompleteChanges = oldIgnoreAutoCompleteChanges; this.list.selectedIndex = -1; this.selectRange(this.text.length, this.text.length); this._listHasFocus = false; this.list.dispatchEventWith(FeathersEventType.FOCUS_OUT); } } /** * @private */ protected function autoComplete_changeHandler(event:Event):void { if(this._ignoreAutoCompleteChanges || !this._source || !this.hasFocus) { return; } if(this.text.length < this._minimumAutoCompleteLength) { this.removeEventListener(Event.ENTER_FRAME, autoComplete_enterFrameHandler); this.closeList(); return; } if(this._autoCompleteDelay == 0) { //just in case the enter frame listener was added before //sourceUpdateDelay was set to 0. this.removeEventListener(Event.ENTER_FRAME, autoComplete_enterFrameHandler); this._source.load(this.text, this._listCollection); } else { this._lastChangeTime = getTimer(); this.addEventListener(Event.ENTER_FRAME, autoComplete_enterFrameHandler); } } /** * @private */ protected function autoComplete_enterFrameHandler():void { var currentTime:int = getTimer(); var secondsSinceLastUpdate:Number = (currentTime - this._lastChangeTime) / 1000; if(secondsSinceLastUpdate < this._autoCompleteDelay) { return; } this.removeEventListener(Event.ENTER_FRAME, autoComplete_enterFrameHandler); this._source.load(this.text, this._listCollection); } /** * @private */ protected function dataProvider_completeHandler(event:Event, data:ListCollection):void { this.list.dataProvider = data; if(data.length == 0) { if(this._popUpContentManager.isOpen) { this.closeList(); } return; } this.openList(); } /** * @private */ protected function list_changeHandler(event:Event):void { if(!this.list.selectedItem) { return; } var oldIgnoreAutoCompleteChanges:Boolean = this._ignoreAutoCompleteChanges; this._ignoreAutoCompleteChanges = true; this.text = this.list.selectedItem.toString(); this.selectRange(this.text.length, this.text.length); this._ignoreAutoCompleteChanges = oldIgnoreAutoCompleteChanges; } /** * @private */ protected function popUpContentManager_openHandler(event:Event):void { this.dispatchEventWith(Event.OPEN); } /** * @private */ protected function popUpContentManager_closeHandler(event:Event):void { this.dispatchEventWith(Event.CLOSE); } /** * @private */ protected function list_removedFromStageHandler(event:Event):void { if(this._focusManager) { this.list.stage.removeEventListener(starling.events.KeyboardEvent.KEY_UP, stage_keyUpHandler); } } /** * @private */ protected function list_triggeredHandler(event:Event):void { if(!this._isEnabled) { return; } this.closeList(); this.selectRange(this.text.length, this.text.length); } /** * @private */ protected function stage_keyUpHandler(event:starling.events.KeyboardEvent):void { if(!this._popUpContentManager.isOpen) { return; } if(event.keyCode == Keyboard.ENTER) { this.closeList(); this.selectRange(this.text.length, this.text.length); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy