haxe/ui/containers/TableView.hx
package haxe.ui.containers;
import haxe.ui.actions.ActionType;
import haxe.ui.behaviours.Behaviour;
import haxe.ui.behaviours.DataBehaviour;
import haxe.ui.behaviours.DefaultBehaviour;
import haxe.ui.behaviours.LayoutBehaviour;
import haxe.ui.components.Column;
import haxe.ui.components.Label;
import haxe.ui.components.VerticalScroll;
import haxe.ui.constants.SelectionMode;
import haxe.ui.containers.ScrollView.ScrollViewBuilder;
import haxe.ui.containers.ScrollView.ScrollViewEvents;
import haxe.ui.core.Component;
import haxe.ui.core.IDataComponent;
import haxe.ui.core.InteractiveComponent;
import haxe.ui.core.ItemRenderer;
import haxe.ui.data.ArrayDataSource;
import haxe.ui.data.DataSource;
import haxe.ui.data.transformation.NativeTypeTransformer;
import haxe.ui.events.ActionEvent;
import haxe.ui.events.ItemEvent;
import haxe.ui.events.MouseEvent;
import haxe.ui.events.ScrollEvent;
import haxe.ui.events.SortEvent;
import haxe.ui.events.UIEvent;
import haxe.ui.geom.Rectangle;
import haxe.ui.geom.Size;
import haxe.ui.layouts.LayoutFactory;
import haxe.ui.layouts.VerticalVirtualLayout;
import haxe.ui.util.MathUtil;
import haxe.ui.util.Variant;
@:composite(Events, Builder, Layout)
class TableView extends ScrollView implements IDataComponent implements IVirtualContainer {
//***********************************************************************************************************
// Public API
//***********************************************************************************************************
@:clonable @:behaviour(DataSourceBehaviour) public var dataSource:DataSource<Dynamic>;
@:clonable @:behaviour(LayoutBehaviour, -1) public var itemWidth:Float;
@:clonable @:behaviour(LayoutBehaviour, -1) public var itemHeight:Float;
@:clonable @:behaviour(LayoutBehaviour, -1) public var itemCount:Int;
@:clonable @:behaviour(LayoutBehaviour, false) public var variableItemSize:Bool;
@:clonable @:behaviour(SelectedIndexBehaviour, -1) public var selectedIndex:Int;
@:clonable @:behaviour(SelectedItemBehaviour) public var selectedItem:Dynamic;
@:clonable @:behaviour(SelectedIndicesBehaviour) public var selectedIndices:Array<Int>;
@:clonable @:behaviour(SelectedItemsBehaviour) public var selectedItems:Array<Dynamic>;
@:clonable @:behaviour(SelectionModeBehaviour, SelectionMode.ONE_ITEM) public var selectionMode:SelectionMode;
@:clonable @:behaviour(DefaultBehaviour, 500) public var longPressSelectionTime:Int; //ms
@:clonable @:behaviour(GetHeader) public var header:Component;
@:call(ClearTable) public function clearContents(clearHeader:Bool = false):Void;
@:call(AddColumn) public function addColumn(text:String):Column;
@:call(RemoveColumn) public function removeColumn(text:String):Void;
@:event(ItemEvent.COMPONENT_EVENT) public var onComponentEvent:ItemEvent->Void;
@:event(SortEvent.SORT_CHANGED) public var onSortChanged:SortEvent->Void;
private var _itemRendererClass:Class<ItemRenderer>;
@:clonable public var itemRendererClass(get, set):Class<ItemRenderer>;
private function get_itemRendererClass():Class<ItemRenderer> {
return _itemRendererClass;
}
private function set_itemRendererClass(value:Class<ItemRenderer>):Class<ItemRenderer> {
if (_itemRendererClass != value) {
_itemRendererClass = value;
invalidateComponentLayout();
}
return value;
}
private var _itemRenderer:ItemRenderer;
@:clonable public var itemRenderer(get, set):ItemRenderer;
private function get_itemRenderer():ItemRenderer {
return _itemRenderer;
}
private function set_itemRenderer(value:ItemRenderer):ItemRenderer {
if (_itemRenderer != value) {
_itemRenderer = value;
invalidateComponentLayout();
}
return value;
}
// TODO: tableview doesnt work properly with native / hybrid scrollers because the header isnt "sticky" and will
// move with the rest of the scroller - disable for now
private override function get_isNativeScroller():Bool {
return false;
}
private override function get_isHybridScroller():Bool {
return false;
}
}
private class CompoundItemRenderer extends ItemRenderer {
public function new() {
super();
this.layout = LayoutFactory.createFromName("horizontal");
removeClass("itemrenderer");
}
private override function onDataChanged(data:Dynamic) {
super.onDataChanged(data);
var renderers = findComponents(ItemRenderer);
for (r in renderers) {
r.data = data;
}
}
private override function _onItemMouseOver(event:MouseEvent) {
addClass(":hover");
for (i in findComponents(ItemRenderer)) {
i.addClass(":hover");
}
}
private override function _onItemMouseOut(event:MouseEvent) {
removeClass(":hover");
for (i in findComponents(ItemRenderer)) {
i.removeClass(":hover");
}
}
private override function _onItemMouseDown(event:MouseEvent) {
addClass(":down");
for (i in findComponents(ItemRenderer)) {
i.addClass(":down");
}
}
private override function _onItemMouseUp(event:MouseEvent) {
removeClass(":down");
for (i in findComponents(ItemRenderer)) {
i.removeClass(":down");
}
}
}
//***********************************************************************************************************
// Events
//***********************************************************************************************************
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
private class Events extends ScrollViewEvents {
private var _tableview:TableView;
public function new(tableview:TableView) {
super(tableview);
//tableview.clip = true;
_tableview = tableview;
}
public override function register() {
super.register();
registerEvent(ScrollEvent.CHANGE, onScrollChange);
registerEvent(UIEvent.RENDERER_CREATED, onRendererCreated);
registerEvent(UIEvent.RENDERER_DESTROYED, onRendererDestroyed);
}
public override function unregister() {
super.unregister();
unregisterEvent(ScrollEvent.CHANGE, onScrollChange);
unregisterEvent(UIEvent.RENDERER_CREATED, onRendererCreated);
unregisterEvent(UIEvent.RENDERER_DESTROYED, onRendererDestroyed);
}
private function onScrollChange(e:ScrollEvent) {
if (_tableview.virtual) {
_tableview.invalidateComponentLayout();
}
var header = _tableview.findComponent(Header, true);
if (header == null) {
return;
}
var vscroll = _tableview.findComponent(VerticalScroll);
if (vscroll != null && vscroll.hidden == false) {
header.addClass("scrolling");
header.invalidateComponent(true);
} else {
header.removeClass("scrolling");
header.invalidateComponent(true);
}
var usableWidth = _tableview.layout.usableWidth;
var rc:Rectangle = new Rectangle(_tableview.hscrollPos + 0, 1, usableWidth, header.height);
header.componentClipRect = rc;
}
private function onRendererCreated(e:UIEvent) {
var instance:ItemRenderer = cast(e.data, ItemRenderer);
instance.registerEvent(MouseEvent.MOUSE_DOWN, onRendererMouseDown);
instance.registerEvent(MouseEvent.CLICK, onRendererClick);
instance.registerEvent(MouseEvent.RIGHT_CLICK, onRendererClick);
if (_tableview.selectedIndices.indexOf(instance.itemIndex) != -1) {
var builder:Builder = cast(_tableview._compositeBuilder, Builder);
builder.addItemRendererClass(instance, ":selected");
}
}
private function onRendererDestroyed(e:UIEvent) {
var instance:ItemRenderer = cast(e.data, ItemRenderer);
instance.unregisterEvent(MouseEvent.MOUSE_DOWN, onRendererMouseDown);
instance.unregisterEvent(MouseEvent.CLICK, onRendererClick);
instance.unregisterEvent(MouseEvent.RIGHT_CLICK, onRendererClick);
if (_tableview.selectedIndices.indexOf(instance.itemIndex) != -1) {
var builder:Builder = cast(_tableview._compositeBuilder, Builder);
builder.addItemRendererClass(instance, ":selected", false);
}
}
private function onRendererMouseDown(e:MouseEvent) {
_tableview.focus = true;
switch (_tableview.selectionMode) {
case SelectionMode.MULTIPLE_LONG_PRESS:
if (_tableview.selectedIndices.length == 0) {
startLongPressSelection(e);
}
default:
}
}
private function startLongPressSelection(e:MouseEvent) {
var timerClick:Timer = null;
var currentMouseX:Float = e.screenX;
var currentMouseY:Float = e.screenY;
var renderer:ItemRenderer = cast(e.target, ItemRenderer);
var __onMouseMove:MouseEvent->Void = null;
var __onMouseUp:MouseEvent->Void = null;
var __onMouseClick:MouseEvent->Void = null;
__onMouseMove = function (_e:MouseEvent) {
currentMouseX = _e.screenX;
currentMouseY = _e.screenY;
}
__onMouseUp = function (_e:MouseEvent) {
if (timerClick != null) {
timerClick.stop();
timerClick = null;
}
renderer.screen.unregisterEvent(MouseEvent.MOUSE_MOVE, __onMouseMove);
renderer.screen.unregisterEvent(MouseEvent.MOUSE_UP, __onMouseUp);
}
__onMouseClick = function(_e:MouseEvent) {
_e.cancel(); //Avoid toggleSelection onRendererClick method
renderer.unregisterEvent(MouseEvent.CLICK, __onMouseClick);
}
renderer.screen.registerEvent(MouseEvent.MOUSE_MOVE, __onMouseMove);
renderer.screen.registerEvent(MouseEvent.MOUSE_UP, __onMouseUp);
timerClick = Timer.delay(function(){
if (timerClick != null) {
timerClick = null;
if (renderer.hitTest(currentMouseX, currentMouseY) &&
MathUtil.distance(e.screenX, e.screenY, currentMouseX, currentMouseY) < 2 * Toolkit.pixelsPerRem) {
toggleSelection(renderer);
renderer.registerEvent(MouseEvent.CLICK, __onMouseClick, 1);
}
}
}, _tableview.longPressSelectionTime);
}
private override function onContainerEventsStatusChanged() {
super.onContainerEventsStatusChanged();
if (_containerEventsPaused == true) {
_tableview.findComponent("tableview-contents", Component, true, "css").removeClass(":hover", true, true);
} else if (_lastMousePos != null) {
/* TODO: may be ill concieved, doesnt look good on mobile
var items = _tableview.findComponentsUnderPoint(_lastMousePos.x, _lastMousePos.y, ItemRenderer);
for (i in items) {
i.addClass(":hover", true, true);
}
*/
}
}
private function onRendererClick(e:MouseEvent) {
if (_containerEventsPaused == true) {
return;
}
var components = e.target.findComponentsUnderPoint(e.screenX, e.screenY);
for (component in components) {
if ((component is InteractiveComponent) && cast(component, InteractiveComponent).allowInteraction == true) {
return;
}
}
var renderer:ItemRenderer = cast(e.target, ItemRenderer);
switch (_tableview.selectionMode) {
case SelectionMode.DISABLED:
case SelectionMode.ONE_ITEM:
_tableview.selectedIndex = renderer.itemIndex;
case SelectionMode.ONE_ITEM_REPEATED:
_tableview.selectedIndices = [renderer.itemIndex];
case SelectionMode.MULTIPLE, SelectionMode.MULTIPLE_MODIFIER_KEY, SelectionMode.MULTIPLE_CLICK_MODIFIER_KEY:
if (e.ctrlKey == true) {
toggleSelection(renderer);
} else if (e.shiftKey == true) {
var selectedIndices:Array<Int> = _tableview.selectedIndices;
var fromIndex:Int = selectedIndices.length > 0 ? selectedIndices[selectedIndices.length - 1]: 0;
var toIndex:Int = renderer.itemIndex;
if (fromIndex < toIndex) {
for (i in selectedIndices) {
if (i < fromIndex) {
fromIndex = i;
}
}
} else {
var tmp:Int = fromIndex;
fromIndex = toIndex;
toIndex = tmp;
}
selectRange(fromIndex, toIndex);
} else if (_tableview.selectionMode == SelectionMode.MULTIPLE || _tableview.selectionMode == SelectionMode.MULTIPLE_CLICK_MODIFIER_KEY) {
_tableview.selectedIndex = renderer.itemIndex;
}
case SelectionMode.MULTIPLE_LONG_PRESS:
var selectedIndices:Array<Int> = _tableview.selectedIndices;
if (selectedIndices.length > 0) {
toggleSelection(renderer);
}
default:
//Nothing
}
}
private function toggleSelection(renderer:ItemRenderer) {
var itemIndex:Int = renderer.itemIndex;
var selectedIndices = _tableview.selectedIndices.copy();
var index:Int;
if ((index = selectedIndices.indexOf(itemIndex)) == -1) {
selectedIndices.push(itemIndex);
} else {
selectedIndices.splice(index, 1);
}
_tableview.selectedIndices = selectedIndices;
}
private function selectRange(fromIndex:Int, toIndex:Int) {
_tableview.selectedIndices = [for (i in fromIndex...toIndex + 1) i];
}
private override function onActionStart(event:ActionEvent) {
switch (event.action) {
case ActionType.DOWN:
if (_tableview.selectedIndex < 0) {
_tableview.selectedIndex = 0;
} else {
var n:Int = _tableview.selectedIndex;
n++;
if (n > _tableview.dataSource.size - 1) {
n = 0;
}
_tableview.selectedIndex = n;
}
event.repeater = true;
case ActionType.UP:
if (_tableview.selectedIndex < 0) {
_tableview.selectedIndex = _tableview.dataSource.size - 1;
} else {
var n:Int = _tableview.selectedIndex;
n--;
if (n < 0) {
n = _tableview.selectedIndex = _tableview.dataSource.size - 1;
}
_tableview.selectedIndex = n;
}
event.repeater = true;
case ActionType.LEFT:
_scrollview.hscrollPos -= 10;
event.repeater = true;
case ActionType.RIGHT:
_scrollview.hscrollPos += 10;
event.repeater = true;
case _:
}
}
}
//***********************************************************************************************************
// Composite Builder
//***********************************************************************************************************
@:dox(hide) @:noCompletion
private class Builder extends ScrollViewBuilder {
private var _tableview:TableView;
private var _header:Header;
public function new(tableview:TableView) {
super(tableview);
_tableview = tableview;
}
public override function create() {
createContentContainer(_tableview.virtual ? "absolute" : "vertical");
}
public override function onInitialize() {
if (_header == null) {
return;
}
if (_tableview.itemRenderer == null) {
buildDefaultRenderer();
} else {
fillExistingRenderer();
}
}
public override function onReady() {
if (_header == null) {
return;
}
if (_tableview.itemRenderer == null) {
buildDefaultRenderer();
} else {
fillExistingRenderer();
}
_component.invalidateComponentLayout();
}
private override function createContentContainer(layoutName:String) {
if (_contents == null) {
super.createContentContainer(layoutName);
_contents.addClass("tableview-contents");
}
}
public override function addComponent(child:Component):Component {
var r = null;
if ((child is ItemRenderer)) {
var itemRenderer = _tableview.itemRenderer;
if (itemRenderer == null) {
itemRenderer = new CompoundItemRenderer();
_tableview.itemRenderer = itemRenderer;
}
itemRenderer.addComponent(child);
return child;
} else if ((child is Header)) {
_header = cast(child, Header);
_header.registerEvent(UIEvent.COMPONENT_ADDED, onColumnAdded);
_header.registerEvent(SortEvent.SORT_CHANGED, onSortChanged);
_header.registerEvent(UIEvent.READY, onHeaderReady);
// if the header is hidden, it means its child columns
// wont have a size since layouts will be skipped for them
// this means that all rows will end up with zero-width cells
// a work around for this is to set header height to 0, and
// show it
if (_header.hidden) {
_header.customStyle.height = 0;
_header.show();
}
/*
if (_tableview.itemRenderer == null) {
buildDefaultRenderer();
} else {
fillExistingRenderer();
}
*/
r = null;
} else {
r = super.addComponent(child);
}
return r;
}
private function onColumnAdded(e) {
if (_tableview.itemRenderer == null) {
buildDefaultRenderer();
} else {
fillExistingRenderer();
}
_component.invalidateComponentLayout();
}
private function onSortChanged(e:SortEvent) {
_tableview.dispatch(e);
if (e.canceled == false) {
var column = cast(e.target, Column);
var field = column.id;
if (column.sortField != null) {
field = column.sortField;
}
_tableview.dataSource.sort(field, e.direction);
}
}
private function onHeaderReady(_) {
if (_tableview.itemRenderer == null) {
buildDefaultRenderer();
} else {
fillExistingRenderer();
}
}
public override function removeComponent(child:Component, dispose:Bool = true, invalidate:Bool = true):Component {
if ((child is Header) == true) {
_header = null;
return null;
}
return super.removeComponent(child, dispose, invalidate);
}
private function createRenderer(column:Column):ItemRenderer {
var itemRenderer:ItemRenderer = null;
if (_tableview.itemRendererClass == null) {
itemRenderer = new ItemRenderer();
} else {
itemRenderer = Type.createInstance(_tableview.itemRendererClass, []);
}
if (itemRenderer.childComponents.length == 0) {
var label = new Label();
label.id = column.id;
label.percentWidth = 100;
label.verticalAlign = "center";
if (column.styleString != null) {
label.styleString = column.styleString;
}
itemRenderer.addComponent(label);
}
itemRenderer.styleNames = "column-" + column.id;
return itemRenderer;
}
public function buildDefaultRenderer() {
var r = new CompoundItemRenderer();
if (_header != null) {
for (column in _header.findComponents(Column)) {
if (column.id == null) {
continue;
}
var itemRenderer = createRenderer(column);
if (itemRenderer.id == null) {
itemRenderer.id = column.id + "Renderer";
}
r.addComponent(itemRenderer);
}
}
_tableview.itemRenderer = r;
}
public function fillExistingRenderer() {
var i = 0;
for (column in _header.findComponents(Column)) {
if (column.id == null) {
continue;
}
var existing = _tableview.itemRenderer.findComponent(column.id, ItemRenderer, true);
if (existing == null) {
var temp = _tableview.itemRenderer.findComponent(column.id, Component, true);
if (temp != null) {
if ((temp is ItemRenderer)) {
existing = cast(temp, ItemRenderer);
} else {
existing = temp.findAncestor(ItemRenderer);
}
existing.styleNames = "column-" + column.id;
_tableview.itemRenderer.setComponentIndex(existing, i);
} else {
var itemRenderer = createRenderer(column);
itemRenderer.styleNames = "column-" + column.id;
_tableview.itemRenderer.addComponentAt(itemRenderer, i);
}
} else {
existing.styleNames = "column-" + column.id;
_tableview.itemRenderer.setComponentIndex(existing, i);
}
i++;
}
/* NOT SURE WHAT THIS IS, OR WHY ITS HERE, IT SEEMS LIKE TEST CODE THAT
* HAS BEEN LEFT IN, COMMENTING FOR NOW, BUT LOOK TO REMOVE LATER IF
* NO USE HAS BEEN FOUND
var data = _component.findComponent("tableview-contents", Box, true, "css");
if (data != null) {
for (item in data.childComponents) {
for (column in _header.childComponents) {
var existing = item.findComponent(column.id, ItemRenderer, true);
if (existing == null) {
var temp = _tableview.itemRenderer.findComponent(column.id, Component, true);
var renderer:ItemRenderer = null;
if ((temp is ItemRenderer)) {
renderer = cast(temp, ItemRenderer);
} else {
renderer = temp.findAncestor(ItemRenderer);
}
var index = _tableview.itemRenderer.getComponentIndex(renderer);
var instance = renderer.cloneComponent();
if (index < 0) {
item.addComponent(instance);
} else {
item.addComponentAt(instance, index);
}
}
}
}
}
*/
}
private override function verticalConstraintModifier():Float {
if (_header == null) {
return 0;
}
return _header.height;
}
public override function onVirtualChanged() {
_contents.layoutName = _tableview.virtual ? "absolute" : "vertical";
}
private override function get_virtualHorizontal():Bool {
return false;
}
public function addItemRendererClass(child:Component, className:String, add:Bool = true) {
child.walkComponents(function(c) {
if ((c is ItemRenderer)) {
if (add == true) {
c.addClass(className);
} else {
c.removeClass(className);
}
} else {
c.invalidateComponentStyle(); // we do want to invalidate the other components incase the css rule applies indirectly
}
return true;
});
}
private function ensureVisible(itemToEnsure:ItemRenderer) {
if (itemToEnsure != null && _tableview.virtual == false) {
var vscroll:VerticalScroll = _tableview.findComponent(VerticalScroll);
if (vscroll != null) {
var vpos:Float = vscroll.pos;
var contents:Component = _tableview.findComponent("tableview-contents", "css");
if (itemToEnsure.top + itemToEnsure.height > vpos + contents.componentClipRect.height) {
vscroll.pos = ((itemToEnsure.top + itemToEnsure.height) - contents.componentClipRect.height);
} else if (itemToEnsure.top < vpos) {
vscroll.pos = itemToEnsure.top;
}
}
}
}
@:access(haxe.ui.layouts.VerticalVirtualLayout)
private function ensureVirtualItemVisible(index:Int) {
var vscroll:VerticalScroll = _tableview.findComponent(VerticalScroll);
if (vscroll != null) {
var layout = cast(_tableview.layout, VerticalVirtualLayout);
var itemHeight = layout.itemHeight;
var itemTop = index * itemHeight;
var vpos:Float = vscroll.pos;
var contents:Component = _tableview.findComponent("tableview-contents", "css");
if (itemTop + itemHeight > vpos + contents.componentClipRect.height) {
vscroll.pos = ((itemTop + itemHeight) - contents.componentClipRect.height);
} else if (itemTop < vpos) {
vscroll.pos = itemTop;
}
}
}
}
//***********************************************************************************************************
// Composite Layout
//***********************************************************************************************************
private class Layout extends VerticalVirtualLayout {
private override function itemClass(index:Int, data:Dynamic):Class<ItemRenderer> {
return CompoundItemRenderer;
}
public override function repositionChildren() {
var header = findComponent(Header, true);
if (header == null) {
return;
}
super.repositionChildren();
header.left = paddingLeft + borderSize; // + marginLeft(header);
header.top = paddingTop + borderSize; // + marginTop(header);
var vscroll = _component.findComponent(VerticalScroll);
if (vscroll != null && vscroll.hidden == false) {
header.addClass("scrolling");
header.invalidateComponent(true);
} else {
header.removeClass("scrolling");
header.invalidateComponent(true);
}
var rc:Rectangle = new Rectangle(cast(_component, ScrollView).hscrollPos + 0, 1, usableWidth, header.height);
header.componentClipRect = rc;
var data = findComponent("tableview-contents", Box, true, "css");
if (data != null) {
//data.lockLayout(true);
for (item in data.childComponents) {
var headerChildComponents = header.findComponents(Column);
for (column in headerChildComponents) {
if (column.id == null) {
continue;
}
var isLast = (headerChildComponents.indexOf(column) == (headerChildComponents.length - 1));
var itemRenderer = item.findComponent(column.id, Component);
if (itemRenderer != null && (itemRenderer is ItemRenderer) == false) {
itemRenderer = itemRenderer.findAncestor(ItemRenderer);
}
if (itemRenderer != null) {
itemRenderer.percentWidth = null;
if (isLast == false) {
itemRenderer.width = column.width - item.layout.horizontalSpacing;
} else {
itemRenderer.width = column.width;
}
}
}
}
var modifier = 0;
if (header.height > 0) {
modifier = 1;
}
data.left = paddingLeft + borderSize;
data.top = header.top + header.height - modifier;
data.componentWidth = header.width;
//data.unlockLayout(true);
}
}
private override function resizeChildren() {
var header = findComponent(Header, true);
if (header == null) {
return;
}
super.resizeChildren();
}
private override function verticalConstraintModifier():Float {
var header = findComponent(Header, true);
if (header == null) {
return 0;
}
return header.height;
}
public override function calcAutoSize(exclusions:Array<Component> = null):Size {
var size = super.calcAutoSize();
size.height += 1;
return size;
}
}
//***********************************************************************************************************
// Behaviours
//***********************************************************************************************************
@:dox(hide) @:noCompletion
private class DataSourceBehaviour extends DataBehaviour {
private var _firstPass:Bool = true; // may not have any any children at first, so a height of 1 causes loads of renderers to be created
public override function set(value:Variant) {
super.set(value);
var dataSource:DataSource<Dynamic> = _value;
if (dataSource != null) {
if (dataSource.transformer == null) {
dataSource.transformer = new NativeTypeTransformer();
}
dataSource.onDataSourceChange = function() {
_component.invalidateComponentLayout();
if (_firstPass == true) {
//_component.syncComponentValidation();
_firstPass = false;
_component.invalidateComponentLayout();
}
}
_component.invalidateComponentLayout();
} else {
_component.invalidateComponentLayout();
}
}
public override function get():Variant {
if (_value == null || _value.isNull) {
_value = new ArrayDataSource<Dynamic>();
set(_value);
}
return _value;
}
}
@:dox(hide) @:noCompletion
private class SelectedIndexBehaviour extends Behaviour {
public override function get():Variant {
var tableView:TableView = cast(_component, TableView);
var selectedIndices:Array<Int> = tableView.selectedIndices;
return selectedIndices != null && selectedIndices.length > 0 ? selectedIndices[selectedIndices.length - 1] : -1;
}
public override function set(value:Variant) {
var tableView:TableView = cast(_component, TableView);
tableView.selectedIndices = value != -1 ? [value] : null;
}
}
@:dox(hide) @:noCompletion
private class SelectedItemBehaviour extends Behaviour {
public override function getDynamic():Dynamic {
var tableView:TableView = cast(_component, TableView);
var selectedIndices:Array<Int> = tableView.selectedIndices;
return selectedIndices.length > 0 ? tableView.dataSource.get(selectedIndices[selectedIndices.length - 1]) : null;
}
public override function set(value:Variant) {
var tableView:TableView = cast(_component, TableView);
var index:Int = tableView.dataSource.indexOf(value);
if (index != -1 && tableView.selectedIndices.indexOf(index) == -1) {
tableView.selectedIndices = [index];
}
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
private class SelectedIndicesBehaviour extends DataBehaviour {
public override function get():Variant {
return _value.isNull ? [] : _value;
}
private override function validateData() {
var tableView:TableView = cast(_component, TableView);
var selectedIndices:Array<Int> = tableView.selectedIndices;
var contents:Component = _component.findComponent("scrollview-contents", false, "css");
var itemToEnsure:ItemRenderer = null;
var builder:Builder = cast(_component._compositeBuilder, Builder);
for (child in contents.childComponents) {
if (selectedIndices.indexOf(cast(child, ItemRenderer).itemIndex) != -1) {
itemToEnsure = cast(child, ItemRenderer);
builder.addItemRendererClass(child, ":selected");
} else {
builder.addItemRendererClass(child, ":selected", false);
}
}
if (tableView.virtual) {
for (i in selectedIndices) {
@:privateAccess builder.ensureVirtualItemVisible(i);
}
} else {
@:privateAccess builder.ensureVisible(itemToEnsure);
}
if (tableView.selectedIndex != -1 && tableView.selectedIndices.length != 0) {
_component.dispatch(new UIEvent(UIEvent.CHANGE));
}
}
}
@:dox(hide) @:noCompletion
private class SelectedItemsBehaviour extends Behaviour {
public override function get():Variant {
var tableView:TableView = cast(_component, TableView);
var selectedIndices:Array<Int> = tableView.selectedIndices;
if (selectedIndices != null && selectedIndices.length > 0) {
var selectedItems:Array<Dynamic> = [];
for (i in selectedIndices) {
if ((i < 0) || (i >= tableView.dataSource.size)) {
continue;
}
var data:Dynamic = tableView.dataSource.get(i);
selectedItems.push(data);
}
return selectedItems;
} else {
return [];
}
}
public override function set(value:Variant) {
var tableView:TableView = cast(_component, TableView);
var selectedItems:Array<Dynamic> = value;
if (selectedItems != null && selectedItems.length > 0) {
var selectedIndices:Array<Int> = [];
var index:Int;
for (item in selectedItems) {
if ((index = tableView.dataSource.indexOf(item)) != -1) {
selectedIndices.push(index);
}
}
tableView.selectedIndices = selectedIndices;
} else {
tableView.selectedIndices = [];
}
}
}
@:dox(hide) @:noCompletion
private class SelectionModeBehaviour extends DataBehaviour {
private override function validateData() {
var tableView:TableView = cast(_component, TableView);
var selectedIndices:Array<Int> = tableView.selectedIndices;
if (selectedIndices.length == 0) {
return;
}
var selectionMode:SelectionMode = cast _value;
switch (selectionMode) {
case SelectionMode.DISABLED:
tableView.selectedIndices = null;
case SelectionMode.ONE_ITEM:
if (selectedIndices.length > 1) {
tableView.selectedIndices = [selectedIndices[0]];
}
default:
}
}
}
@:dox(hide) @:noCompletion
private class GetHeader extends DefaultBehaviour {
public override function get():Variant {
var header:Header = _component.findComponent(Header);
return header;
}
}
@:dox(hide) @:noCompletion
private class ClearTable extends Behaviour {
public override function call(param:Any = null):Variant {
if (param == true) {
if (cast(_component, TableView).itemRenderer != null) {
cast(_component, TableView).itemRenderer.removeAllComponents();
cast(_component, TableView).itemRenderer = null;
}
var header:Header = _component.findComponent(Header);
if (header != null) {
header.removeAllComponents();
_component.removeComponent(header);
}
}
var contents = _component.findComponent("tableview-contents", Box, true, "css");
if (contents != null) {
contents.removeAllComponents();
}
return null;
}
}
@:dox(hide) @:noCompletion
private class AddColumn extends Behaviour {
public override function call(param:Any = null):Variant {
var header:Header = _component.findComponent(Header);
if (header == null) {
header = new Header();
_component.addComponent(header);
}
var column = new Column();
column.text = param;
var columnId:String = param;
columnId = StringTools.replace(columnId, " ", "_");
columnId = StringTools.replace(columnId, "*", "");
column.id = columnId;
header.addComponent(column);
return column;
}
}
@:dox(hide) @:noCompletion
private class RemoveColumn extends Behaviour {
public override function call(param:Any = null):Variant {
var header:Header = _component.findComponent(Header);
if (header == null) {
return null;
}
for (c in header.findComponents(Column)) {
if (c.id == null) {
continue;
}
if (c.text == param) {
header.removeComponent(c);
break;
}
}
return null;
}
}