-- @docclass --[[ TODO: * Make table headers more robust. * Get dynamic row heights working with text wrapping. ]] TABLE_SORTING_ASC = 0 TABLE_SORTING_DESC = 1 UITable = extends(UIWidget, "UITable") -- Initialize default values function UITable.create() local table = UITable.internalCreate() table.headerRow = nil table.headerColumns = {} table.dataSpace = nil table.rows = {} table.rowBaseStyle = nil table.columns = {} table.columnWidth = {} table.columBaseStyle = nil table.headerRowBaseStyle = nil table.headerColumnBaseStyle = nil table.selectedRow = nil table.defaultColumnWidth = 80 table.sortColumn = -1 table.sortType = TABLE_SORTING_ASC table.autoSort = false return table end -- Clear table values function UITable:onDestroy() for _,row in pairs(self.rows) do row.onClick = nil end self.rows = {} self.columns = {} self.headerRow = nil self.headerColumns = {} self.columnWidth = {} self.selectedRow = nil if self.dataSpace then self.dataSpace:destroyChildren() self.dataSpace = nil end end -- Detect if a header is already defined function UITable:onSetup() local header = self:getChildById('header') if header then self:setHeader(header) end end -- Parse table related styles function UITable:onStyleApply(styleName, styleNode) for name, value in pairs(styleNode) do if value ~= false then if name == 'table-data' then addEvent(function() self:setTableData(self:getParent():getChildById(value)) end) elseif name == 'column-style' then addEvent(function() self:setColumnStyle(value) end) elseif name == 'row-style' then addEvent(function() self:setRowStyle(value) end) elseif name == 'header-column-style' then addEvent(function() self:setHeaderColumnStyle(value) end) elseif name == 'header-row-style' then addEvent(function() self:setHeaderRowStyle(value) end) end end end end function UITable:setColumnWidth(width) if self:hasHeader() then return end self.columnWidth = width end function UITable:setDefaultColumnWidth(width) self.defaultColumnWidth = width end -- Check if the table has a header function UITable:hasHeader() return self.headerRow ~= nil end -- Clear all rows function UITable:clearData() if not self.dataSpace then return end self.dataSpace:destroyChildren() self.selectedRow = nil self.columns = {} self.rows = {} end -- Set existing child as header function UITable:setHeader(headerWidget) self:removeHeader() if self.dataSpace then local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop() self.dataSpace:applyStyle({ height = newHeight }) end self.headerColumns = {} self.columnWidth = {} for colId, column in pairs(headerWidget:getChildren()) do column.colId = colId column.table = self table.insert(self.columnWidth, column:getWidth()) table.insert(self.headerColumns, column) end self.headerRow = headerWidget end -- Create and add header from table data function UITable:addHeader(data) if not data or type(data) ~= 'table' then g_logger.error('UITable:addHeaderRow - table columns must be provided in a table') return end self:removeHeader() -- build header columns local columns = {} for colId, column in pairs(data) do local col = g_ui.createWidget(self.headerColumnBaseStyle) col.colId = colId col.table = self for type, value in pairs(column) do if type == 'width' then col:setWidth(value) elseif type == 'height' then col:setHeight(value) elseif type == 'text' then col:setText(tr(value)) elseif type == 'onClick' then col.onClick = value end end table.insert(columns, col) end -- create a new header local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self) local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop() self.dataSpace:applyStyle({ height = newHeight }) headerRow:setId('header') self.headerColumns = {} self.columnWidth = {} for _, column in pairs(columns) do headerRow:addChild(column) table.insert(self.columnWidth, column:getWidth()) table.insert(self.headerColumns, column) end self.headerRow = headerRow return headerRow end -- Remove header function UITable:removeHeader() if self:hasHeader() then if self.dataSpace then local newHeight = self.dataSpace:getHeight()+self.headerRow:getHeight()+self.dataSpace:getMarginTop() self.dataSpace:applyStyle({ height = newHeight }) end self.headerColumns = {} self.columnWidth = {} self.headerRow:destroy() self.headerRow = nil end end function UITable:addRow(data, height) if not self.dataSpace then g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.') return end if not data or type(data) ~= 'table' then g_logger.error('UITable:addRow - table columns must be provided in a table.') return end local row = g_ui.createWidget(self.rowBaseStyle) row.table = self if height then row:setHeight(height) end local rowId = #self.rows + 1 row.rowId = rowId row:setId('row'..rowId) row:updateBackgroundColor() self.columns[rowId] = {} for colId, column in pairs(data) do local col = g_ui.createWidget(self.columBaseStyle, row) if column.width then col:setWidth(column.width) else col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth) end if column.height then col:setHeight(column.height) end if column.text then col:setText(column.text) end if column.sortvalue then col.sortvalue = column.sortvalue else col.sortvalue = column.text or 0 end table.insert(self.columns[rowId], col) end self.dataSpace:addChild(row) table.insert(self.rows, row) if self.autoSort then self:sort() end return row end -- Update row indices and background color function UITable:updateRows() for rowId = 1, #self.rows do local row = self.rows[rowId] row.rowId = rowId row:setId('row'..rowId) row:updateBackgroundColor() end end -- Removes the given row widget from the table function UITable:removeRow(row) if self.selectedRow == row then self:selectRow(nil) end row.onClick = nil row.table = nil table.remove(self.columns, row.rowId) table.remove(self.rows, row.rowId) self.dataSpace:removeChild(row) self:updateRows() end function UITable:toggleSorting(enabled) self.autoSort = enabled end function UITable:setSorting(colId, sortType) self.headerColumns[colId]:focus() if sortType then self.sortType = sortType elseif self.sortColumn == colId then if self.sortType == TABLE_SORTING_ASC then self.sortType = TABLE_SORTING_DESC else self.sortType = TABLE_SORTING_ASC end else self.sortType = TABLE_SORTING_ASC end self.sortColumn = colId end function UITable:sort() if self.sortColumn <= 0 then return end if self.sortType == TABLE_SORTING_ASC then table.sort(self.rows, function(rowA, b) return rowA:getChildByIndex(self.sortColumn).sortvalue < b:getChildByIndex(self.sortColumn).sortvalue end) else table.sort(self.rows, function(rowA, b) return rowA:getChildByIndex(self.sortColumn).sortvalue > b:getChildByIndex(self.sortColumn).sortvalue end) end if self.dataSpace then for _, child in pairs(self.dataSpace:getChildren()) do self.dataSpace:removeChild(child) end end self:updateRows() self.columns = {} for _, row in pairs(self.rows) do if self.dataSpace then self.dataSpace:addChild(row) end self.columns[row.rowId] = {} for _, column in pairs(row:getChildren()) do table.insert(self.columns[row.rowId], column) end end end function UITable:selectRow(selectedRow) if selectedRow == self.selectedRow then return end local previousSelectedRow = self.selectedRow self.selectedRow = selectedRow if previousSelectedRow then previousSelectedRow:setChecked(false) end if selectedRow then selectedRow:setChecked(true) end signalcall(self.onSelectionChange, self, selectedRow, previousSelectedRow) end function UITable:setTableData(tableData) local headerHeight = 0 if self.headerRow then headerHeight = self.headerRow:getHeight() end self.dataSpace = tableData self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() }) end function UITable:setRowStyle(style, dontUpdate) self.rowBaseStyle = style if not dontUpdate then for _, row in pairs(self.rows) do row:setStyle(style) end end end function UITable:setColumnStyle(style, dontUpdate) self.columBaseStyle = style if not dontUpdate then for _, columns in pairs(self.columns) do for _, col in pairs(columns) do col:setStyle(style) end end end end function UITable:setHeaderRowStyle(style) self.headerRowBaseStyle = style if self.headerRow then self.headerRow:setStyle(style) end end function UITable:setHeaderColumnStyle(style) self.headerColumnBaseStyle = style for _, col in pairs(self.headerColumns) do col:setStyle(style) end end UITableRow = extends(UIWidget, "UITableRow") function UITableRow:onFocusChange(focused) if focused then if self.table then self.table:selectRow(self) end end end function UITableRow:onStyleApply(styleName, styleNode) for name,value in pairs(styleNode) do if name == 'even-background-color' then self.evenBackgroundColor = value elseif name == 'odd-background-color' then self.oddBackgroundColor = value end end end function UITableRow:updateBackgroundColor() self.backgroundColor = nil local isEven = (self.rowId % 2 == 0) if isEven and self.evenBackgroundColor then self.backgroundColor = self.evenBackgroundColor elseif not isEven and self.oddBackgroundColor then self.backgroundColor = self.oddBackgroundColor end if self.backgroundColor then self:mergeStyle({ ['background-color'] = self.backgroundColor }) end end UITableHeaderColumn = extends(UIButton, "UITableHeaderColumn") function UITableHeaderColumn:onClick() if self.table then self.table:setSorting(self.colId) self.table:sort() end end