haxe/ui/components/DropDown.hx
package haxe.ui.components;
import haxe.ui.Toolkit;
import haxe.ui.actions.ActionType;
import haxe.ui.behaviours.DataBehaviour;
import haxe.ui.behaviours.DefaultBehaviour;
import haxe.ui.behaviours.DynamicDataBehaviour;
import haxe.ui.components.Button.ButtonBuilder;
import haxe.ui.components.Button.ButtonEvents;
import haxe.ui.components.TextField;
import haxe.ui.containers.Box;
import haxe.ui.containers.CalendarView;
import haxe.ui.containers.HBox;
import haxe.ui.containers.ListView;
import haxe.ui.containers.VBox;
import haxe.ui.core.BasicItemRenderer;
import haxe.ui.core.Component;
import haxe.ui.core.IDataComponent;
import haxe.ui.core.ItemRenderer;
import haxe.ui.core.Screen;
import haxe.ui.data.ArrayDataSource;
import haxe.ui.data.DataSource;
import haxe.ui.events.ActionEvent;
import haxe.ui.events.MouseEvent;
import haxe.ui.events.UIEvent;
import haxe.ui.locale.Formats;
import haxe.ui.locale.LocaleManager;
import haxe.ui.util.Variant;
@:composite(DropDownEvents, DropDownBuilder)
class DropDown extends Button implements IDataComponent {
//***********************************************************************************************************
// Public API
//***********************************************************************************************************
@:clonable @:behaviour(DefaultBehaviour) public var handlerStyleNames:String;
@:clonable @:behaviour(DataSourceBehaviour) public var dataSource:DataSource<Dynamic>;
@:clonable @:behaviour(DefaultBehaviour, "list") public var type:String;
@:clonable @:behaviour(DefaultBehaviour, false) public var virtual:Bool;
@:clonable @:behaviour(DefaultBehaviour) public var dropdownWidth:Null<Float>;
@:clonable @:behaviour(DefaultBehaviour) public var dropdownHeight:Null<Float>;
@:clonable @:behaviour(DefaultBehaviour) public var dropdownSize:Null<Int>;
@:clonable @:behaviour(DefaultBehaviour) public var dropdownHorizontalPosition:String;
@:clonable @:behaviour(DefaultBehaviour) public var dropdownVerticalPosition:String;
@:clonable @:behaviour(SelectedIndexBehaviour, -1) public var selectedIndex:Int;
@:clonable @:behaviour(SelectedItemBehaviour) public var selectedItem:Dynamic;
@:clonable @:behaviour(DefaultBehaviour, false) public var searchable:Bool;
@:clonable @:behaviour(DefaultBehaviour, "{{search}}") public var searchPrompt:String;
@:clonable @:value(selectedItem) public var value:Dynamic;
@:clonable @:behaviour(SearchFieldBehaviour) public var searchField:Component;
@:clonable @:behaviour(DropDownOpenBehaviour) public var dropDownOpen:Bool;
@:call(HideDropDown) public function hideDropDown();
@:call(ShowDropDown) public function showDropDown();
public function selectItemBy(fn:Dynamic->Bool, allowUnselection:Bool = false) {
var indexToSelect = -1;
for (i in 0...this.dataSource.size) {
var item = this.dataSource.get(i);
if (fn(item)) {
indexToSelect = i;
break;
}
}
if (allowUnselection) {
this.selectedIndex = -1;
this.selectedIndex = indexToSelect;
} else if (indexToSelect != -1) {
this.selectedIndex = -1;
this.selectedIndex = indexToSelect;
}
}
private var _itemRenderer:ItemRenderer = null;
@:clonable public var itemRenderer(get, set):ItemRenderer;
private function get_itemRenderer():ItemRenderer {
return _itemRenderer;
}
private function set_itemRenderer(value:ItemRenderer):ItemRenderer {
_itemRenderer = value;
return value;
}
private override function onThemeChanged() {
super.onThemeChanged();
var builder:DropDownBuilder = cast(this._compositeBuilder, DropDownBuilder);
builder.onThemeChanged();
}
private override function postCloneComponent(c:Component) {
super.postCloneComponent(c);
if (_itemRenderer != null) {
c.addComponent(_itemRenderer.cloneComponent());
}
}
}
//***********************************************************************************************************
// Composite Behaviours
//***********************************************************************************************************
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
private class HideDropDown extends DefaultBehaviour {
public override function call(param:Any = null):Variant {
var events:DropDownEvents = cast(_component._internalEvents, DropDownEvents);
events.hideDropDown();
return null;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
private class ShowDropDown extends DefaultBehaviour {
public override function call(param:Any = null):Variant {
var events:DropDownEvents = cast(_component._internalEvents, DropDownEvents);
events.showDropDown();
return null;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
private class DataSourceBehaviour extends DefaultBehaviour {
public override function get():Variant {
if (_value == null || _value.isNull == true) {
_value = new ArrayDataSource<Dynamic>();
}
return _value;
}
public override function set(value:Variant) {
super.set(value);
if (value == _value) {
return;
}
var handler:IDropDownHandler = cast(_component._compositeBuilder, DropDownBuilder).handler;
handler.reset();
if (_component.text == null && _component.isReady) {
cast(_component, DropDown).selectedIndex = 0;
}
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
private class SelectedIndexBehaviour extends DataBehaviour {
private override function validateData() {
var handler:IDropDownHandler = cast(_component._compositeBuilder, DropDownBuilder).handler;
handler.selectedIndex = _value;
}
public override function get():Variant {
if (_component.isReady == false) {
return super.get();
}
var handler:IDropDownHandler = cast(_component._compositeBuilder, DropDownBuilder).handler;
return handler.selectedIndex;
}
public override function set(value:Variant) {
if (_component.isReady == false) {
super.set(value);
return;
}
if (value == get()) {
return;
}
_value = value;
invalidateData();
var handler:IDropDownHandler = cast(_component._compositeBuilder, DropDownBuilder).handler;
handler.selectedIndex = _value;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
private class SelectedItemBehaviour extends DynamicDataBehaviour {
private override function validateData() {
var handler:IDropDownHandler = cast(_component._compositeBuilder, DropDownBuilder).handler;
handler.selectedItem = _value;
}
public override function getDynamic():Dynamic {
if (_component.isReady == false) {
return _value;
}
var handler:IDropDownHandler = cast(_component._compositeBuilder, DropDownBuilder).handler;
return handler.selectedItem;
}
public override function setDynamic(value:Dynamic) {
if (_component.isReady == false) {
super.setDynamic(value);
return;
}
_value = value;
invalidateData();
var handler:IDropDownHandler = cast(_component._compositeBuilder, DropDownBuilder).handler;
handler.selectedItem = value;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
private class SearchFieldBehaviour extends DefaultBehaviour {
public override function get():Variant {
var events:DropDownEvents = cast(_component._internalEvents, DropDownEvents);
return events.searchField;
}
public override function set(value:Variant) {
var events:DropDownEvents = cast(_component._internalEvents, DropDownEvents);
events.searchField = cast value.toComponent();
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
private class DropDownOpenBehaviour extends DefaultBehaviour {
public override function get():Variant {
var events:DropDownEvents = cast(_component._internalEvents, DropDownEvents);
return @:privateAccess events._dropdownOpen;
}
public override function set(value:Variant) {
var events:DropDownEvents = cast(_component._internalEvents, DropDownEvents);
if (value == true) {
events.showDropDown();
} else {
events.hideDropDown();
}
}
}
//***********************************************************************************************************
// Dropdown Handlers
//***********************************************************************************************************
interface IDropDownHandler {
var component(get, null):Component;
function prepare(wrapper:Box):Void;
function reset():Void;
var selectedIndex(get, set):Int;
var selectedItem(get, set):Dynamic;
function applyDefault():Void;
function pauseEvents():Void;
function resumeEvents():Void;
}
class DropDownHandler implements IDropDownHandler {
private var _dropdown:DropDown;
public function new(dropdown:DropDown) {
_dropdown = dropdown;
}
public var component(get, null):Component;
private function get_component():Component {
return null;
}
public function prepare(wrapper:Box) {
}
public function reset() {
}
public var selectedIndex(get, set):Int;
private function get_selectedIndex():Int {
return -1;
}
private function set_selectedIndex(value:Int):Int {
return value;
}
public var selectedItem(get, set):Dynamic;
private function get_selectedItem():Dynamic {
return null;
}
private function set_selectedItem(value:Dynamic):Dynamic {
return value;
}
public function applyDefault() {
}
private var eventsPaused:Bool = false;
public function pauseEvents() {
eventsPaused = true;
}
public function resumeEvents() {
Toolkit.callLater(function() {
eventsPaused = false;
});
}
}
@:access(haxe.ui.core.Component)
private class ListDropDownHandler extends DropDownHandler {
private var _listview:ListView;
private override function get_component():Component {
createListView();
return _listview;
}
public override function reset() {
if (_listview != null) {
_listview.dataSource = _dropdown.dataSource;
/*
_listview.unregisterEvent(UIEvent.CHANGE, onListChange); // TODO: not great!
selectedIndex = _cachedSelectedIndex;
_listview.registerEvent(UIEvent.CHANGE, onListChange); // TODO: not great!
*/
}
}
public override function prepare(wrapper:Box) {
var itemCount = 4;
if (_dropdown.dropdownSize != null) {
itemCount = _dropdown.dropdownSize;
}
if (_listview.dataSource != null && _listview.dataSource.size < itemCount) {
itemCount = _listview.dataSource.size;
}
if (itemCount > 0 && _dropdown.dropdownHeight == null) {
_listview.itemCount = itemCount;
}
_listview.syncComponentValidation();
if (_dropdown.dropdownWidth == null) {
wrapper.syncComponentValidation();
var offset:Float = 0;
if (wrapper.layout != null) {
offset = wrapper.layout.paddingLeft + wrapper.layout.paddingRight;
}
if (wrapper.style != null && wrapper.style.marginLeft != null) {
offset += wrapper.style.marginLeft;
}
if (wrapper.style != null && wrapper.style.marginRight != null) {
offset += wrapper.style.marginRight;
}
_listview.width = _dropdown.width - offset;
} else {
_listview.width = _dropdown.dropdownWidth;
}
if (_dropdown.dropdownHeight != null) {
_listview.height = _dropdown.dropdownHeight;
}
var selectedIndex = _dropdown.selectedIndex;
if (_dropdown.dataSource != null && _dropdown.text != null && selectedIndex < 0) {
var text = _dropdown.text;
var itemIndex = indexOfItem(text);
if (itemIndex != -1) {
selectedIndex = itemIndex;
}
}
//Screen.instance.addComponent(_listview);
_listview.unregisterEvent(UIEvent.CHANGE, onListChange); // TODO: not great!
_listview.selectedIndex = selectedIndex;
_listview.syncComponentValidation();
_listview.registerEvent(UIEvent.CHANGE, onListChange); // TODO: not great!
}
private var _cachedSelectedIndex:Int = -1;
private override function get_selectedIndex():Int{
if (_listview == null) {
return _cachedSelectedIndex;
}
return _listview.selectedIndex;
}
private override function set_selectedIndex(value:Int):Int {
var data = null;
var dispatchChanged:Bool = false;
if (_listview != null && _cachedSelectedIndex != value) {
_cachedSelectedIndex = value;
_listview.selectedIndex = value;
} else if (_cachedSelectedIndex != value) {
_cachedSelectedIndex = value;
if (_dropdown.dataSource != null && value >= 0 && value < _dropdown.dataSource.size) {
data = _dropdown.dataSource.get(value);
}
dispatchChanged = true;
}
if (_dropdown.dataSource != null && value >= 0 && value < _dropdown.dataSource.size) {
var data = _dropdown.dataSource.get(value);
var itemRenderer = _dropdown.findComponent(ItemRenderer);
if (itemRenderer == null) {
var text = null;
if (Type.typeof(data) == TObject) {
text = data.text;
if (text == null) {
text = data.value;
}
} else {
text = Std.string(data);
}
_dropdown.text = text;
} else {
itemRenderer.data = data;
}
}
if (dispatchChanged) {
var event = new UIEvent(UIEvent.CHANGE, false, data);
event.value = Variant.fromDynamic(data);
_dropdown.dispatch(event);
_dropdown.dispatch(new UIEvent(UIEvent.PROPERTY_CHANGE, false, "selectedItem"));
}
return value;
}
private function indexOfItem(text:String):Int {
if (text == null) {
return -1;
}
var index = -1;
if (_dropdown.dataSource != null) {
for (i in 0..._dropdown.dataSource.size) {
var item:Dynamic = _dropdown.dataSource.get(i);
if (item == text || item.value == text || item.text == text) {
index = i;
}
}
}
if (index == -1 && _dropdown.dataSource != null) {
var expr = LocaleManager.instance.findBindingExpr(_dropdown, "text");
if (expr != null) {
text = expr;
for (i in 0..._dropdown.dataSource.size) {
var item:Dynamic = _dropdown.dataSource.get(i);
if (item == text || item.value == text || item.text == text) {
index = i;
}
}
}
}
return index;
}
private override function get_selectedItem():Dynamic {
if (_listview == null) {
if (_cachedSelectedIndex >= 0 && _cachedSelectedIndex < _dropdown.dataSource.size) {
var data = _dropdown.dataSource.get(_cachedSelectedIndex);
return data;
} else {
return _cachedSelectedItem;
}
}
return _listview.selectedItem;
}
private var _cachedSelectedItem:Dynamic = null;
private override function set_selectedItem(value:Dynamic):Dynamic {
if (value == null) {
return value;
}
var text = null;
if (Type.typeof(value) == TObject) {
text = value.text;
if (text == null) {
text = value.value;
}
} else {
text = Std.string(value);
}
var index:Int = indexOfItem(text);
selectedIndex = index;
return value;
}
private function createListView() {
if (_listview == null) {
_listview = new ListView();
if (_dropdown.itemRenderer != null) {
_listview.addComponent(_dropdown.itemRenderer);
}
_listview.componentTabIndex = -1;
_listview.virtual = _dropdown.virtual;
_listview.dataSource = _dropdown.dataSource;
if (_cachedSelectedIndex != -1) {
_listview.selectedIndex = _cachedSelectedIndex;
}
if (_dropdown.id != null) {
_listview.addClass(_dropdown.id + "-listview");
_listview.id = _dropdown.id + "_listview";
}
_listview.registerEvent(ActionEvent.ACTION_START, function(e:ActionEvent) {
switch (e.action) {
case ActionType.BACK | ActionType.CANCEL:
e.cancel();
cast(_dropdown._internalEvents, DropDownEvents).hideDropDown();
case ActionType.CONFIRM | ActionType.PRESS:
e.cancel();
applySelection();
case _:
}
});
}
}
private function onListChange(event:UIEvent) {
if ((event.relatedEvent is MouseEvent)) {
applySelection();
}
}
private function applySelection() {
if (_listview.selectedItem == null) {
return;
}
var currentHover:Component = _listview.findComponent(":hover", null, true, "css");
if (currentHover != null) { // since the dropdown list dissapears it doesnt recvieve a mouse out (sometimes)
currentHover.removeClass(":hover");
}
var selectedItem = _listview.selectedItem;
var itemRenderer = _dropdown.findComponent(ItemRenderer);
if (itemRenderer == null) {
var text = null;
if (Type.typeof(selectedItem) == TObject) {
text = _listview.selectedItem.text;
if (text == null) {
text = _listview.selectedItem.value;
}
} else {
text = Std.string(selectedItem);
}
_dropdown.text = text;
} else {
itemRenderer.data = selectedItem;
}
//_dropdown.selectedIndex = _listview.selectedIndex;
if (eventsPaused == false) {
cast(_dropdown._internalEvents, DropDownEvents).hideDropDown();
var event = new UIEvent(UIEvent.CHANGE, false, selectedItem);
event.value = Variant.fromDynamic(selectedItem);
_dropdown.dispatch(event);
_dropdown.dispatch(new UIEvent(UIEvent.PROPERTY_CHANGE, false, "selectedItem"));
}
}
public override function applyDefault() {
var indexToSelect = 0;
if (_cachedSelectedItem != null) {
var index = indexOfItem(_cachedSelectedItem);
if (index != -1) {
indexToSelect = index;
}
} else if (_cachedSelectedIndex != -1) {
indexToSelect = _cachedSelectedIndex;
}
_dropdown.selectedIndex = indexToSelect;
}
}
@:dox(hide)
@:access(haxe.ui.core.Component)
class CalendarDropDownHandler extends DropDownHandler {
private var _calendar:CalendarView;
private override function get_component():Component {
if (_calendar == null) {
_calendar = new CalendarView();
if (_dropdown.id != null) {
_calendar.addClass(_dropdown.id + "-calendar");
_calendar.id = _dropdown.id + "_calendar";
}
_calendar.registerEvent(UIEvent.CHANGE, onCalendarChange);
}
return _calendar;
}
public override function prepare(wrapper:Box) {
if (_dropdown.dropdownWidth != null) {
_calendar.width = _dropdown.dropdownWidth;
}
if (_dropdown.dropdownHeight != null) {
_calendar.height = _dropdown.dropdownHeight;
}
if (_cachedSelectedDate != null) {
_calendar.unregisterEvent(UIEvent.CHANGE, onCalendarChange); // TODO: not great!
_calendar.selectedDate = _cachedSelectedDate;
_calendar.registerEvent(UIEvent.CHANGE, onCalendarChange); // TODO: not great!
}
//Screen.instance.addComponent(_calendar);
_calendar.syncComponentValidation();
}
private var _cachedSelectedDate:Date = null;
private override function get_selectedItem():Dynamic {
if (_calendar == null) {
return _cachedSelectedDate;
}
return _calendar.selectedDate;
}
private override function set_selectedItem(value:Dynamic):Dynamic {
if (value == null) {
return value;
}
var date:Date = null;
if ((value is Date)) {
date = cast(value, Date);
} else {
date = Date.fromString(Std.string(value));
}
if (_calendar != null && date != null) {
if (date.toString() == _calendar.selectedDate.toString()) {
_dropdown.text = DateTools.format(date, Formats.dateFormatShort);
return value;
}
_cachedSelectedDate = date;
_calendar.selectedDate = date;
//_dropdown.text = DateTools.format(date, DATE_FORMAT);
} else if (date != null) {
_cachedSelectedDate = date;
_dropdown.text = DateTools.format(_cachedSelectedDate, Formats.dateFormatShort);
}
return value;
}
public function onCalendarChange(event:UIEvent) {
if (_calendar.selectedDate == null) {
return;
}
_cachedSelectedDate = _calendar.selectedDate;
_dropdown.text = DateTools.format(_calendar.selectedDate, Formats.dateFormatShort);
cast(_dropdown._internalEvents, DropDownEvents).hideDropDown();
_dropdown.dispatch(new UIEvent(UIEvent.CHANGE, false, _calendar.selectedDate));
_dropdown.dispatch(new UIEvent(UIEvent.PROPERTY_CHANGE, false, "selectedItem"));
}
public override function applyDefault() {
var now = Date.now();
_dropdown.selectedItem = now;
}
}
//***********************************************************************************************************
// Events
//***********************************************************************************************************
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
class DropDownEvents extends ButtonEvents {
private var _dropdown:DropDown;
public function new(dropdown:DropDown) {
super(dropdown);
_dropdown = dropdown;
}
public override function register() {
super.register();
registerEvent(MouseEvent.MOUSE_DOWN, onClick);
registerEvent(UIEvent.HIDDEN, onHidden);
}
public override function unregister() {
super.unregister();
unregisterEvent(MouseEvent.MOUSE_DOWN, onClick);
unregisterEvent(UIEvent.HIDDEN, onHidden);
}
private function onClick(event:MouseEvent) {
_dropdown.selected = !_dropdown.selected;
if (_dropdown.selected == true) {
showDropDown();
} else {
hideDropDown();
}
}
private function onHidden(_) {
hideDropDown();
}
private override function onMouseClick(event:MouseEvent) {
// do nothing
}
private var _overlay:Component = null;
private var _wrapper:Box = null;
private var _dropdownOpen:Bool = false;
public function showDropDown() {
if (_dropdownOpen) {
return;
}
var handler:IDropDownHandler = cast(_dropdown._compositeBuilder, DropDownBuilder).handler;
if (handler == null) {
return;
}
if (_wrapper == null) {
_wrapper = new Box();
_wrapper.addClass("popup");
_wrapper.addClass("dropdown-popup");
if (_button.id != null) {
_wrapper.addClass(_button.id + "-popup");
_wrapper.id = _button.id + "_popup";
} else {
_wrapper.id = "dropdown_popup";
}
_wrapper.styleNames = _dropdown.handlerStyleNames;
if (_dropdown.searchable == true) {
var searchContainer = new VBox();
searchContainer.id = "dropdown-search-container";
searchContainer.addClass("dropdown-search-container");
searchContainer.scriptAccess = false;
var searchFieldContainer = new HBox();
searchFieldContainer.id = "dropdown-search-field-container";
searchFieldContainer.addClass("dropdown-search-field-container");
searchFieldContainer.scriptAccess = false;
searchFieldContainer.addComponent(searchField);
var searchFieldSeparator = new Component();
searchFieldSeparator.id = "dropdown-search-field-separator";
searchFieldSeparator.addClass("dropdown-search-field-separator");
searchFieldSeparator.scriptAccess = false;
searchContainer.addComponent(searchFieldContainer);
searchContainer.addComponent(searchFieldSeparator);
searchContainer.addComponent(handler.component);
_wrapper.addComponent(searchContainer);
} else {
_wrapper.addComponent(handler.component);
}
if (_dropdown.style.fontSize != null && handler.component.customStyle.fontSize != _dropdown.style.fontSize) {
handler.component.customStyle.fontSize = _dropdown.style.fontSize;
}
var filler = new Component();
filler.includeInLayout = false;
filler.addClass("dropdown-filler");
filler.id = "dropdown-filler";
_wrapper.addComponent(filler);
}
var componentOffset = _dropdown.getComponentOffset();
if (_dropdown.style.mode != null && _dropdown.style.mode == "mobile") {
if (_overlay == null) {
_overlay = new Component();
_overlay.id = "modal-background";
_overlay.addClass("modal-background");
_overlay.percentWidth = _overlay.percentHeight = 100;
}
Screen.instance.addComponent(_overlay);
Screen.instance.addComponent(_wrapper);
handler.prepare(_wrapper);
_wrapper.syncComponentValidation();
_wrapper.validateNow();
_wrapper.left = (Screen.instance.actualWidth / 2) - (_wrapper.actualComponentWidth / 2);
_wrapper.top = (Screen.instance.actualHeight / 2) - (_wrapper.actualComponentHeight / 2);
} else {
_wrapper.left = _dropdown.screenLeft + componentOffset.x;
_wrapper.top = _dropdown.screenTop + (_dropdown.actualComponentHeight - Toolkit.scaleY) + componentOffset.y;
_wrapper.hide();
Screen.instance.addComponent(_wrapper);
handler.prepare(_wrapper);
_wrapper.syncComponentValidation();
_wrapper.validateNow();
var marginLeft = 0.0;
if (_wrapper.style != null && _wrapper.style.marginLeft != null) {
marginLeft = _wrapper.style.marginLeft;
}
var marginTop = 0.0;
if (_wrapper.style != null && _wrapper.style.marginTop != null) {
marginTop = _wrapper.style.marginTop;
}
_wrapper.left += marginLeft;
_wrapper.top += marginTop;
_wrapper.show();
var popupToRight = false;
var popupFromBottom = false;
if (_dropdown.dropdownHorizontalPosition == "right" || _wrapper.screenLeft + _wrapper.actualComponentWidth > Screen.instance.actualWidth) {
var left = _wrapper.screenLeft - _wrapper.actualComponentWidth + _dropdown.actualComponentWidth;
_wrapper.left = left >= 0 ? left : (Screen.instance.actualWidth / 2) - (_wrapper.actualComponentWidth / 2);
popupToRight = true;
}
_wrapper.removeClass("popup-from-bottom");
if (_dropdown.dropdownVerticalPosition == "bottom" || _wrapper.screenTop + _wrapper.actualComponentHeight > Screen.instance.actualHeight) {
_wrapper.addClass("popup-from-bottom");
_wrapper.top = (_dropdown.screenTop - _wrapper.actualComponentHeight) + Toolkit.scaleY;
_wrapper.syncComponentValidation();
_wrapper.validateNow();
var marginLeft = 0.0;
if (_wrapper.style != null && _wrapper.style.marginLeft != null) {
marginLeft = _wrapper.style.marginLeft;
}
var marginTop = 0.0;
if (_wrapper.style != null && _wrapper.style.marginTop != null) {
marginTop = _wrapper.style.marginTop;
}
_wrapper.left += marginLeft;
_wrapper.top += marginTop;
popupFromBottom = true;
}
var cx = _wrapper.width - _dropdown.width;
var filler:Component = _wrapper.findComponent("dropdown-filler", false);
if (cx > 0 && filler != null) {
_wrapper.addClass("dropdown-popup-expanded");
if (_wrapper.style != null) {
if (_wrapper.style.marginLeft != null) {
cx += _wrapper.style.marginLeft;
}
if (_wrapper.style.marginRight != null) {
cx += _wrapper.style.marginRight;
}
}
filler.width = cx;
if (popupToRight) {
cx -= Toolkit.scaleX;
filler.left = Toolkit.scaleX;
} else {
cx += Toolkit.scaleX;
filler.left = _wrapper.width - cx;
}
if (popupFromBottom) {
filler.top = _wrapper.actualComponentHeight - Toolkit.scaleY;
} else {
filler.top = 0;
}
if (filler.style != null && (filler.style.hidden == null || filler.style.hidden == false)) {
filler.hidden = false;
}
} else if (filler != null) {
filler.hidden = true;
_wrapper.removeClass("dropdown-popup-expanded");
}
}
Screen.instance.registerEvent(MouseEvent.MOUSE_DOWN, onScreenMouseDown);
Screen.instance.registerEvent(MouseEvent.RIGHT_MOUSE_DOWN, onScreenMouseDown);
registerEvent(UIEvent.MOVE, onDropDownMoved);
_dropdownOpen = true;
var event = new UIEvent(UIEvent.OPEN);
_dropdown.dispatch(event);
}
private function onDropDownMoved(_) {
hideDropDown();
}
public function createSearchField():TextField {
var searchField = new TextField();
searchField.registerEvent(ActionEvent.ACTION_START, function(e:ActionEvent) {
if (e.action == ActionType.DOWN || e.action == ActionType.UP ||
e.action == ActionType.CONFIRM || e.action == ActionType.PRESS ||
e.action == ActionType.BACK || e.action == ActionType.CANCEL) {
var builder:DropDownBuilder = cast(_dropdown._compositeBuilder, DropDownBuilder);
builder.handler.component.dispatch(e);
}
});
searchField.id = "dropdown-search-field";
searchField.addClass("dropdown-search-field");
searchField.placeholder = _dropdown.searchPrompt;
searchField.scriptAccess = false;
searchField.registerEvent(UIEvent.CHANGE, onSearchChange);
return searchField;
}
private var _searchField:TextField = null;
public var searchField(get, set):TextField;
private function get_searchField():TextField {
if (_searchField == null) {
_searchField = createSearchField();
}
return _searchField;
}
private function set_searchField(value:TextField):TextField {
_searchField = value;
return value;
}
private var _lastSearchTerm = "";
private function onSearchChange(event:UIEvent) {
if (_wrapper == null) {
return;
}
var searchField = _wrapper.findComponent("dropdown-search-field", TextField);
if (searchField == null) {
return;
}
var selectedItem = _dropdown.selectedItem;
var searchTerm = searchField.text;
if (searchTerm == null || StringTools.trim(searchTerm).length == 0) {
searchTerm = "";
}
if (_lastSearchTerm == searchTerm) {
return;
}
_lastSearchTerm = searchTerm;
if (searchTerm.length == 0) {
_dropdown.dataSource.clearFilter();
} else {
_dropdown.dataSource.filter(function(index, data) {
var v = data.text;
return Std.string(v).toLowerCase().indexOf(searchTerm.toLowerCase()) > -1;
});
}
var handler:IDropDownHandler = cast(_dropdown._compositeBuilder, DropDownBuilder).handler;
if (handler == null) {
return;
}
handler.prepare(_wrapper);
if (selectedItem != null) {
handler.pauseEvents();
_dropdown.selectedItem = selectedItem;
handler.resumeEvents();
}
}
public function hideDropDown() {
if (!_dropdownOpen) {
return;
}
var handler:IDropDownHandler = cast(_dropdown._compositeBuilder, DropDownBuilder).handler;
if (handler == null) {
return;
}
if (_wrapper != null) {
var searchField = _wrapper.findComponent("dropdown-search-field", TextField);
if (searchField != null) {
searchField.focus = false;
}
}
if (_overlay != null) {
Screen.instance.removeComponent(_overlay);
_overlay = null;
}
_dropdown.selected = false;
if (_wrapper != null) {
Screen.instance.removeComponent(_wrapper, false);
}
Screen.instance.unregisterEvent(MouseEvent.MOUSE_DOWN, onScreenMouseDown);
Screen.instance.unregisterEvent(MouseEvent.RIGHT_MOUSE_DOWN, onScreenMouseDown);
unregisterEvent(UIEvent.MOVE, onDropDownMoved);
_dropdownOpen = false;
var event = new UIEvent(UIEvent.CLOSE);
_dropdown.dispatch(event);
}
private function onScreenMouseDown(event:MouseEvent) {
if (_wrapper == null) {
return;
}
if (_wrapper.hitTest(event.screenX, event.screenY) == true) {
return;
}
var componentOffset = _dropdown.getComponentOffset();
if (_dropdown.hitTest(event.screenX - componentOffset.x, event.screenY - componentOffset.y) == true) {
return;
}
hideDropDown();
}
// override and do nothing
private override function dispatchChanged() {
}
private override function release() {
if (_down == true) {
super.release();
if (_dropdown.selected == true) {
showDropDown();
} else {
hideDropDown();
}
}
}
}
//***********************************************************************************************************
// Composite Builder
//***********************************************************************************************************
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.components.DropDownEvents)
class DropDownBuilder extends ButtonBuilder {
public static var HANDLER_MAP:Map<String, String> = new Map<String, String>();
private var _dropdown:DropDown;
@:noCompletion private var _handler:IDropDownHandler;
public function new(dropdown:DropDown) {
super(dropdown);
_dropdown = dropdown;
HANDLER_MAP.set("list", Type.getClassName(ListDropDownHandler));
HANDLER_MAP.set("date", Type.getClassName(CalendarDropDownHandler));
}
public var handler(get, null):IDropDownHandler;
private function get_handler():IDropDownHandler {
if (_handler == null) {
var handlerClass:String = DropDownBuilder.HANDLER_MAP.get(_dropdown.type);
if (handlerClass == null) {
handlerClass = _dropdown.type;
}
_handler = Type.createInstance(Type.resolveClass(handlerClass), [_dropdown]);
}
return _handler;
}
public override function onReady() {
super.onReady();
if (_dropdown.text == null) {
handler.applyDefault();
}
}
public override function create() {
_dropdown.toggle = true;
if (_dropdown.findComponent(ItemRenderer) == null) {
_dropdown.addComponent(new BasicItemRenderer());
}
}
public override function destroy() {
var events:DropDownEvents = cast(_dropdown._internalEvents, DropDownEvents);
events.hideDropDown();
if (events._wrapper != null) {
Screen.instance.removeComponent(events._wrapper);
events._wrapper = null;
}
}
public override function addComponent(child:Component):Component {
if ((child is ItemRenderer)) {
_dropdown.itemRenderer = cast child.cloneComponent();
if (child.id == "dropdown-renderer" || child.id == "dropdownRenderer") {
return child;
}
}
return super.addComponent(child);
}
@:access(haxe.ui.core.Screen)
public function onThemeChanged() {
if (_handler != null) {
Screen.instance.invalidateChildren(_handler.component);
Screen.instance.onThemeChangedChildren(_handler.component);
}
}
}