js/components/table.js
// NPM IMPORTS
import assert from 'assert'
// COMMON IMPORTS
import T from '../../../node_modules/devapt-core-common/dist/js/utils/types'
import html_entities from '../../../node_modules/devapt-core-common/dist/js/utils/html_entities'
// BROWSER IMPORTS
import Container from '../base/container'
const context = 'browser/components/table'
/**
* @file UI component class.
* @author Luc BORIES
* @license Apache-2.0
*/
export default class Table extends Container
{
/**
* Creates an instance of Table.
*
* @param {object} arg_runtime - client runtime.
* @param {object} arg_state - component state.
* @param {string} arg_log_context - context of traces of this instance (optional).
*
* API:
* ->get_children_component():array - Get view children components.
*
* ->ui_items_get_count():integer - Get container items count.
*
* ->ui_items_append(arg_items_array, arg_items_count):nothing - Append tems to the container.
* ->ui_items_prepend(arg_items_array, arg_items_count):nothing - Prepend tems to the container.
* ->ui_items_insert_at(arg_index, arg_items_array, arg_items_count):nothing - Insert items at container position index.
* ->ui_items_replace(arg_items_array, arg_items_count):nothing - Replace container items.
*
* ->ui_items_remove_at_index(arg_index):nothing - Remove a row at given position.
* ->ui_items_remove_first():nothing - Remove a row at first position.
* ->ui_items_remove_last(arg_count):nothing - Remove a row at last position.
*
* ->build_row(arg_row_array, arg_row_index, arg_max_cols):string - Build a table row html tag.
* ->update_rows(arg_rows_array, arg_options):nothing - Append or prepend a row.
* ->update_section_collection(arg_collection_def, arg_collection_values):nothing - Update values on a table part.
*
* @returns {nothing}
*/
constructor(arg_runtime, arg_state, arg_log_context)
{
const log_context = arg_log_context ? arg_log_context : context
super(arg_runtime, arg_state, log_context)
this.is_table_component = true
// DEBUG
// this.enable_trace()
}
/**
* Get view children components.
*
* @returns {array} - list of Component.
*/
get_children_component()
{
if ( ! this._children_component)
{
this._children_component = []
const items = this.get_state_value('items', [])
const headers = this.get_state_value('headers', [])
const footers = this.get_state_value('headers', [])
// console.log(context + ':get_children_component:init with items:', items)
headers.forEach(
(row)=>{
if ( T.isArray(row) )
{
row.forEach(
(cell)=>{
if ( T.isObject(cell) )
{
if (cell.is_component)
{
this._children_component.push(cell)
return
}
if ( T.isString(cell.view) )
{
const component = window.devapt().ui(cell.view)
if (component && component.is_component)
{
this._children_component.push(component)
}
}
}
}
)
}
}
)
footers.forEach(
(row)=>{
if ( T.isArray(row) )
{
row.forEach(
(cell)=>{
if ( T.isObject(cell) )
{
if (cell.is_component)
{
this._children_component.push(cell)
return
}
if ( T.isString(cell.view) )
{
const component = window.devapt().ui(cell.view)
if (component && component.is_component)
{
this._children_component.push(component)
}
}
}
}
)
}
}
)
items.forEach(
(row)=>{
if ( T.isArray(row) )
{
row.forEach(
(cell)=>{
if ( T.isObject(cell) )
{
if (cell.is_component)
{
this._children_component.push(cell)
return
}
if ( T.isString(cell.key) )
{
if ( T.isString(cell.view) )
{
const component = window.devapt().ui(cell.view)
if (component && component.is_component)
{
this._children_component.push(component)
}
}
}
}
}
)
}
}
)
}
return this._children_component
}
/**
* Get container items count.
*
* @returns {nothing}
*/
ui_items_get_count()
{
const table_body_elem = document.getElementById(this.get_dom_id())
const tr_elems = table_body_elem.children
return tr_elems.length
}
/**
* Erase container items.
*
* @returns {nothing}
*/
ui_items_clear()
{
const table_elem = this.get_dom_element()
const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
while(table_body_elem.hasChildNodes())
{
const tr_elem = table_body_elem.lastChild
this.delete_row_elem(tr_elem)
table_body_elem.removeChild(tr_elem)
}
}
/**
* Append items to the container.
*
* @param {array} arg_items_array - items array.
* @param {intege} arg_items_count - items count.
*
* @returns {nothing}
*/
ui_items_append(arg_items_array, arg_items_count)
{
// console.log(context + ':ui_items_append:arg_items_array', arg_items_array, arg_items_count)
let arg_options = arg_options ? arg_options : {}
arg_options.mode = 'append'
this.update_rows(arg_items_array, arg_options)
}
/**
* Prepend items to the container.
*
* @param {array} arg_items_array - items array.
* @param {intege} arg_items_count - items count.
*
* @returns {nothing}
*/
ui_items_prepend(arg_items_array, arg_items_count)
{
// console.log(context + ':ui_items_prepend:%s:count=%s:arg_items_array', this.get_name(), arg_items_count, arg_items_array)
let arg_options = arg_options ? arg_options : {}
arg_options.mode = 'prepend'
this.update_rows(arg_items_array, arg_options)
}
/**
* Replace container items.
*
* @param {array} arg_items_array - items array.
* @param {intege} arg_items_count - items count.
*
* @returns {nothing}
*/
ui_items_replace(arg_items_array/*, arg_items_count*/)
{
// console.log(context + ':ui_items_replace:arg_items_array', arg_items_array.length)
// REMOVE ALL EXISTING ROWS
this.ui_items_clear()
let arg_options = arg_options ? arg_options : {}
arg_options.mode = 'replace'
this.update_rows(arg_items_array, arg_options)
}
/**
* Insert items at container position index.
*
* @param {intege} arg_index - position index.
* @param {array} arg_items_array - items array.
* @param {intege} arg_items_count - items count.
*
* @returns {nothing}
*/
ui_items_insert_at(arg_index, arg_items_array, arg_items_count)
{
assert( T.isArray(arg_items_array), context + ':ui_items_replace:bad items array')
assert( T.isNumber(arg_items_count), context + ':ui_items_replace:bad items count')
// NOT YET IMPLEMENTED
}
/**
* Remove a row at given position.
*
* @param {number} arg_index - row index.
*
* @returns {nothing}
*/
ui_items_remove_at_index(arg_index)
{
assert( T.isNumber(arg_index), context + ':ui_items_remove_at_index:bad index number')
const table_elem = this.get_dom_element()
const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
if (arg_index < 0 || arg_index >= table_body_elem.children.length)
{
console.warn(context + ':ui_items_remove_at_index:%s:bad item index=%s', this.get_name(), arg_index)
return
}
const tr_elem = table_body_elem.children[arg_index]
this.delete_row_elem(tr_elem)
table_body_elem.removeChild(tr_elem)
}
/**
* Remove a row at first position.
*
* @returns {nothing}
*/
ui_items_remove_first()
{
const table_elem = this.get_dom_element()
const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
const tr_elem = table_body_elem.firstElementChild()
this.delete_row_elem(tr_elem)
table_body_elem.removeChild(tr_elem)
}
/**
* Remove a row at last position.
*
* @param {integer} arg_count - items count to remove.
*
* @returns {nothing}
*/
ui_items_remove_last(arg_count=1)
{
// console.log(context + ':ui_items_remove_last:arg_count', arg_count)
if (arg_count <= 0)
{
return
}
const table_elem = this.get_dom_element()
const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
const tr_elems = table_body_elem.children
const last_index = tr_elems.length - arg_count - 1
let row_index = last_index - 1 >= 0 ? last_index - 1 : 0
for( ; row_index < tr_elems.length ; row_index++)
{
const tr_elem = tr_elems[row_index]
this.delete_row_elem(tr_elem)
table_body_elem.removeChild(tr_elem)
}
}
/**
* Delete table rows DOM elements.
*
* @param {array} arg_rows_array - rows Element array.
*
* @returns {Element} - TD DOM Element.
*/
delete_row_elem(arg_row_elem)
{
}
/**
* Build a row cell DOM element.
*
* @param {any} arg_cell_value - cell value.
* @param {integer} arg_row_index - row index.
* @param {integer} arg_column_index - column index.
* @param {Document} arg_document - DOM document.
*
* @returns {Element} - TD DOM Element.
*/
build_cell(arg_cell_value, arg_row_index, arg_column_index, arg_document)
{
const td_elem = arg_document.createElement('td')
td_elem.setAttribute('data-column-index', arg_column_index)
td_elem.innerText = arg_cell_value
return td_elem
}
/**
* Build a table row DOM element.
*
* @param {array} arg_row_array - row values array.
* @param {integer} arg_row_index - row index.
* @param {integer} arg_max_cols - max columns number.
* @param {integer} arg_depth - path depth.
*
* @returns {Element} - TD DOM Element.
*/
build_row(arg_row_array, arg_row_index, arg_max_cols/*, arg_depth*/)
{
const this_document = this.get_dom_element().ownerDocument
const row_elem = this_document.createElement('tr')
row_elem.setAttribute('data-row-index', arg_row_index)
// DEBUG
// console.log(context + ':build_row:rows_index=%i row_array max_cols', arg_row_index, arg_row_array, arg_max_cols)
if( ! T.isArray(arg_row_array) )
{
console.warn(context + ':build_row:row_array is not an array at rows_index=%i', arg_row_index, arg_row_array)
return undefined
}
arg_row_array.forEach(
(cell, index) => {
if (arg_max_cols && index > arg_max_cols)
{
return
}
const td_elem = this.build_cell(cell, arg_row_index, index, this_document)
if (! td_elem)
{
console.warn(context + ':build_row:bad cell element at rows_index=%i at column_index=%i', arg_row_index, index)
return
}
row_elem.appendChild(td_elem)
}
)
return row_elem
}
/**
* Build a table row DOM element.
*
* @param {Element} arg_body_element - table body element.
* @param {array} arg_row_array - row values array.
* @param {integer} arg_row_index - row index.
* @param {integer} arg_max_rows - max rows number.
* @param {integer} arg_max_cols - max columns number.
* @param {string} arg_mode - fill mode:append/prepend
* @param {string} arg_max_rows_action - action on max rows.
* @param {integer} arg_depth - path depth.
*
* @returns {Element} - TD DOM Element.
*/
process_row_array(arg_body_element, arg_row_array, arg_row_index, arg_max_rows, arg_max_cols, arg_mode, arg_max_rows_action, arg_depth=0)
{
const rows_count = arg_body_element.children.length
const row_elem = this.build_row(arg_row_array, arg_row_index, arg_max_cols, arg_depth)
if (! row_elem)
{
console.warn(context + ':update_rows:%s:at %i:max cols=%i:bad row element for ', this.get_name(), arg_row_index, arg_max_cols, arg_row_array)
return
}
if (arg_max_rows && (rows_count + arg_row_index) > arg_max_rows)
{
if (arg_max_rows_action == 'remove_bottom')
{
// TODO
console.warn('TODO remove_bottom')
}
else if (arg_max_rows_action == 'remove_top')
{
// TODO
console.warn('TODO remove_top')
}
else
{
return
}
}
// console.log(context + ':update_rows:rows_index=%i mode=%s', row_index, arg_options.mode)
if (arg_mode == 'prepend')
{
arg_body_element.insertBefore(row_elem, arg_body_element.firstChild )
}
else
{
arg_body_element.appendChild(row_elem)
}
}
/**
* Append or prepend a row.
*
* @param {array} arg_rows_array - rows array.
* @param {object} arg_options - operation settigs (optional).
*
* @returns {nothing}
*/
update_rows(arg_rows_array, arg_options)
{
const arg_table_id = this.get_dom_id()
assert( T.isString(arg_table_id), context + ':update_rows:bad table id string:' + arg_table_id)
assert( T.isArray(arg_rows_array), context + ':update_rows:bad rows array')
const state = this.get_state()
arg_options = arg_options ? arg_options : {}
arg_options.mode = arg_options.mode ? arg_options.mode : 'append'
const table_elem = this.get_dom_element()
const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
const max_cols = T.isNumber(state.max_columns) ? state.max_columns : undefined
const max_rows = T.isNumber(state.max_rows) ? state.max_rows : undefined
const max_rows_action = T.isString(state.max_rows_action) ? state.max_rows_action : undefined
let fields_count = this.get_state_value('fields_count', 0)
if (fields_count == 0)
{
const headers = this.get_state_value('headers', [])
if ( T.isArray(headers) && headers.length > 0 )
{
const last_headers = headers[headers.length - 1]
if ( T.isArray(last_headers) )
{
fields_count = last_headers.length
}
}
}
// DEBUG
// console.log( context + ':update_rows:arg_rows_array=', arg_rows_array)
// console.log( context + ':update_rows:rows_count=%i', rows_count)
arg_rows_array.forEach(
(arg_row_array, row_index) => {
const row_array = T.isArray(arg_row_array) ? arg_row_array : (fields_count == 1 ? [arg_row_array] : undefined)
if (! row_array)
{
console.warn(context + ':update_rows:%s:at %i:fields_count=%i:bad row array for ', this.get_name(), row_index, fields_count, arg_row_array)
return
}
this.process_row_array(table_body_elem, row_array, row_index, max_rows, max_cols, arg_options.mode, max_rows_action)
}
)
}
/**
* Update values on a table part.
*
* @param {object} arg_collection_def - plain object map of collection definition ({collection_name:"", collection_dom_id:""}.
* @param {object} arg_collection_values - plain object map of collection key/value pairs.
*
* @returns {nothing}
*/
update_section_collection(arg_collection_def, arg_collection_values)
{
const table_id = this.get_dom_id()
// console.log(context + ':update_section_collection:%s:def= values=', this.get_name(), arg_collection_def, arg_collection_values)
if (arg_collection_def && arg_collection_def.collection_name && arg_collection_def.collection_dom_id && arg_collection_values)
{
const arg_collection_name = arg_collection_def.collection_name
const collection_elem = document.getElementById(arg_collection_def.collection_dom_id)
if (!collection_elem)
{
// CALLED BY BINDING ON A VIEW WHICH IS NOT VISIBLE
// console.log(context + ':update_section_collection:' + this.get_name() + ':collection element not found for id [' + arg_collection_def.collection_dom_id + ']')
return
}
var collection_dom_template_default = "<tr> <td></td> <td> {collection_key} </td> <td id='{collection_id}'>{collection_value}</td> </tr>"
var collection_dom_template = arg_collection_def.collection_dom_template ? html_entities.decode(arg_collection_def.collection_dom_template) : collection_dom_template_default
// DEBUG
// console.log(context + ':update_section_collection:arg_collection_def.collection_dom_template=%s', arg_collection_def.collection_dom_template)
// console.log(context + ':update_section_collection:collection_dom_template=%s', collection_dom_template)
var collection_key_safe = undefined
var collection_value = undefined
var collection_id = undefined
var collection_value_elem = undefined
var collection_value_html = undefined
var collection_keys = Object.keys(arg_collection_values)
var re = /[^a-zA-Z0-9]/gi
// console.log('update_metric_collection2:collection=%s keys= jqo=', arg_collection_name, collection_keys, arg_collection_jqo)
collection_keys.forEach(
function(collection_key)
{
collection_key_safe = collection_key.replace(re, '_')
// console.log('update_metric_collection2:collection=%s loop on key=', arg_collection_name, collection_key)
collection_value = arg_collection_values[collection_key]
collection_id = table_id + "_" + arg_collection_name + "_" + collection_key_safe
collection_value_elem = document.getElementById(collection_id)
if (! collection_value_elem )
{
// console.log('update_metric_collection2:collection=%s loop on key=', collection_key)
collection_value_html = collection_dom_template.replace('{collection_key}', collection_key).replace('{collection_id}', collection_id).replace('{collection_value}', collection_value)
// console.log(context + ':update_section_collection:html', collection_value_html)
const tr_elem = document.createElement('tr')
tr_elem.innerHTML = collection_value_html
collection_elem.parentNode.insertBefore(tr_elem, collection_elem.nextSibling)
collection_value_elem = document.getElementById(collection_id)
}
collection_value_elem.textContent = collection_value
}
)
}
}
}