forked from mbk-lab/rui_orig
2
0
Fork 0

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 # v0.4.0
* Added SetTitle and SetTitleColor function to the Session interface * Added SetTitle and SetTitleColor function to the Session interface

View File

@ -615,12 +615,13 @@ function selectDropDownListItem(elementId, number) {
function listItemClickEvent(element, event) { function listItemClickEvent(element, event) {
event.stopPropagation(); event.stopPropagation();
var selected = false; var selected = false;
if (element.classList) { if (element.classList) {
selected = (element.classList.contains("ruiListItemFocused") || element.classList.contains("ruiListItemSelected")); const focusStyle = getListFocusedItemStyle(element);
} else { const blurStyle = getListSelectedItemStyle(element);
selected = element.className.indexOf("ruiListItemFocused") >= 0 || element.className.indexOf("ruiListItemSelected") >= 0; selected = (element.classList.contains(focusStyle) || element.classList.contains(blurStyle));
} }
var list = element.parentNode.parentNode var list = element.parentNode.parentNode
if (list) { 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) { function selectListItem(element, item, needSendMessage) {
var currentId = element.getAttribute("data-current"); var currentId = element.getAttribute("data-current");
var message; var message;
var focusStyle = element.getAttribute("data-focusitemstyle"); const focusStyle = getListFocusedItemStyle(element);
var blurStyle = element.getAttribute("data-bluritemstyle"); const blurStyle = getListSelectedItemStyle(element);
if (!focusStyle) {
focusStyle = "ruiListItemFocused"
}
if (!blurStyle) {
blurStyle = "ruiListItemSelected"
}
if (currentId) { if (currentId) {
var current = document.getElementById(currentId); var current = document.getElementById(currentId);
@ -801,24 +811,29 @@ function findBottomListItem(list, x, y) {
return result return result
} }
function listViewKeyDownEvent(element, event) { function getKey(event) {
var key;
if (event.key) { if (event.key) {
key = event.key; return event.key;
} else if (event.keyCode) { }
if (event.keyCode) {
switch (event.keyCode) { switch (event.keyCode) {
case 13: key = "Enter"; break; case 13: return "Enter";
case 32: key = " "; break; case 32: return " ";
case 33: key = "PageUp"; break; case 33: return "PageUp";
case 34: key = "PageDown"; break; case 34: return "PageDown";
case 35: key = "End"; break; case 35: return "End";
case 36: key = "Home"; break; case 36: return "Home";
case 37: key = "ArrowLeft"; break; case 37: return "ArrowLeft";
case 38: key = "ArrowUp"; break; case 38: return "ArrowUp";
case 39: key = "ArrowRight"; break; case 39: return "ArrowRight";
case 40: key = "ArrowDown"; break; case 40: return "ArrowDown";
} }
} }
}
function listViewKeyDownEvent(element, event) {
const key = getKey(event);
if (key) { if (key) {
var currentId = element.getAttribute("data-current"); var currentId = element.getAttribute("data-current");
var current var current
@ -885,21 +900,10 @@ function listViewFocusEvent(element, event) {
if (currentId) { if (currentId) {
var current = document.getElementById(currentId); var current = document.getElementById(currentId);
if (current) { 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) { if (current.classList) {
current.classList.remove(blurStyle); current.classList.remove(getListSelectedItemStyle(element));
current.classList.add(focusStyle); current.classList.add(getListFocusedItemStyle(element));
} else { // IE < 10 }
current.className = "ruiListItem " + focusStyle;
}
} }
} }
} }
@ -909,20 +913,9 @@ function listViewBlurEvent(element, event) {
if (currentId) { if (currentId) {
var current = document.getElementById(currentId); var current = document.getElementById(currentId);
if (current) { 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) { if (current.classList) {
current.classList.remove(focusStyle); current.classList.remove(getListFocusedItemStyle(element));
current.classList.add(blurStyle); current.classList.add(getListSelectedItemStyle(element));
} else { // IE < 10
current.className = "ruiListItem " + blurStyle;
} }
} }
} }
@ -1372,4 +1365,388 @@ function setTitleColor(color) {
function detailsEvent(element) { function detailsEvent(element) {
sendMessage("details-open{session=" + sessionID + ",id=" + element.id + ",open=" + (element.open ? "1}" : "0}")); 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() return customView.superView.Scroll()
} }
func (customView *CustomViewData) HasFocus() bool {
return customView.superView.HasFocus()
}
func (customView *CustomViewData) onResize(self View, x, y, width, height float64) { func (customView *CustomViewData) onResize(self View, x, y, width, height float64) {
customView.superView.onResize(customView.superView, x, y, width, height) 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) 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){} picker.dateChangedListeners = []func(DatePicker, time.Time){}
} }
func (picker *datePickerData) Focusable() bool {
return true
}
func (picker *datePickerData) normalizeTag(tag string) string { func (picker *datePickerData) normalizeTag(tag string) string {
tag = strings.ToLower(tag) tag = strings.ToLower(tag)
switch tag { switch tag {

View File

@ -213,24 +213,32 @@ theme {
text-color = @ruiPopupTextColor, text-color = @ruiPopupTextColor,
radius = 4px, radius = 4px,
shadow = _{spread-radius=4px, blur=16px, color=#80808080}, shadow = _{spread-radius=4px, blur=16px, color=#80808080},
} },
ruiPopupTitle { ruiPopupTitle {
background-color = @ruiPopupTitleColor, background-color = @ruiPopupTitleColor,
text-color = @ruiPopupTitleTextColor, text-color = @ruiPopupTitleTextColor,
min-height = 24px, min-height = 24px,
} },
ruiMessageText { ruiMessageText {
padding-left = 64px, padding-left = 64px,
padding-right = 64px, padding-right = 64px,
padding-top = 32px, padding-top = 32px,
padding-bottom = 32px, padding-bottom = 32px,
} },
ruiPopupMenuItem { ruiPopupMenuItem {
padding-top = 4px, padding-top = 4px,
padding-bottom = 4px, padding-bottom = 4px,
padding-left = 8px, padding-left = 8px,
padding-right = 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"]}, 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 = 6, column = 0:1, id = tableRowStyle, content = "Row style" },
Checkbox { row = 7, column = 0:1, id = tableColumnStyle, content = "Column 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) { rui.Set(view, "tableCellGap", rui.DropDownEvent, func(list rui.DropDownList, number int) {
if number == 0 { if number == 0 {
rui.Set(view, "demoTableView1", rui.Gap, rui.Px(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){} list.dropDownListener = []func(DropDownList, int){}
} }
func (list *dropDownListData) Focusable() bool {
return true
}
func (list *dropDownListData) Remove(tag string) { func (list *dropDownListData) Remove(tag string) {
list.remove(strings.ToLower(tag)) list.remove(strings.ToLower(tag))
} }

View File

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

View File

@ -89,6 +89,10 @@ func (picker *filePickerData) Init(session Session) {
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){} picker.fileSelectedListeners = []func(FilePicker, []FileInfo){}
} }
func (picker *filePickerData) Focusable() bool {
return true
}
func (picker *filePickerData) Files() []FileInfo { func (picker *filePickerData) Files() []FileInfo {
return picker.files 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) { func focusEventsHtml(view View, buffer *strings.Builder) {
for tag, js := range focusEvents { if view.Focusable() {
if value := view.getRaw(tag); value != nil { for _, js := range focusEvents {
if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 { buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `)
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 { func (listView *listViewData) handleCommand(self View, command string, data DataObject) bool {
switch command { switch command {
case "itemSelected": case "itemSelected":
if text, ok := data.PropertyValue(`number`); ok { if number, ok := dataIntProperty(data, `number`); ok {
if number, err := strconv.Atoi(text); err == nil { listView.properties[Current] = number
listView.properties[Current] = number for _, listener := range listView.selectedListeners {
for _, listener := range listView.selectedListeners { listener(listView, number)
listener(listView, number)
}
listView.propertyChangedEvent(Current)
} }
listView.propertyChangedEvent(Current)
} }
case "itemUnselected": case "itemUnselected":
@ -1162,9 +1160,14 @@ func (listView *listViewData) onItemClick() {
} }
} }
func (listView *listViewData) onItemResize(self View, index int, x, y, width, height float64) { func (listView *listViewData) onItemResize(self View, index string, x, y, width, height float64) {
if index >= 0 && index < len(listView.itemFrame) { n, err := strconv.Atoi(index)
listView.itemFrame[index] = Frame{Left: x, Top: y, Width: width, Height: height} 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" player.tag = "MediaPlayer"
} }
func (player *mediaPlayerData) Focusable() bool {
return true
}
func (player *mediaPlayerData) Remove(tag string) { func (player *mediaPlayerData) Remove(tag string) {
player.remove(strings.ToLower(tag)) player.remove(strings.ToLower(tag))
} }

View File

@ -50,6 +50,10 @@ func (picker *numberPickerData) Init(session Session) {
picker.numberChangedListeners = []func(NumberPicker, float64){} picker.numberChangedListeners = []func(NumberPicker, float64){}
} }
func (picker *numberPickerData) Focusable() bool {
return true
}
func (picker *numberPickerData) normalizeTag(tag string) string { func (picker *numberPickerData) normalizeTag(tag string) string {
tag = strings.ToLower(tag) tag = strings.ToLower(tag)
switch 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 { 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 viewID, ok := obj.PropertyValue("id"); ok {
if n := strings.IndexRune(viewID, '-'); n > 0 { 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 {
if view := session.viewByHTMLID(viewID[:n]); view != nil { view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
view.onItemResize(view, index, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
} else {
ErrorLogF(`View with id == %s not found`, viewID[:n])
}
} else { } 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 { } else if view := session.viewByHTMLID(viewID); view != nil {
view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height")) view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))

View File

@ -214,11 +214,14 @@ type TableView interface {
View View
ParanetView ParanetView
ReloadTableData() ReloadTableData()
CellFrame(row, column int) Frame
getCurrent() CellIndex
} }
type tableViewData struct { type tableViewData struct {
viewData viewData
cellViews []View cellViews []View
cellFrame []Frame
cellSelectedListener, cellClickedListener []func(TableView, int, int) cellSelectedListener, cellClickedListener []func(TableView, int, int)
rowSelectedListener, rowClickedListener []func(TableView, int) rowSelectedListener, rowClickedListener []func(TableView, int)
current CellIndex current CellIndex
@ -245,6 +248,7 @@ func (table *tableViewData) Init(session Session) {
table.viewData.Init(session) table.viewData.Init(session)
table.tag = "TableView" table.tag = "TableView"
table.cellViews = []View{} table.cellViews = []View{}
table.cellFrame = []Frame{}
table.cellSelectedListener = []func(TableView, int, int){} table.cellSelectedListener = []func(TableView, int, int){}
table.cellClickedListener = []func(TableView, int, int){} table.cellClickedListener = []func(TableView, int, int){}
table.rowSelectedListener = []func(TableView, int){} table.rowSelectedListener = []func(TableView, int){}
@ -270,6 +274,10 @@ func (table *tableViewData) normalizeTag(tag string) string {
return tag return tag
} }
func (table *tableViewData) Focusable() bool {
return GetSelectionMode(table, "") != NoneSelection
}
func (table *tableViewData) Get(tag string) interface{} { func (table *tableViewData) Get(tag string) interface{} {
return table.get(table.normalizeTag(tag)) return table.get(table.normalizeTag(tag))
} }
@ -312,6 +320,10 @@ func (table *tableViewData) remove(tag string) {
table.current.Column = -1 table.current.Column = -1
table.propertyChanged(tag) table.propertyChanged(tag)
case SelectionMode:
table.viewData.remove(tag)
table.propertyChanged(tag)
default: default:
table.viewData.remove(tag) table.viewData.remove(tag)
} }
@ -486,14 +498,51 @@ func (table *tableViewData) set(tag string, value interface{}) bool {
} }
case Current: case Current:
switch GetSelectionMode(table, "") { switch value := value.(type) {
case NoneSelection: case int:
table.current.Row = value
table.current.Column = -1
case CellSelection: case CellIndex:
// TODO table.current = value
case RowSelection: case DataObject:
// TODO 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: default:
@ -524,11 +573,75 @@ func (table *tableViewData) propertyChanged(tag string) {
updateCSSProperty(htmlID, "border-spacing", gap.cssString("0"), session) updateCSSProperty(htmlID, "border-spacing", gap.cssString("0"), session)
updateCSSProperty(htmlID, "border-collapse", "separate", 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) 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) { func (table *tableViewData) valueToCellListeners(value interface{}) []func(TableView, int, int) {
if value == nil { if value == nil {
return []func(TableView, int, int){} return []func(TableView, int, int){}
@ -643,16 +756,69 @@ func (table *tableViewData) htmlTag() string {
return "table" return "table"
} }
func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { func (table *tableViewData) rowID(index int) string {
table.cellViews = []View{} return fmt.Sprintf("%s-%d", table.htmlID(), index)
}
content := table.getRaw(Content) func (table *tableViewData) cellID(row, column int) string {
if content == nil { return fmt.Sprintf("%s-%d-%d", table.htmlID(), row, column)
return }
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 selectionMode := GetSelectionMode(table, ""); selectionMode != NoneSelection {
if !ok { 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 return
} }
@ -662,10 +828,12 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
return return
} }
table.cellFrame = make([]Frame, rowCount*columnCount)
rowStyle := table.getRowStyle() rowStyle := table.getRowStyle()
var cellStyle1 TableCellStyle = nil var cellStyle1 TableCellStyle = nil
if style, ok := content.(TableCellStyle); ok { if style, ok := adapter.(TableCellStyle); ok {
cellStyle1 = style cellStyle1 = style
} }
@ -691,6 +859,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
view.Init(session) view.Init(session)
ignorCells := []struct{ row, column int }{} ignorCells := []struct{ row, column int }{}
selectionMode := GetSelectionMode(table, "")
tableCSS := func(startRow, endRow int, cellTag string, cellBorder BorderProperty, cellPadding BoundsProperty) { tableCSS := func(startRow, endRow int, cellTag string, cellBorder BorderProperty, cellPadding BoundsProperty) {
for row := startRow; row < endRow; row++ { 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 id="`)
buffer.WriteString(`<tr style="`) buffer.WriteString(table.rowID(row))
buffer.WriteString(cssBuilder.buffer.String()) buffer.WriteRune('"')
buffer.WriteString(`">`)
} else { if selectionMode == RowSelection {
buffer.WriteString("<tr>") 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++ { for column := 0; column < columnCount; column++ {
ignore := false ignore := false
for _, cell := range ignorCells { for _, cell := range ignorCells {
@ -748,7 +934,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
return value return value
case string: case string:
if value, ok = session.resolveConstants(value); ok { if value, ok := session.resolveConstants(value); ok {
if n, err := strconv.Atoi(value); err == nil { if n, err := strconv.Atoi(value); err == nil {
return n return n
} }
@ -780,6 +966,23 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
buffer.WriteRune('<') buffer.WriteRune('<')
buffer.WriteString(cellTag) 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 { if columnSpan > 1 {
buffer.WriteString(` colspan="`) buffer.WriteString(` colspan="`)
@ -1087,6 +1290,10 @@ func (table *tableViewData) getCellBorder() BorderProperty {
return nil return nil
} }
func (table *tableViewData) getCurrent() CellIndex {
return table.current
}
func (table *tableViewData) cssStyle(self View, builder cssBuilder) { func (table *tableViewData) cssStyle(self View, builder cssBuilder) {
table.viewData.cssViewStyle(builder, table.Session()) table.viewData.cssViewStyle(builder, table.Session())
@ -1101,30 +1308,93 @@ func (table *tableViewData) cssStyle(self View, builder cssBuilder) {
} }
func (table *tableViewData) ReloadTableData() { 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()) updateInnerHTML(table.htmlID(), table.Session())
} }
func (cell *tableCellView) Set(tag string, value interface{}) bool { func (table *tableViewData) onItemResize(self View, index string, x, y, width, height float64) {
return cell.set(strings.ToLower(tag), value) 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 { func (table *tableViewData) CellFrame(row, column int) Frame {
switch tag { if content := table.content(); content != nil {
case VerticalAlign: i := row*content.ColumnCount() + column
tag = TableVerticalAlign if i < len(table.cellFrame) {
} return table.cellFrame[i]
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])
} }
return Frame{}
} }
func (table *tableViewData) Views() []View { func (table *tableViewData) Views() []View {
return table.cellViews 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 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. // GetSelectionMode returns the mode of the TableView elements selection.
// Valid values are NoneSelection (0), CellSelection (1), and RowSelection (2). // 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. // 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 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. // 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 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. // 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){} picker.timeChangedListeners = []func(TimePicker, time.Time){}
} }
func (picker *timePickerData) Focusable() bool {
return true
}
func (picker *timePickerData) normalizeTag(tag string) string { func (picker *timePickerData) normalizeTag(tag string) string {
tag = strings.ToLower(tag) tag = strings.ToLower(tag)
switch tag { switch tag {

18
view.go
View File

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