From 09544fdf252e8c43ea85ecf66e5535c2c4e3108b Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Sun, 16 Jan 2022 12:25:45 -0500 Subject: [PATCH] Updated TableView Added selection scroll Fixed TableVerticalAlign property ListView bug fixing --- app_scripts.js | 223 ++++++++++++++++++++++++---------------------- demo/tableDemo.go | 6 ++ listView.go | 2 + tableAdapter.go | 55 ++++++++++++ tableView.go | 106 +++++++++++----------- tableViewUtils.go | 96 +++++++++++++++++--- 6 files changed, 315 insertions(+), 173 deletions(-) diff --git a/app_scripts.js b/app_scripts.js index 729de78..526f43b 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -668,8 +668,6 @@ function selectListItem(element, item, needSendMessage) { if (current) { if (current.classList) { current.classList.remove(focusStyle, blurStyle); - } else { // IE < 10 - current.className = "ruiListItem"; } if (sendMessage) { message = "itemUnselected{session=" + sessionID + ",id=" + element.id + "}"; @@ -681,14 +679,10 @@ function selectListItem(element, item, needSendMessage) { if (element === document.activeElement) { if (item.classList) { item.classList.add(focusStyle); - } else { // IE < 10 - item.className = "ruiListItem " + focusStyle } } else { if (item.classList) { item.classList.add(blurStyle); - } else { // IE < 10 - item.className = "ruiListItem " + blurStyle } } @@ -700,6 +694,12 @@ function selectListItem(element, item, needSendMessage) { } } + if (item.scrollIntoViewIfNeeded) { + item.scrollIntoViewIfNeeded() + } else { + item.scrollIntoView({block: "nearest", inline: "nearest"}); + } + /* var left = item.offsetLeft - element.offsetLeft; if (left < element.scrollLeft) { element.scrollLeft = left; @@ -718,7 +718,7 @@ function selectListItem(element, item, needSendMessage) { var bottom = top + item.offsetHeight if (bottom > element.scrollTop + element.clientHeight) { element.scrollTop = bottom - element.clientHeight; - } + }*/ } if (needSendMessage && message != undefined) { @@ -1418,6 +1418,11 @@ function setTableCellCursor(element, row, column) { cell.classList.add(focusStyle); element.setAttribute("data-current", cellID); + if (cell.scrollIntoViewIfNeeded) { + cell.scrollIntoViewIfNeeded() + } else { + cell.scrollIntoView({block: "nearest", inline: "nearest"}); + } sendMessage("currentCell{session=" + sessionID + ",id=" + element.id + ",row=" + row + ",column=" + column + "}"); @@ -1466,108 +1471,107 @@ function moveTableCellCursor(element, row, column, dr, 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; - } - + if (!key) { + return; } - event.stopPropagation(); - event.preventDefault(); + const currentId = element.getAttribute("data-current"); + if (!currentId || currentId == "") { + 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++; + } + } + event.stopPropagation(); + event.preventDefault(); + break; + } + return; + } + + 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(); + } else { + element.setAttribute("data-current", ""); + } } function setTableRowCursor(element, row) { @@ -1590,6 +1594,11 @@ function setTableRowCursor(element, row) { tableRow.classList.add(focusStyle); element.setAttribute("data-current", tableRowID); + if (tableRow.scrollIntoViewIfNeeded) { + tableRow.scrollIntoViewIfNeeded() + } else { + tableRow.scrollIntoView({block: "nearest", inline: "nearest"}); + } sendMessage("currentRow{session=" + sessionID + ",id=" + element.id + ",row=" + row + "}"); return true; diff --git a/demo/tableDemo.go b/demo/tableDemo.go index 2da8de3..c622c75 100644 --- a/demo/tableDemo.go +++ b/demo/tableDemo.go @@ -36,6 +36,8 @@ GridLayout { DropDownList { row = 8, column = 1, id = tableSelectionMode, current = 0, items = ["none", "cell", "row"]}, Checkbox { row = 9, column = 0:1, id = tableDisableHead, content = "Disable head selection" }, Checkbox { row = 10, column = 0:1, id = tableDisableFoot, content = "Disable foot selection" }, + TextView { row = 11, text = "Vertical align in cells" }, + DropDownList { row = 11, column = 1, id = tableVerticalAlign, current = 0, items = ["top", "bottom", "center", "baseline"]}, ] } ] @@ -274,5 +276,9 @@ func createTableViewDemo(session rui.Session) rui.View { } }) + rui.Set(view, "tableVerticalAlign", rui.DropDownEvent, func(list rui.DropDownList, number int) { + rui.Set(view, "demoTableView1", rui.TableVerticalAlign, number) + }) + return view } diff --git a/listView.go b/listView.go index 28ba149..74bc67a 100644 --- a/listView.go +++ b/listView.go @@ -928,6 +928,8 @@ func (listView *listViewData) htmlProperties(self View, buffer *strings.Builder) buffer.WriteString(strconv.Itoa(current)) buffer.WriteRune('"') } + + listView.viewData.htmlProperties(self, buffer) } /* diff --git a/tableAdapter.go b/tableAdapter.go index ee52f20..4704b20 100644 --- a/tableAdapter.go +++ b/tableAdapter.go @@ -1,31 +1,66 @@ package rui +// TableAdapter describes the TableView content type TableAdapter interface { + // RowCount returns number of rows in the table RowCount() int + + // ColumnCount returns number of columns in the table ColumnCount() int + + // Cell returns the contents of a table cell. The function can return elements of the following types: + // * string + // * rune + // * float32, float64 + // * integer values: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64 + // * bool + // * rui.Color + // * rui.View + // * fmt.Stringer + // * rui.VerticalTableJoin, rui.HorizontalTableJoin Cell(row, column int) interface{} } +// TableColumnStyle describes the style of TableView columns. +// To set column styles, you must either implement the TableColumnStyle interface in the table adapter +// or assign its separate implementation to the "column-style" property. type TableColumnStyle interface { ColumnStyle(column int) Params } +// TableRowStyle describes the style of TableView rows. +// To set row styles, you must either implement the TableRowStyle interface in the table adapter +// or assign its separate implementation to the "row-style" property. type TableRowStyle interface { RowStyle(row int) Params } +// TableCellStyle describes the style of TableView cells. +// To set row cells, you must either implement the TableCellStyle interface in the table adapter +// or assign its separate implementation to the "cell-style" property. type TableCellStyle interface { CellStyle(row, column int) Params } +// TableAllowCellSelection determines whether TableView cell selection is allowed. +// It is only used if the "selection-mode" property is set to CellSelection (1). +// To set cell selection allowing, you must either implement the TableAllowCellSelection interface +// in the table adapter or assign its separate implementation to the "allow-selection" property. type TableAllowCellSelection interface { AllowCellSelection(row, column int) bool } +// TableAllowRowSelection determines whether TableView row selection is allowed. +// It is only used if the "selection-mode" property is set to RowSelection (2). +// To set row selection allowing, you must either implement the TableAllowRowSelection interface +// in the table adapter or assign its separate implementation to the "allow-selection" property. type TableAllowRowSelection interface { AllowRowSelection(row int) bool } +// SimpleTableAdapter is implementation of TableAdapter where the content +// defines as [][]interface{}. +// When you assign [][]interface{} value to the "content" property, it is converted to SimpleTableAdapter type SimpleTableAdapter interface { TableAdapter TableCellStyle @@ -36,6 +71,9 @@ type simpleTableAdapter struct { columnCount int } +// TextTableAdapter is implementation of TableAdapter where the content +// defines as [][]string. +// When you assign [][]string value to the "content" property, it is converted to TextTableAdapter type TextTableAdapter interface { TableAdapter } @@ -45,12 +83,17 @@ type textTableAdapter struct { columnCount int } +// NewTextTableAdapter is an auxiliary structure. It used as cell content and +// specifies that the cell should be merged with the one above it type VerticalTableJoin struct { } +// HorizontalTableJoin is an auxiliary structure. It used as cell content and +// specifies that the cell should be merged with the one before it type HorizontalTableJoin struct { } +// NewSimpleTableAdapter creates the new SimpleTableAdapter func NewSimpleTableAdapter(content [][]interface{}) SimpleTableAdapter { if content == nil { return nil @@ -145,6 +188,7 @@ func (adapter *simpleTableAdapter) CellStyle(row, column int) Params { return params } +// NewTextTableAdapter creates the new TextTableAdapter func NewTextTableAdapter(content [][]string) TextTableAdapter { if content == nil { return nil @@ -337,3 +381,14 @@ func (table *tableViewData) getColumnStyle() TableColumnStyle { } return nil } + +func (table *tableViewData) getCellStyle() TableCellStyle { + for _, tag := range []string{CellStyle, Content} { + if value := table.getRaw(tag); value != nil { + if style, ok := value.(TableCellStyle); ok { + return style + } + } + } + return nil +} diff --git a/tableView.go b/tableView.go index 3562fb7..9000617 100644 --- a/tableView.go +++ b/tableView.go @@ -9,7 +9,7 @@ import ( const ( // TableVerticalAlign is the constant for the "table-vertical-align" property tag. // The "table-vertical-align" int property sets the vertical alignment of the content inside a table cell. - // Valid values are LeftAlign (0), RightAlign (1), CenterAlign (2), and BaselineAlign (3, 4) + // Valid values are TopAlign (0), BottomAlign (1), CenterAlign (2), and BaselineAlign (3, 4) TableVerticalAlign = "table-vertical-align" // HeadHeight is the constant for the "head-height" property tag. @@ -221,7 +221,12 @@ type TableView interface { ParanetView ReloadTableData() CellFrame(row, column int) Frame + + content() TableAdapter getCurrent() CellIndex + getRowStyle() TableRowStyle + getColumnStyle() TableColumnStyle + getCellStyle() TableCellStyle } type tableViewData struct { @@ -298,8 +303,8 @@ func (table *tableViewData) remove(tag string) { table.removeBoundsSide(CellPadding, tag) table.propertyChanged(tag) - case Gap, CellBorder, CellPadding, RowStyle, ColumnStyle, CellStyle, - HeadHeight, HeadStyle, FootHeight, FootStyle, AllowSelection: + case SelectionMode, TableVerticalAlign, Gap, CellBorder, CellPadding, RowStyle, + ColumnStyle, CellStyle, HeadHeight, HeadStyle, FootHeight, FootStyle, AllowSelection: if _, ok := table.properties[tag]; ok { delete(table.properties, tag) table.propertyChanged(tag) @@ -326,10 +331,6 @@ 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) } @@ -489,7 +490,7 @@ func (table *tableViewData) set(tag string, value interface{}) bool { return false } - case CellBorder, CellBorderStyle, CellBorderColor, CellBorderWidth, + case SelectionMode, TableVerticalAlign, CellBorder, CellBorderStyle, CellBorderColor, CellBorderWidth, CellBorderLeft, CellBorderLeftStyle, CellBorderLeftColor, CellBorderLeftWidth, CellBorderRight, CellBorderRightStyle, CellBorderRightColor, CellBorderRightWidth, CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth, @@ -498,11 +499,6 @@ func (table *tableViewData) set(tag string, value interface{}) bool { return false } - case SelectionMode: - if !table.setEnumProperty(SelectionMode, value, enumProperties[SelectionMode].values) { - return false - } - case AllowSelection: switch value.(type) { case TableAllowCellSelection: @@ -575,8 +571,8 @@ func (table *tableViewData) set(tag string, value interface{}) bool { func (table *tableViewData) propertyChanged(tag string) { if table.created { switch tag { - case Content, RowStyle, ColumnStyle, CellStyle, CellPadding, CellBorder, - HeadHeight, HeadStyle, FootHeight, FootStyle, + case Content, TableVerticalAlign, RowStyle, ColumnStyle, CellStyle, CellPadding, + CellBorder, HeadHeight, HeadStyle, FootHeight, FootStyle, CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft, TableCellClickedEvent, TableCellSelectedEvent, TableRowClickedEvent, TableRowSelectedEvent, AllowSelection: @@ -851,18 +847,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { table.cellFrame = make([]Frame, rowCount*columnCount) rowStyle := table.getRowStyle() - - var cellStyle1 TableCellStyle = nil - if style, ok := adapter.(TableCellStyle); ok { - cellStyle1 = style - } - - var cellStyle2 TableCellStyle = nil - if value := table.getRaw(CellStyle); value != nil { - if style, ok := value.(TableCellStyle); ok { - cellStyle2 = style - } - } + cellStyle := table.getCellStyle() session := table.Session() @@ -901,6 +886,14 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { } } + vAlignCss := enumProperties[TableVerticalAlign].cssValues + vAlignValue := GetTableVerticalAlign(table, "") + if vAlignValue < 0 || vAlignValue >= len(vAlignCss) { + vAlignValue = 0 + } + + vAlign := vAlignCss[vAlignValue] + tableCSS := func(startRow, endRow int, cellTag string, cellBorder BorderProperty, cellPadding BoundsProperty) { for row := startRow; row < endRow; row++ { @@ -968,41 +961,37 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { view.set(Padding, cellPadding) } - appendFrom := func(cellStyle TableCellStyle) { - if cellStyle != nil { - if styles := cellStyle.CellStyle(row, column); styles != nil { - for tag, value := range styles { - valueToInt := func() int { - switch value := value.(type) { - case int: - return value + if cellStyle != nil { + if styles := cellStyle.CellStyle(row, column); styles != nil { + for tag, value := range styles { + valueToInt := func() int { + switch value := value.(type) { + case int: + return value - case string: - if value, ok := session.resolveConstants(value); ok { - if n, err := strconv.Atoi(value); err == nil { - return n - } + case string: + if value, ok := session.resolveConstants(value); ok { + if n, err := strconv.Atoi(value); err == nil { + return n } } - return 0 } + return 0 + } - switch tag = strings.ToLower(tag); tag { - case RowSpan: - rowSpan = valueToInt() + switch tag = strings.ToLower(tag); tag { + case RowSpan: + rowSpan = valueToInt() - case ColumnSpan: - columnSpan = valueToInt() + case ColumnSpan: + columnSpan = valueToInt() - default: - view.set(tag, value) - } + default: + view.set(tag, value) } } } } - appendFrom(cellStyle1) - appendFrom(cellStyle2) if len(view.properties) > 0 { view.cssStyle(&view, &cssBuilder) @@ -1162,13 +1151,17 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { if style, ok := session.resolveConstants(value); ok { buffer.WriteString(` class="`) buffer.WriteString(style) - buffer.WriteString(`">`) + buffer.WriteString(`" style="vertical-align: `) + buffer.WriteString(vAlign) + buffer.WriteString(`;">`) + return table.cellBorderFromStyle(style), table.cellPaddingFromStyle(style) } case Params: cssBuilder.buffer.Reset() view.Clear() + view.Set(TableVerticalAlign, vAlignValue) for tag, val := range value { view.Set(tag, val) } @@ -1203,7 +1196,10 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { return border, padding } } - buffer.WriteRune('>') + + buffer.WriteString(` style="vertical-align: `) + buffer.WriteString(vAlign) + buffer.WriteString(`;">`) return nil, nil } @@ -1231,7 +1227,9 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { } if rowCount > footHeight+headHeight { - buffer.WriteString("") + buffer.WriteString(``) tableCSS(headHeight, rowCount-footHeight, "td", cellBorder, cellPadding) buffer.WriteString("") } diff --git a/tableViewUtils.go b/tableViewUtils.go index 3d079da..090dfc9 100644 --- a/tableViewUtils.go +++ b/tableViewUtils.go @@ -23,6 +23,70 @@ func (cell *tableCellView) cssStyle(self View, builder cssBuilder) { } } +// GetTableContent returns a TableAdapter which defines the TableView content. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetTableContent(view View, subviewID string) TableAdapter { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + if tableView, ok := view.(TableView); ok { + return tableView.content() + } + } + + return nil +} + +// GetTableRowStyle returns a TableRowStyle which defines styles of TableView rows. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetTableRowStyle(view View, subviewID string) TableRowStyle { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + if tableView, ok := view.(TableView); ok { + return tableView.getRowStyle() + } + } + + return nil +} + +// GetTableColumnStyle returns a TableColumnStyle which defines styles of TableView columns. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetTableColumnStyle(view View, subviewID string) TableColumnStyle { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + if tableView, ok := view.(TableView); ok { + return tableView.getColumnStyle() + } + } + + return nil +} + +// GetTableCellStyle returns a TableCellStyle which defines styles of TableView cells. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetTableCellStyle(view View, subviewID string) TableCellStyle { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + if tableView, ok := view.(TableView); ok { + return tableView.getCellStyle() + } + } + + return nil +} + // 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. @@ -38,28 +102,36 @@ 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. +// GetTableVerticalAlign returns a vertical align in a TavleView cell. Returns one of next values: +// TopAlign (0), BottomAlign (1), CenterAlign (2), and BaselineAlign (3) // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetCurrentTableRow(view View, subviewID string) int { +func GetTableVerticalAlign(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 - } + if result, ok := enumStyledProperty(view, TableVerticalAlign, TopAlign); ok { + return result } } - return -1 + return TopAlign } -// 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. +// +//HeadHeight = "head-height" + +//HeadStyle = "head-style" + +//FootHeight = "foot-height" + +//FootStyle = "foot-style" + +// GetTableCurrent returns the row and column index of the TableView selected cell/row. +// If there is no selected cell/row or the selection mode is NoneSelection (0), +// then a value of the row and column index less than 0 is returned. +// If the selection mode is RowSelection (2) then the returned column index is less than 0. // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetCurrentTableCell(view View, subviewID string) CellIndex { +func GetTableCurrent(view View, subviewID string) CellIndex { if subviewID != "" { view = ViewByID(view, subviewID) }