js/components/attributes_table.js
// NPM IMPORTS
// import assert from 'assert'
import _ from 'lodash'
// COMMON IMPORTS
import T from '../../../node_modules/devapt-core-common/dist/js/utils/types'
// BROWSER IMPORTS
import Table from './table'
const context = 'browser/components/attributes_table'
/**
* @file UI Attributes table component class.
*
* @author Luc BORIES
*
* @license Apache-2.0
*/
export default class AttributesTable extends Table
{
/**
* Creates an instance of Component.
*
* @param {object} arg_runtime - client runtime.
* @param {object} arg_state - component state.
* @param {string} arg_log_context - context of traces of this instance (optional).
*
* @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_attributes_table_component = true
}
/**
* Set all object attributes.
*
* @param {object} arg_object - attributes object.
* @param {integer} arg_max_depth - nested attributes max deep (TODO).
*
* @returns {noting}
*/
set_view_attributes_by_name(arg_view_name, arg_max_depth)
{
console.log(context + ':set_view_attributes_by_name:%s:view name=%s,max depth=%i', this.get_name(), arg_view_name, arg_max_depth)
if ( ! T.isString(arg_view_name) )
{
console.warn(context + ':set_view_attributes_by_name:%s:bad view name string=%s', this.get_name(), arg_view_name)
return
}
const view_desc = this.get_runtime().ui().get_resource_description_resolver()(arg_view_name)
if (! view_desc)
{
console.warn(context + ':set_view_attributes_by_name:%s:view not found=%s', this.get_name(), arg_view_name)
return
}
this.set_object_attributes(view_desc, arg_max_depth)
}
parse_integer(arg_string)
{
let integer = 0
try{
integer = parseInt(arg_string)
}
catch(e){
console.warn(context + ':build_row:click handler:bad integer for string', arg_string)
integer = 0
}
return integer
}
/**
* Set all object attributes.
*
* @param {object} arg_object - attributes object.
* @param {integer} arg_max_depth - nested attributes max deep (TODO).
*
* @returns {noting}
*/
set_object_attributes(arg_object/*, arg_max_depth*/)
{
if ( ! T.isObject(arg_object) )
{
console.warn(context + ':set_object_attributes:%s:bad object instance=', this.get_name(), arg_object)
return
}
const attributes = []
const attributes_exclude = this.get_state_value('attributes_exclude', undefined)
let attributes_path = this.get_state_value('attributes_path', undefined)
// DEBUG
// console.log(context + ':set_object_attributes:%s:attributes_exclude=', this.get_name(), attributes_exclude)
// console.log(context + ':set_object_attributes:%s:attributes_path=', this.get_name(), attributes_path)
if (attributes_path)
{
if (attributes_path.toJS)
{
attributes_path = attributes_path.toJS()
}
if ( T.isArray(attributes_path) && attributes_path.length > 0 )
{
// console.log(context + ':set_object_attributes:%s:attributes_path is a non empty array=', this.get_name(), attributes_path)
let key = attributes_path.shift()
while(key)
{
if (key in arg_object)
{
// console.log(context + ':set_object_attributes:%s:attributes_path:loop on key=%s (found, go inside)', this.get_name(), key)
arg_object = arg_object[key]
} else {
// console.warn(context + ':set_object_attributes:%s:attributes_path:loop on key=%s (not found in current object)', this.get_name(), key)
this.do_action_clear_items()
return
}
key = attributes_path.shift()
}
}
}
const keys = Object.keys(arg_object)
// console.log(context + ':set_object_attributes:%s:keys=', this.get_name(), keys)
keys.forEach(
(key/*, index*/)=>{
if (attributes_exclude)
{
if (attributes_exclude.indexOf(key) > -1)
{
// console.log(context + ':set_object_attributes:%s:skip key=%s', this.get_name(), key)
return
}
}
const value = arg_object[key] ? arg_object[key] : 'undefined'
// console.log(context + ':set_object_attributes:%s:key=%s,value=%s', this.get_name(), key, value)
// const formated_value = (T.isString(value) || T.isNumber(value) || T.isBoolean(value)) ? value : ( T.isFunction(value) ? 'Function' : value.toString())
const formated_value = value
attributes.push( [key, formated_value] )
}
)
// console.log(context + ':set_object_attributes:%s:object&attributes=', this.get_name(), arg_object, attributes)
this.do_action_replace(attributes, attributes.length)
// COLLAPSE / EXPAND CLICK HANDLER
// handler signature: f(component, event name, selection, event, target)
const handler = (component, event_name, selection, event, target)=>{
if (! target || ! component)
{
console.warn(context + ':build_row:click handler:bad target or component')
return
}
const parent_tr_elem = target.parentNode
const collapsed = target.getAttribute('devapt-collapsed') == 'true' ? true : false
const css_display = collapsed ? 'table-row' : 'none'
let parent_depth = parent_tr_elem ? this.parse_integer( parent_tr_elem.getAttribute('devapt-depth') ) : 0
// DEBUG
// console.log(context + ':build_row:click handler:parent_tr_elem=', parent_tr_elem)
// console.log(context + ':build_row:click handler:collapsed=%b', collapsed)
// console.log(context + ':build_row:click handler:css_display=%s', css_display)
// console.log(context + ':build_row:click handler:parent_depth=%i', parent_depth)
let tr_elem = parent_tr_elem ? parent_tr_elem.nextSibling : undefined
let elem_depth = tr_elem ? this.parse_integer( tr_elem.getAttribute('devapt-depth') ) : 0
while(tr_elem && elem_depth > parent_depth)
{
// console.log(context + ':build_row:click handler:loop on elem', tr_elem)
tr_elem.style.display = css_display
tr_elem = tr_elem.nextSibling
}
target.setAttribute('devapt-collapsed', !collapsed)
target.firstChild.textContent = (! collapsed) ? '\u25B9' : '\u25BF'
}
this.on_dom_event('click', 'td.devapt-collapsable-row', handler, undefined, false)
}
/**
* Get object from attributes.
*
* @returns {object}
*/
get_object_attributes()// TODO
{
const result_object = {}
const table_elem = this.get_dom_element()
const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
const tr_elems = table_body_elem.children
let row_index = 0
// let previous_index = undefined
for( ; row_index < tr_elems.length ; row_index++)
{
const tr_elem = tr_elems[row_index]
const tr_index = tr_elem.getAttribute('devapt-row-index')
const td_key_elem = tr_elem.firstChild
const td_value_elem = td_key_elem.nextSibling
const value_type = td_value_elem.getAttribute('devapt-data-type')
const key = td_key_elem.textContent
const value = td_value_elem.textContent
switch(value_type) {
case 'string': result_object[key] = value ; break
case 'number': result_object[key] = new Number(value) ; break
case 'boolean': result_object[key] = (value == 'true') ? true : false ; break
// case 'array':
// case 'object':
}
// previous_index = tr_index
}
return result_object
}
/**
* 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('devapt-column-index', arg_column_index)
if ( T.isString(arg_cell_value) )
{
// console.log(context + ':build_cell:%s:string:at row %i:value=%s', this.get_name(), arg_row_index, arg_cell_value)
td_elem.innerText = arg_cell_value
td_elem.setAttribute('devapt-data-type', 'string')
return td_elem
}
else if ( T.isNumber(arg_cell_value) )
{
// console.log(context + ':build_cell:%s:number:at row %i:value=%s', this.get_name(), arg_row_index, arg_cell_value)
td_elem.innerText = arg_cell_value
td_elem.setAttribute('devapt-data-type', 'number')
}
else if ( T.isBoolean(arg_cell_value) )
{
// console.log(context + ':build_cell:%s:boolean:at row %i:value=%s', this.get_name(), arg_row_index, arg_cell_value)
td_elem.innerText = arg_cell_value ? 'true' : 'false'
td_elem.setAttribute('devapt-data-type', 'boolean')
}
else if ( T.isFunction(arg_cell_value) )
{
// console.log(context + ':build_cell:%s:function:at row %i:value=%s', this.get_name(), arg_row_index, 'Function')
td_elem.innerText = 'Function'
td_elem.setAttribute('devapt-data-type', 'function')
} else {
// console.log(context + ':build_cell:%s:array:at row %i:unknow=%s', this.get_name(), arg_row_index, arg_cell_value)
td_elem.innerText = 'unknow'
td_elem.setAttribute('devapt-data-type', 'unknow')
}
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=0)
{
const this_document = this.get_dom_element().ownerDocument
const row_elem = this_document.createElement('tr')
row_elem.setAttribute('devapt-row-index', arg_row_index)
row_elem.setAttribute('devapt-depth', arg_depth)
// 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) || arg_row_array.length != 2 )
{
console.warn(context + ':build_row:row_array is not an array of size 2 at rows_index=%i', arg_row_index, arg_row_array)
return undefined
}
const attribute_name = arg_row_array[0]
const attribute_value = arg_row_array[1]
const pad_left = 10
const td_name_elem = this_document.createElement('td')
td_name_elem.setAttribute('devapt-column-index', 0)
td_name_elem.style.paddingLeft = td_name_elem.style.paddingLeft ? td_name_elem.style.paddingLeft + arg_depth * pad_left : arg_depth * pad_left
td_name_elem.innerText = attribute_name
row_elem.appendChild(td_name_elem)
const td_value_elem = this.build_cell(attribute_value, arg_row_index, 1, this_document, arg_depth)
row_elem.appendChild(td_value_elem)
return row_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_collapsed(arg_row_array, arg_row_index, arg_max_cols, arg_depth=0)
{
const this_document = this.get_dom_element().ownerDocument
const row_elem = this_document.createElement('tr')
row_elem.setAttribute('devapt-row-index', arg_row_index)
row_elem.setAttribute('devapt-depth', arg_depth)
// 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) || arg_row_array.length != 2 )
{
console.warn(context + ':build_row:row_array is not an array of size 2 at rows_index=%i', arg_row_index, arg_row_array)
return undefined
}
const attribute_name = arg_row_array[0]
const attribute_value = arg_row_array[1]
const pad_left = 10
const td_name_elem = this_document.createElement('td')
td_name_elem.setAttribute('devapt-column-index', 0)
td_name_elem.setAttribute('devapt-collapsed', false)
td_name_elem.className = 'devapt-collapsable-row'
td_name_elem.style.paddingLeft = td_name_elem.style.paddingLeft ? td_name_elem.style.paddingLeft + arg_depth * pad_left : arg_depth * pad_left
// td_name_elem.innerText = attribute_name
row_elem.appendChild(td_name_elem)
const span_elem = this_document.createElement('span')
span_elem.textContent = '\u25BF'
td_name_elem.appendChild(span_elem)
const text_elem = this_document.createElement('span')
text_elem.textContent = attribute_name
td_name_elem.appendChild(text_elem)
const td_value_elem = this.build_cell(attribute_value, arg_row_index, 1, this_document, arg_depth)
row_elem.appendChild(td_value_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 {nothing}
*/
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)
{
// console.log(context + ':process_row_array:%s:at %i:max cols=%i:depth=%i:row array=', this.get_name(), arg_row_index, 2, arg_depth, arg_row_array)
const attribute_name = arg_row_array[0]
const attribute_value = arg_row_array[1]
const pad_left = 10
if ( T.isArray(attribute_value) )
{
// console.log(context + ':process_row_array:%s:array:at row %i:depth=%i', this.get_name(), arg_row_index, arg_depth )
/*
TODO EXPANDABLE ICONS:
'<span class="node_closed">\u25B9</span>'
'<span class="node_opened">\u25BF</span>'
*/
const first_row_elem = this.build_row_collapsed([attribute_name, ''], arg_row_index, 2, arg_depth)
if (! first_row_elem)
{
console.warn(context + ':process_row_array:%s:at %i:max cols=%i:array:bad row element for ', this.get_name(), arg_row_index, 2, arg_row_array)
return
}
first_row_elem.setAttribute('devapt-data-type', 'array')
arg_body_element.appendChild(first_row_elem)
_.forEach(attribute_value,
(item, index)=>{
this.process_row_array(arg_body_element, [index, item], arg_row_index, arg_max_rows, arg_max_cols, arg_mode, arg_max_rows_action, arg_depth + 1)
}
)
return
}
else if ( T.isObject(attribute_value) )
{
// console.log(context + ':process_row_array:%s:object:at row %i:depth=%i', this.get_name(), arg_row_index, arg_depth )
// OPEN=\u25BF CLOSE=\u25B9
const first_row_elem = this.build_row_collapsed([attribute_name, ''], arg_row_index, 2, arg_depth)
if (! first_row_elem)
{
console.warn(context + ':process_row_array:%s:at %i:max cols=%i:object:bad row element for ', this.get_name(), arg_row_index, 2, arg_row_array)
return
}
first_row_elem.setAttribute('devapt-data-type', 'object')
arg_body_element.appendChild(first_row_elem)
_.forEach(attribute_value,
(item, key)=>{
this.process_row_array(arg_body_element, [key, item], arg_row_index, arg_max_rows, arg_max_cols, arg_mode, arg_max_rows_action, arg_depth + 1)
}
)
return
}
// DEFAULT CASE
// console.log(context + ':process_row_array:%s:default:at row %i:depth=%i', this.get_name(), arg_row_index, arg_depth )
const row_elem = this.build_row(arg_row_array, arg_row_index, 2, arg_depth)
if (! row_elem)
{
console.warn(context + ':process_row_array:%s:at %i:max cols=%i:default:bad row element for ', this.get_name(), arg_row_index, 2, arg_row_array)
return
}
row_elem.style.paddingLeft = row_elem.style.paddingLeft ? row_elem.style.paddingLeft + (arg_depth + 1) * pad_left : (arg_depth + 1) * pad_left
arg_body_element.appendChild(row_elem)
}
}