Added TableView cell/row selection mode

This commit is contained in:
Alexei Anoshenko 2022-01-14 17:20:04 -05:00
parent 15a11dc558
commit 1a4040bd00
19 changed files with 888 additions and 121 deletions

View File

@ -1,3 +1,7 @@
# v0.5.0
* Added HasFocus function to the View interface
# v0.4.0
* Added SetTitle and SetTitleColor function to the Session interface

View File

@ -615,12 +615,13 @@ function selectDropDownListItem(elementId, number) {
function listItemClickEvent(element, event) {
event.stopPropagation();
var selected = false;
if (element.classList) {
selected = (element.classList.contains("ruiListItemFocused") || element.classList.contains("ruiListItemSelected"));
} else {
selected = element.className.indexOf("ruiListItemFocused") >= 0 || element.className.indexOf("ruiListItemSelected") >= 0;
}
const focusStyle = getListFocusedItemStyle(element);
const blurStyle = getListSelectedItemStyle(element);
selected = (element.classList.contains(focusStyle) || element.classList.contains(blurStyle));
}
var list = element.parentNode.parentNode
if (list) {
@ -640,18 +641,27 @@ function getListItemNumber(itemId) {
}
}
function getStyleAttribute(element, attr, defValue) {
var result = element.getAttribute(attr);
if (result) {
return result;
}
return defValue;
}
function getListFocusedItemStyle(element) {
return getStyleAttribute(element, "data-focusitemstyle", "ruiListItemFocused");
}
function getListSelectedItemStyle(element) {
return getStyleAttribute(element, "data-bluritemstyle", "ruiListItemSelected");
}
function selectListItem(element, item, needSendMessage) {
var currentId = element.getAttribute("data-current");
var message;
var focusStyle = element.getAttribute("data-focusitemstyle");
var blurStyle = element.getAttribute("data-bluritemstyle");
if (!focusStyle) {
focusStyle = "ruiListItemFocused"
}
if (!blurStyle) {
blurStyle = "ruiListItemSelected"
}
const focusStyle = getListFocusedItemStyle(element);
const blurStyle = getListSelectedItemStyle(element);
if (currentId) {
var current = document.getElementById(currentId);
@ -801,24 +811,29 @@ function findBottomListItem(list, x, y) {
return result
}
function listViewKeyDownEvent(element, event) {
var key;
function getKey(event) {
if (event.key) {
key = event.key;
} else if (event.keyCode) {
return event.key;
}
if (event.keyCode) {
switch (event.keyCode) {
case 13: key = "Enter"; break;
case 32: key = " "; break;
case 33: key = "PageUp"; break;
case 34: key = "PageDown"; break;
case 35: key = "End"; break;
case 36: key = "Home"; break;
case 37: key = "ArrowLeft"; break;
case 38: key = "ArrowUp"; break;
case 39: key = "ArrowRight"; break;
case 40: key = "ArrowDown"; break;
case 13: return "Enter";
case 32: return " ";
case 33: return "PageUp";
case 34: return "PageDown";
case 35: return "End";
case 36: return "Home";
case 37: return "ArrowLeft";
case 38: return "ArrowUp";
case 39: return "ArrowRight";
case 40: return "ArrowDown";
}
}
}
function listViewKeyDownEvent(element, event) {
const key = getKey(event);
if (key) {
var currentId = element.getAttribute("data-current");
var current
@ -885,21 +900,10 @@ function listViewFocusEvent(element, event) {
if (currentId) {
var current = document.getElementById(currentId);
if (current) {
var focusStyle = element.getAttribute("data-focusitemstyle");
var blurStyle = element.getAttribute("data-bluritemstyle");
if (!focusStyle) {
focusStyle = "ruiListItemFocused"
}
if (!blurStyle) {
blurStyle = "ruiListItemSelected"
}
if (current.classList) {
current.classList.remove(blurStyle);
current.classList.add(focusStyle);
} else { // IE < 10
current.className = "ruiListItem " + focusStyle;
}
current.classList.remove(getListSelectedItemStyle(element));
current.classList.add(getListFocusedItemStyle(element));
}
}
}
}
@ -909,20 +913,9 @@ function listViewBlurEvent(element, event) {
if (currentId) {
var current = document.getElementById(currentId);
if (current) {
var focusStyle = element.getAttribute("data-focusitemstyle");
var blurStyle = element.getAttribute("data-bluritemstyle");
if (!focusStyle) {
focusStyle = "ruiListItemFocused"
}
if (!blurStyle) {
blurStyle = "ruiListItemSelected"
}
if (current.classList) {
current.classList.remove(focusStyle);
current.classList.add(blurStyle);
} else { // IE < 10
current.className = "ruiListItem " + blurStyle;
current.classList.remove(getListFocusedItemStyle(element));
current.classList.add(getListSelectedItemStyle(element));
}
}
}
@ -1372,4 +1365,388 @@ function setTitleColor(color) {
function detailsEvent(element) {
sendMessage("details-open{session=" + sessionID + ",id=" + element.id + ",open=" + (element.open ? "1}" : "0}"));
}
function getTableFocusedItemStyle(element) {
return getStyleAttribute(element, "data-focusitemstyle", "ruiCurrentTableCellFocused");
}
function getTableSelectedItemStyle(element) {
return getStyleAttribute(element, "data-bluritemstyle", "ruiCurrentTableCell");
}
function tableViewFocusEvent(element, event) {
var currentId = element.getAttribute("data-current");
if (currentId) {
var current = document.getElementById(currentId);
if (current) {
if (current.classList) {
current.classList.remove(getTableSelectedItemStyle(element));
current.classList.add(getTableFocusedItemStyle(element));
}
}
}
}
function tableViewBlurEvent(element, event) {
var currentId = element.getAttribute("data-current");
if (currentId) {
var current = document.getElementById(currentId);
if (current && current.classList) {
current.classList.remove(getTableFocusedItemStyle(element));
current.classList.add(getTableSelectedItemStyle(element));
}
}
}
function setTableCellCursor(element, row, column) {
const cellID = element.id + "-" + row + "-" + column;
var cell = document.getElementById(cellID);
if (!cell) {
return false;
}
const focusStyle = getTableFocusedItemStyle(element);
const oldCellID = element.getAttribute("data-current");
if (oldCellID) {
const oldCell = document.getElementById(oldCellID);
if (oldCell && oldCell.classList) {
oldCell.classList.remove(focusStyle);
oldCell.classList.remove(getTableSelectedItemStyle(element));
}
}
cell.classList.add(focusStyle);
element.setAttribute("data-current", cellID);
sendMessage("currentCell{session=" + sessionID + ",id=" + element.id +
",row=" + row + ",column=" + column + "}");
return true;
}
function moveTableCellCursor(element, row, column, dr, dc) {
const rows = element.getAttribute("data-rows");
if (!rows) {
return;
}
const columns = element.getAttribute("data-columns");
if (!columns) {
return;
}
const rowCount = parseInt(rows);
const columnCount = parseInt(columns);
row += dr;
column += dc;
while (row >= 0 && row < rowCount && column >= 0 && column < columnCount) {
if (setTableCellCursor(element, row, column)) {
return;
} else if (dr == 0) {
var r2 = row - 1;
while (r2 >= 0) {
if (setTableCellCursor(element, r2, column)) {
return;
}
r2--;
}
} else if (dc == 0) {
var c2 = column - 1;
while (c2 >= 0) {
if (setTableCellCursor(element, row, c2)) {
return;
}
c2--;
}
}
row += dr;
column += dc;
}
}
function tableViewCellKeyDownEvent(element, event) {
const key = getKey(event);
if (key) {
const currentId = element.getAttribute("data-current");
if (currentId) {
const elements = currentId.split("-");
if (elements.length >= 3) {
const row = parseInt(elements[1], 10)
const column = parseInt(elements[2], 10)
switch (key) {
case " ":
case "Enter":
sendMessage("cellClick{session=" + sessionID + ",id=" + element.id +
",row=" + row + ",column=" + column + "}");
break;
case "ArrowLeft":
moveTableCellCursor(element, row, column, 0, -1)
break;
case "ArrowRight":
moveTableCellCursor(element, row, column, 0, 1)
break;
case "ArrowDown":
moveTableCellCursor(element, row, column, 1, 0)
break;
case "ArrowUp":
moveTableCellCursor(element, row, column, -1, 0)
break;
case "Home":
// TODO
break;
case "End":
/*var newRow = rowCount-1;
while (newRow > row) {
if (setTableRowCursor(element, newRow)) {
break;
}
newRow--;
}*/
// TODO
break;
case "PageUp":
// TODO
break;
case "PageDown":
// TODO
break;
default:
return;
}
event.stopPropagation();
event.preventDefault();
return;
}
}
switch (key) {
case "ArrowLeft":
case "ArrowRight":
case "ArrowDown":
case "ArrowUp":
case "Home":
case "End":
case "PageUp":
case "PageDown":
const rows = element.getAttribute("data-rows");
const columns = element.getAttribute("data-columns");
if (rows && columns) {
const rowCount = parseInt(rows);
const columnCount = parseInt(rows);
row = 0;
while (row < rowCount) {
column = 0;
while (columns < columnCount) {
if (setTableCellCursor(element, row, column)) {
event.stopPropagation();
event.preventDefault();
return;
}
column++;
}
row++;
}
}
break;
default:
return;
}
}
event.stopPropagation();
event.preventDefault();
}
function setTableRowCursor(element, row) {
const tableRowID = element.id + "-" + row;
var tableRow = document.getElementById(tableRowID);
if (!tableRow) {
return false;
}
const focusStyle = getTableFocusedItemStyle(element);
const oldRowID = element.getAttribute("data-current");
if (oldRowID) {
const oldRow = document.getElementById(oldRowID);
if (oldRow && oldRow.classList) {
oldRow.classList.remove(focusStyle);
oldRow.classList.remove(getTableSelectedItemStyle(element));
}
}
tableRow.classList.add(focusStyle);
element.setAttribute("data-current", tableRowID);
sendMessage("currentRow{session=" + sessionID + ",id=" + element.id + ",row=" + row + "}");
return true;
}
function moveTableRowCursor(element, row, dr) {
const rows = element.getAttribute("data-rows");
if (!rows) {
return;
}
const rowCount = parseInt(rows);
row += dr;
while (row >= 0 && row < rowCount) {
if (setTableRowCursor(element, row)) {
return;
}
row += dr;
}
}
function tableViewRowKeyDownEvent(element, event) {
const key = getKey(event);
if (key) {
const currentId = element.getAttribute("data-current");
if (currentId) {
const elements = currentId.split("-");
if (elements.length >= 2) {
const row = parseInt(elements[1], 10);
switch (key) {
case " ":
case "Enter":
sendMessage("rowClick{session=" + sessionID + ",id=" + element.id + ",row=" + row + "}");
break;
case "ArrowDown":
moveTableRowCursor(element, row, 1)
break;
case "ArrowUp":
moveTableRowCursor(element, row, -1)
break;
case "Home":
var newRow = 0;
while (newRow < row) {
if (setTableRowCursor(element, newRow)) {
break;
}
newRow++;
}
break;
case "End":
var newRow = rowCount-1;
while (newRow > row) {
if (setTableRowCursor(element, newRow)) {
break;
}
newRow--;
}
break;
case "PageUp":
// TODO
break;
case "PageDown":
// TODO
break;
default:
return;
}
event.stopPropagation();
event.preventDefault();
return;
}
}
switch (key) {
case "ArrowLeft":
case "ArrowRight":
case "ArrowDown":
case "ArrowUp":
case "Home":
case "End":
case "PageUp":
case "PageDown":
const rows = element.getAttribute("data-rows");
if (rows) {
const rowCount = parseInt(rows);
row = 0;
while (row < rowCount) {
if (setTableRowCursor(element, row)) {
break;
}
row++;
}
}
break;
default:
return;
}
}
event.stopPropagation();
event.preventDefault();
}
function tableCellClickEvent(element, event) {
event.preventDefault();
const elements = element.id.split("-");
if (elements.length < 3) {
return
}
const tableID = elements[0];
const row = parseInt(elements[1], 10);
const column = parseInt(elements[2], 10);
const table = document.getElementById(tableID);
if (table) {
const selection = table.getAttribute("data-selection");
if (selection == "cell") {
const currentID = table.getAttribute("data-current");
if (!currentID || currentID != element.ID) {
setTableCellCursor(table, row, column)
}
}
}
sendMessage("cellClick{session=" + sessionID + ",id=" + tableID +
",row=" + row + ",column=" + column + "}");
}
function tableRowClickEvent(element, event) {
event.preventDefault();
const elements = element.id.split("-");
if (elements.length < 2) {
return
}
const tableID = elements[0];
const row = parseInt(elements[1], 10);
const table = document.getElementById(tableID);
if (table) {
const selection = table.getAttribute("data-selection");
if (selection == "cell") {
const currentID = table.getAttribute("data-current");
if (!currentID || currentID != element.ID) {
setTableRowCursor(table, row)
}
}
}
sendMessage("rowClick{session=" + sessionID + ",id=" + tableID + ",row=" + row + "}");
}

View File

@ -148,11 +148,15 @@ func (customView *CustomViewData) Scroll() Frame {
return customView.superView.Scroll()
}
func (customView *CustomViewData) HasFocus() bool {
return customView.superView.HasFocus()
}
func (customView *CustomViewData) onResize(self View, x, y, width, height float64) {
customView.superView.onResize(customView.superView, x, y, width, height)
}
func (customView *CustomViewData) onItemResize(self View, index int, x, y, width, height float64) {
func (customView *CustomViewData) onItemResize(self View, index string, x, y, width, height float64) {
customView.superView.onItemResize(customView.superView, index, x, y, width, height)
}

View File

@ -44,6 +44,10 @@ func (picker *datePickerData) Init(session Session) {
picker.dateChangedListeners = []func(DatePicker, time.Time){}
}
func (picker *datePickerData) Focusable() bool {
return true
}
func (picker *datePickerData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag {

View File

@ -213,24 +213,32 @@ theme {
text-color = @ruiPopupTextColor,
radius = 4px,
shadow = _{spread-radius=4px, blur=16px, color=#80808080},
}
},
ruiPopupTitle {
background-color = @ruiPopupTitleColor,
text-color = @ruiPopupTitleTextColor,
min-height = 24px,
}
},
ruiMessageText {
padding-left = 64px,
padding-right = 64px,
padding-top = 32px,
padding-bottom = 32px,
}
},
ruiPopupMenuItem {
padding-top = 4px,
padding-bottom = 4px,
padding-left = 8px,
padding-right = 8px,
}
},
ruiCurrentTableCell {
background-color=@ruiSelectedColor,
text-color=@ruiSelectedTextColor,
},
ruiCurrentTableCellFocused {
background-color=@ruiHighlightColor,
text-color=@ruiHighlightTextColor,
},
],
}

View File

@ -32,6 +32,8 @@ GridLayout {
DropDownList { row = 5, column = 1, id = tableFootStyle, current = 0, items = ["none", "tableFoot1", "rui.Params"]},
Checkbox { row = 6, column = 0:1, id = tableRowStyle, content = "Row style" },
Checkbox { row = 7, column = 0:1, id = tableColumnStyle, content = "Column style" },
TextView { row = 8, text = "Selection mode" },
DropDownList { row = 8, column = 1, id = tableSelectionMode, current = 0, items = ["none", "cell", "row"]},
]
}
]
@ -93,6 +95,10 @@ func createTableViewDemo(session rui.Session) rui.View {
}
}
rui.Set(view, "tableSelectionMode", rui.DropDownEvent, func(list rui.DropDownList, number int) {
rui.Set(view, "demoTableView1", rui.SelectionMode, number)
})
rui.Set(view, "tableCellGap", rui.DropDownEvent, func(list rui.DropDownList, number int) {
if number == 0 {
rui.Set(view, "demoTableView1", rui.Gap, rui.Px(0))

View File

@ -39,6 +39,10 @@ func (list *dropDownListData) Init(session Session) {
list.dropDownListener = []func(DropDownList, int){}
}
func (list *dropDownListData) Focusable() bool {
return true
}
func (list *dropDownListData) Remove(tag string) {
list.remove(strings.ToLower(tag))
}

View File

@ -63,6 +63,10 @@ func (edit *editViewData) Init(session Session) {
edit.tag = "EditView"
}
func (edit *editViewData) Focusable() bool {
return true
}
func (edit *editViewData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag {

View File

@ -89,6 +89,10 @@ func (picker *filePickerData) Init(session Session) {
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){}
}
func (picker *filePickerData) Focusable() bool {
return true
}
func (picker *filePickerData) Files() []FileInfo {
return picker.files
}

View File

@ -140,11 +140,9 @@ func getFocusListeners(view View, subviewID string, tag string) []func(View) {
}
func focusEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range focusEvents {
if value := view.getRaw(tag); value != nil {
if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
}
if view.Focusable() {
for _, js := range focusEvents {
buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
}
}
}

View File

@ -1083,14 +1083,12 @@ func (listView *listViewData) htmlSubviews(self View, buffer *strings.Builder) {
func (listView *listViewData) handleCommand(self View, command string, data DataObject) bool {
switch command {
case "itemSelected":
if text, ok := data.PropertyValue(`number`); ok {
if number, err := strconv.Atoi(text); err == nil {
listView.properties[Current] = number
for _, listener := range listView.selectedListeners {
listener(listView, number)
}
listView.propertyChangedEvent(Current)
if number, ok := dataIntProperty(data, `number`); ok {
listView.properties[Current] = number
for _, listener := range listView.selectedListeners {
listener(listView, number)
}
listView.propertyChangedEvent(Current)
}
case "itemUnselected":
@ -1162,9 +1160,14 @@ func (listView *listViewData) onItemClick() {
}
}
func (listView *listViewData) onItemResize(self View, index int, x, y, width, height float64) {
if index >= 0 && index < len(listView.itemFrame) {
listView.itemFrame[index] = Frame{Left: x, Top: y, Width: width, Height: height}
func (listView *listViewData) onItemResize(self View, index string, x, y, width, height float64) {
n, err := strconv.Atoi(index)
if err != nil {
ErrorLog(err.Error())
} else if n >= 0 && n < len(listView.itemFrame) {
listView.itemFrame[n] = Frame{Left: x, Top: y, Width: width, Height: height}
} else {
ErrorLogF(`Invalid ListView item index: %d`, n)
}
}

View File

@ -168,6 +168,10 @@ func (player *mediaPlayerData) Init(session Session) {
player.tag = "MediaPlayer"
}
func (player *mediaPlayerData) Focusable() bool {
return true
}
func (player *mediaPlayerData) Remove(tag string) {
player.remove(strings.ToLower(tag))
}

View File

@ -50,6 +50,10 @@ func (picker *numberPickerData) Init(session Session) {
picker.numberChangedListeners = []func(NumberPicker, float64){}
}
func (picker *numberPickerData) Focusable() bool {
return true
}
func (picker *numberPickerData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag {

View File

@ -18,7 +18,7 @@ func (view *viewData) onResize(self View, x, y, width, height float64) {
}
}
func (view *viewData) onItemResize(self View, index int, x, y, width, height float64) {
func (view *viewData) onItemResize(self View, index string, x, y, width, height float64) {
}
func (view *viewData) setFrameListener(tag string, value interface{}) bool {

View File

@ -378,14 +378,10 @@ func (session *sessionData) handleResize(data DataObject) {
}
if viewID, ok := obj.PropertyValue("id"); ok {
if n := strings.IndexRune(viewID, '-'); n > 0 {
if index, err := strconv.Atoi(viewID[n+1:]); err == nil {
if view := session.viewByHTMLID(viewID[:n]); view != nil {
view.onItemResize(view, index, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
} else {
ErrorLogF(`View with id == %s not found`, viewID[:n])
}
if view := session.viewByHTMLID(viewID[:n]); view != nil {
view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
} else {
ErrorLogF(`Invalid view id == %s not found`, viewID)
ErrorLogF(`View with id == %s not found`, viewID[:n])
}
} else if view := session.viewByHTMLID(viewID); view != nil {
view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))

View File

@ -214,11 +214,14 @@ type TableView interface {
View
ParanetView
ReloadTableData()
CellFrame(row, column int) Frame
getCurrent() CellIndex
}
type tableViewData struct {
viewData
cellViews []View
cellFrame []Frame
cellSelectedListener, cellClickedListener []func(TableView, int, int)
rowSelectedListener, rowClickedListener []func(TableView, int)
current CellIndex
@ -245,6 +248,7 @@ func (table *tableViewData) Init(session Session) {
table.viewData.Init(session)
table.tag = "TableView"
table.cellViews = []View{}
table.cellFrame = []Frame{}
table.cellSelectedListener = []func(TableView, int, int){}
table.cellClickedListener = []func(TableView, int, int){}
table.rowSelectedListener = []func(TableView, int){}
@ -270,6 +274,10 @@ func (table *tableViewData) normalizeTag(tag string) string {
return tag
}
func (table *tableViewData) Focusable() bool {
return GetSelectionMode(table, "") != NoneSelection
}
func (table *tableViewData) Get(tag string) interface{} {
return table.get(table.normalizeTag(tag))
}
@ -312,6 +320,10 @@ func (table *tableViewData) remove(tag string) {
table.current.Column = -1
table.propertyChanged(tag)
case SelectionMode:
table.viewData.remove(tag)
table.propertyChanged(tag)
default:
table.viewData.remove(tag)
}
@ -486,14 +498,51 @@ func (table *tableViewData) set(tag string, value interface{}) bool {
}
case Current:
switch GetSelectionMode(table, "") {
case NoneSelection:
switch value := value.(type) {
case int:
table.current.Row = value
table.current.Column = -1
case CellSelection:
// TODO
case CellIndex:
table.current = value
case RowSelection:
// TODO
case DataObject:
if row, ok := dataIntProperty(value, "row"); ok {
table.current.Row = row
}
if column, ok := dataIntProperty(value, "column"); ok {
table.current.Column = column
}
case string:
if strings.Contains(value, ",") {
if values := strings.Split(value, ","); len(values) == 2 {
var n = []int{0, 0}
for i := 0; i < 2; i++ {
var err error
if n[i], err = strconv.Atoi(values[i]); err != nil {
ErrorLog(err.Error())
return false
}
}
table.current.Row = n[0]
table.current.Column = n[1]
} else {
notCompatibleType(tag, value)
}
} else {
n, err := strconv.Atoi(value)
if err != nil {
ErrorLog(err.Error())
return false
}
table.current.Row = n
table.current.Column = -1
}
default:
notCompatibleType(tag, value)
return false
}
default:
@ -524,11 +573,75 @@ func (table *tableViewData) propertyChanged(tag string) {
updateCSSProperty(htmlID, "border-spacing", gap.cssString("0"), session)
updateCSSProperty(htmlID, "border-collapse", "separate", session)
}
case SelectionMode:
htmlID := table.htmlID()
session := table.Session()
switch GetSelectionMode(table, "") {
case CellSelection:
updateProperty(htmlID, "tabindex", "0", session)
updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)", session)
updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)", session)
updateProperty(htmlID, "data-selection", "cell", session)
updateProperty(htmlID, "data-focusitemstyle", table.currentStyle(), session)
updateProperty(htmlID, "data-bluritemstyle", table.currentInactiveStyle(), session)
if table.current.Row >= 0 && table.current.Column >= 0 {
updateProperty(htmlID, "data-current", table.cellID(table.current.Row, table.current.Column), session)
} else {
removeProperty(htmlID, "data-current", session)
}
updateProperty(htmlID, "onkeydown", "tableViewCellKeyDownEvent(this, event)", session)
case RowSelection:
updateProperty(htmlID, "tabindex", "0", session)
updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)", session)
updateProperty(htmlID, "onblur", "tableViewBlurEvent(this, event)", session)
updateProperty(htmlID, "data-selection", "cell", session)
updateProperty(htmlID, "data-focusitemstyle", table.currentStyle(), session)
updateProperty(htmlID, "data-bluritemstyle", table.currentInactiveStyle(), session)
if table.current.Row >= 0 {
updateProperty(htmlID, "data-current", table.rowID(table.current.Row), session)
} else {
removeProperty(htmlID, "data-current", session)
}
updateProperty(htmlID, "onkeydown", "tableViewRowKeyDownEvent(this, event)", session)
default: // NoneSelection
for _, prop := range []string{"tabindex", "data-current", "onfocus", "onblur", "onkeydown", "data-selection"} {
removeProperty(htmlID, prop, session)
}
}
updateInnerHTML(htmlID, session)
}
}
table.propertyChangedEvent(tag)
}
func (table *tableViewData) currentStyle() string {
if value := table.getRaw(CurrentStyle); value != nil {
if style, ok := value.(string); ok {
if style, ok = table.session.resolveConstants(style); ok {
return style
}
}
}
return "ruiCurrentTableCellFocused"
}
func (table *tableViewData) currentInactiveStyle() string {
if value := table.getRaw(CurrentInactiveStyle); value != nil {
if style, ok := value.(string); ok {
if style, ok = table.session.resolveConstants(style); ok {
return style
}
}
}
return "ruiCurrentTableCell"
}
func (table *tableViewData) valueToCellListeners(value interface{}) []func(TableView, int, int) {
if value == nil {
return []func(TableView, int, int){}
@ -643,16 +756,69 @@ func (table *tableViewData) htmlTag() string {
return "table"
}
func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
table.cellViews = []View{}
func (table *tableViewData) rowID(index int) string {
return fmt.Sprintf("%s-%d", table.htmlID(), index)
}
content := table.getRaw(Content)
if content == nil {
return
func (table *tableViewData) cellID(row, column int) string {
return fmt.Sprintf("%s-%d-%d", table.htmlID(), row, column)
}
func (table *tableViewData) htmlProperties(self View, buffer *strings.Builder) {
if content := table.content(); content != nil {
buffer.WriteString(` data-rows="`)
buffer.WriteString(strconv.Itoa(content.RowCount()))
buffer.WriteString(`" data-columns="`)
buffer.WriteString(strconv.Itoa(content.ColumnCount()))
buffer.WriteRune('"')
}
adapter, ok := content.(TableAdapter)
if !ok {
if selectionMode := GetSelectionMode(table, ""); selectionMode != NoneSelection {
buffer.WriteString(` onfocus="tableViewFocusEvent(this, event)" onblur="tableViewBlurEvent(this, event)" data-focusitemstyle="`)
buffer.WriteString(table.currentStyle())
buffer.WriteString(`" data-bluritemstyle="`)
buffer.WriteString(table.currentInactiveStyle())
buffer.WriteRune('"')
switch selectionMode {
case RowSelection:
buffer.WriteString(` data-selection="row" onkeydown="tableViewRowKeyDownEvent(this, event)"`)
if table.current.Row >= 0 {
buffer.WriteString(` data-current="`)
buffer.WriteString(table.rowID(table.current.Row))
buffer.WriteRune('"')
}
case CellSelection:
buffer.WriteString(` data-selection="cell" onkeydown="tableViewCellKeyDownEvent(this, event)"`)
if table.current.Row >= 0 && table.current.Column >= 0 {
buffer.WriteString(` data-current="`)
buffer.WriteString(table.cellID(table.current.Row, table.current.Column))
buffer.WriteRune('"')
}
}
}
table.viewData.htmlProperties(self, buffer)
}
func (table *tableViewData) content() TableAdapter {
if content := table.getRaw(Content); content != nil {
if adapter, ok := content.(TableAdapter); ok {
return adapter
}
}
return nil
}
func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
table.cellViews = []View{}
table.cellFrame = []Frame{}
adapter := table.content()
if adapter == nil {
return
}
@ -662,10 +828,12 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
return
}
table.cellFrame = make([]Frame, rowCount*columnCount)
rowStyle := table.getRowStyle()
var cellStyle1 TableCellStyle = nil
if style, ok := content.(TableCellStyle); ok {
if style, ok := adapter.(TableCellStyle); ok {
cellStyle1 = style
}
@ -691,6 +859,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
view.Init(session)
ignorCells := []struct{ row, column int }{}
selectionMode := GetSelectionMode(table, "")
tableCSS := func(startRow, endRow int, cellTag string, cellBorder BorderProperty, cellPadding BoundsProperty) {
for row := startRow; row < endRow; row++ {
@ -706,14 +875,31 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
}
}
if cssBuilder.buffer.Len() > 0 {
buffer.WriteString(`<tr style="`)
buffer.WriteString(cssBuilder.buffer.String())
buffer.WriteString(`">`)
} else {
buffer.WriteString("<tr>")
buffer.WriteString(`<tr id="`)
buffer.WriteString(table.rowID(row))
buffer.WriteRune('"')
if selectionMode == RowSelection {
if row == table.current.Row {
buffer.WriteString(` class="`)
if table.HasFocus() {
buffer.WriteString(table.currentStyle())
} else {
buffer.WriteString(table.currentInactiveStyle())
}
buffer.WriteRune('"')
}
buffer.WriteString(` onclick="tableRowClickEvent(this, event)"`)
}
if cssBuilder.buffer.Len() > 0 {
buffer.WriteString(` style="`)
buffer.WriteString(cssBuilder.buffer.String())
buffer.WriteString(`"`)
}
buffer.WriteString(">")
for column := 0; column < columnCount; column++ {
ignore := false
for _, cell := range ignorCells {
@ -748,7 +934,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
return value
case string:
if value, ok = session.resolveConstants(value); ok {
if value, ok := session.resolveConstants(value); ok {
if n, err := strconv.Atoi(value); err == nil {
return n
}
@ -780,6 +966,23 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
buffer.WriteRune('<')
buffer.WriteString(cellTag)
buffer.WriteString(` id="`)
buffer.WriteString(table.cellID(row, column))
buffer.WriteString(`" class="ruiView`)
if selectionMode == CellSelection && row == table.current.Row && column == table.current.Column {
buffer.WriteRune(' ')
if table.HasFocus() {
buffer.WriteString(table.currentStyle())
} else {
buffer.WriteString(table.currentInactiveStyle())
}
}
buffer.WriteRune('"')
if selectionMode == CellSelection {
buffer.WriteString(` onclick="tableCellClickEvent(this, event)"`)
}
if columnSpan > 1 {
buffer.WriteString(` colspan="`)
@ -1087,6 +1290,10 @@ func (table *tableViewData) getCellBorder() BorderProperty {
return nil
}
func (table *tableViewData) getCurrent() CellIndex {
return table.current
}
func (table *tableViewData) cssStyle(self View, builder cssBuilder) {
table.viewData.cssViewStyle(builder, table.Session())
@ -1101,30 +1308,93 @@ func (table *tableViewData) cssStyle(self View, builder cssBuilder) {
}
func (table *tableViewData) ReloadTableData() {
if content := table.content(); content != nil {
updateProperty(table.htmlID(), "data-rows", strconv.Itoa(content.RowCount()), table.Session())
updateProperty(table.htmlID(), "data-columns", strconv.Itoa(content.ColumnCount()), table.Session())
}
updateInnerHTML(table.htmlID(), table.Session())
}
func (cell *tableCellView) Set(tag string, value interface{}) bool {
return cell.set(strings.ToLower(tag), value)
func (table *tableViewData) onItemResize(self View, index string, x, y, width, height float64) {
if n := strings.IndexRune(index, '-'); n > 0 {
if row, err := strconv.Atoi(index[:n]); err == nil {
if column, err := strconv.Atoi(index[n+1:]); err == nil {
if content := table.content(); content != nil {
i := row*content.ColumnCount() + column
if i < len(table.cellFrame) {
table.cellFrame[i].Left = x
table.cellFrame[i].Top = y
table.cellFrame[i].Width = width
table.cellFrame[i].Height = height
}
}
} else {
ErrorLog(err.Error())
}
} else {
ErrorLog(err.Error())
}
} else {
ErrorLogF(`Invalid cell index: %s`, index)
}
}
func (cell *tableCellView) set(tag string, value interface{}) bool {
switch tag {
case VerticalAlign:
tag = TableVerticalAlign
}
return cell.viewData.set(tag, value)
}
func (cell *tableCellView) cssStyle(self View, builder cssBuilder) {
session := cell.Session()
cell.viewData.cssViewStyle(builder, session)
if value, ok := enumProperty(cell, TableVerticalAlign, session, 0); ok {
builder.add("vertical-align", enumProperties[TableVerticalAlign].values[value])
func (table *tableViewData) CellFrame(row, column int) Frame {
if content := table.content(); content != nil {
i := row*content.ColumnCount() + column
if i < len(table.cellFrame) {
return table.cellFrame[i]
}
}
return Frame{}
}
func (table *tableViewData) Views() []View {
return table.cellViews
}
func (table *tableViewData) handleCommand(self View, command string, data DataObject) bool {
switch command {
case "currentRow":
if row, ok := dataIntProperty(data, "row"); ok && row != table.current.Row {
table.current.Row = row
for _, listener := range table.rowSelectedListener {
listener(table, row)
}
}
case "currentCell":
if row, ok := dataIntProperty(data, "row"); ok {
if column, ok := dataIntProperty(data, "column"); ok {
if row != table.current.Row || column != table.current.Column {
table.current.Row = row
table.current.Column = column
for _, listener := range table.cellSelectedListener {
listener(table, row, column)
}
}
}
}
case "rowClick":
if row, ok := dataIntProperty(data, "row"); ok {
for _, listener := range table.rowClickedListener {
listener(table, row)
}
}
case "cellClick":
if row, ok := dataIntProperty(data, "row"); ok {
if column, ok := dataIntProperty(data, "column"); ok {
for _, listener := range table.cellClickedListener {
listener(table, row, column)
}
}
}
default:
return table.viewData.handleCommand(self, command, data)
}
return true
}

View File

@ -1,5 +1,28 @@
package rui
import "strings"
func (cell *tableCellView) Set(tag string, value interface{}) bool {
return cell.set(strings.ToLower(tag), value)
}
func (cell *tableCellView) set(tag string, value interface{}) bool {
switch tag {
case VerticalAlign:
tag = TableVerticalAlign
}
return cell.viewData.set(tag, value)
}
func (cell *tableCellView) cssStyle(self View, builder cssBuilder) {
session := cell.Session()
cell.viewData.cssViewStyle(builder, session)
if value, ok := enumProperty(cell, TableVerticalAlign, session, 0); ok {
builder.add("vertical-align", enumProperties[TableVerticalAlign].values[value])
}
}
// GetSelectionMode returns the mode of the TableView elements selection.
// Valid values are NoneSelection (0), CellSelection (1), and RowSelection (2).
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
@ -15,6 +38,42 @@ func GetSelectionMode(view View, subviewID string) int {
return NoneSelection
}
// GetSelectionMode returns the index of the TableView selected row.
// If there is no selected row, then a value less than 0 are returned.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func GetCurrentTableRow(view View, subviewID string) int {
if subviewID != "" {
view = ViewByID(view, subviewID)
}
if view != nil {
if selectionMode := GetSelectionMode(view, ""); selectionMode != NoneSelection {
if tableView, ok := view.(TableView); ok {
return tableView.getCurrent().Row
}
}
}
return -1
}
// GetCurrentTableCell returns the row and column index of the TableView selected cell.
// If there is no selected cell, then a value of the row and column index less than 0 is returned.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func GetCurrentTableCell(view View, subviewID string) CellIndex {
if subviewID != "" {
view = ViewByID(view, subviewID)
}
if view != nil {
if selectionMode := GetSelectionMode(view, ""); selectionMode != NoneSelection {
if tableView, ok := view.(TableView); ok {
return tableView.getCurrent()
}
}
}
return CellIndex{Row: -1, Column: -1}
}
// GetTableCellClickedListeners returns listeners of event which occurs when the user clicks on a table cell.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.

View File

@ -44,6 +44,10 @@ func (picker *timePickerData) Init(session Session) {
picker.timeChangedListeners = []func(TimePicker, time.Time){}
}
func (picker *timePickerData) Focusable() bool {
return true
}
func (picker *timePickerData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag {

18
view.go
View File

@ -58,6 +58,8 @@ type View interface {
SetAnimated(tag string, value interface{}, animation Animation) bool
// SetChangeListener set the function to track the change of the View property
SetChangeListener(tag string, listener func(View, string))
// HasFocus returns 'true' if the view has focus
HasFocus() bool
handleCommand(self View, command string, data DataObject) bool
htmlClass(disabled bool) string
@ -73,7 +75,7 @@ type View interface {
getTransitions() Params
onResize(self View, x, y, width, height float64)
onItemResize(self View, index int, x, y, width, height float64)
onItemResize(self View, index string, x, y, width, height float64)
setNoResizeEvent()
isNoResizeEvent() bool
setScroll(x, y, width, height float64)
@ -95,6 +97,7 @@ type viewData struct {
scroll Frame
noResizeEvent bool
created bool
hasFocus bool
//animation map[string]AnimationEndListener
}
@ -737,7 +740,14 @@ func (view *viewData) handleCommand(self View, command string, data DataObject)
case TouchStart, TouchEnd, TouchMove, TouchCancel:
handleTouchEvents(self, command, data)
case FocusEvent, LostFocusEvent:
case FocusEvent:
view.hasFocus = true
for _, listener := range getFocusListeners(view, "", command) {
listener(self)
}
case LostFocusEvent:
view.hasFocus = false
for _, listener := range getFocusListeners(view, "", command) {
listener(self)
}
@ -838,3 +848,7 @@ func (view *viewData) SetChangeListener(tag string, listener func(View, string))
view.changeListener[tag] = listener
}
}
func (view *viewData) HasFocus() bool {
return view.hasFocus
}