Reference Source

js/components/table.js

  1. // NPM IMPORTS
  2. import assert from 'assert'
  3.  
  4. // COMMON IMPORTS
  5. import T from '../../../node_modules/devapt-core-common/dist/js/utils/types'
  6. import html_entities from '../../../node_modules/devapt-core-common/dist/js/utils/html_entities'
  7.  
  8. // BROWSER IMPORTS
  9. import Container from '../base/container'
  10.  
  11.  
  12. const context = 'browser/components/table'
  13.  
  14.  
  15.  
  16. /**
  17. * @file UI component class.
  18. * @author Luc BORIES
  19. * @license Apache-2.0
  20. */
  21. export default class Table extends Container
  22. {
  23. /**
  24. * Creates an instance of Table.
  25. *
  26. * @param {object} arg_runtime - client runtime.
  27. * @param {object} arg_state - component state.
  28. * @param {string} arg_log_context - context of traces of this instance (optional).
  29. *
  30. * API:
  31. * ->get_children_component():array - Get view children components.
  32. *
  33. * ->ui_items_get_count():integer - Get container items count.
  34. *
  35. * ->ui_items_append(arg_items_array, arg_items_count):nothing - Append tems to the container.
  36. * ->ui_items_prepend(arg_items_array, arg_items_count):nothing - Prepend tems to the container.
  37. * ->ui_items_insert_at(arg_index, arg_items_array, arg_items_count):nothing - Insert items at container position index.
  38. * ->ui_items_replace(arg_items_array, arg_items_count):nothing - Replace container items.
  39. *
  40. * ->ui_items_remove_at_index(arg_index):nothing - Remove a row at given position.
  41. * ->ui_items_remove_first():nothing - Remove a row at first position.
  42. * ->ui_items_remove_last(arg_count):nothing - Remove a row at last position.
  43. *
  44. * ->build_row(arg_row_array, arg_row_index, arg_max_cols):string - Build a table row html tag.
  45. * ->update_rows(arg_rows_array, arg_options):nothing - Append or prepend a row.
  46. * ->update_section_collection(arg_collection_def, arg_collection_values):nothing - Update values on a table part.
  47. *
  48. * @returns {nothing}
  49. */
  50. constructor(arg_runtime, arg_state, arg_log_context)
  51. {
  52. const log_context = arg_log_context ? arg_log_context : context
  53. super(arg_runtime, arg_state, log_context)
  54. this.is_table_component = true
  55. // DEBUG
  56. // this.enable_trace()
  57. }
  58.  
  59.  
  60.  
  61. /**
  62. * Get view children components.
  63. *
  64. * @returns {array} - list of Component.
  65. */
  66. get_children_component()
  67. {
  68. if ( ! this._children_component)
  69. {
  70. this._children_component = []
  71.  
  72. const items = this.get_state_value('items', [])
  73. const headers = this.get_state_value('headers', [])
  74. const footers = this.get_state_value('headers', [])
  75. // console.log(context + ':get_children_component:init with items:', items)
  76.  
  77. headers.forEach(
  78. (row)=>{
  79. if ( T.isArray(row) )
  80. {
  81. row.forEach(
  82. (cell)=>{
  83. if ( T.isObject(cell) )
  84. {
  85. if (cell.is_component)
  86. {
  87. this._children_component.push(cell)
  88. return
  89. }
  90.  
  91. if ( T.isString(cell.view) )
  92. {
  93. const component = window.devapt().ui(cell.view)
  94. if (component && component.is_component)
  95. {
  96. this._children_component.push(component)
  97. }
  98. }
  99. }
  100. }
  101. )
  102. }
  103. }
  104. )
  105.  
  106. footers.forEach(
  107. (row)=>{
  108. if ( T.isArray(row) )
  109. {
  110. row.forEach(
  111. (cell)=>{
  112. if ( T.isObject(cell) )
  113. {
  114. if (cell.is_component)
  115. {
  116. this._children_component.push(cell)
  117. return
  118. }
  119.  
  120. if ( T.isString(cell.view) )
  121. {
  122. const component = window.devapt().ui(cell.view)
  123. if (component && component.is_component)
  124. {
  125. this._children_component.push(component)
  126. }
  127. }
  128. }
  129. }
  130. )
  131. }
  132. }
  133. )
  134.  
  135. items.forEach(
  136. (row)=>{
  137. if ( T.isArray(row) )
  138. {
  139. row.forEach(
  140. (cell)=>{
  141. if ( T.isObject(cell) )
  142. {
  143. if (cell.is_component)
  144. {
  145. this._children_component.push(cell)
  146. return
  147. }
  148. if ( T.isString(cell.key) )
  149. {
  150. if ( T.isString(cell.view) )
  151. {
  152. const component = window.devapt().ui(cell.view)
  153. if (component && component.is_component)
  154. {
  155. this._children_component.push(component)
  156. }
  157. }
  158. }
  159. }
  160. }
  161. )
  162. }
  163. }
  164. )
  165. }
  166.  
  167. return this._children_component
  168. }
  169. /**
  170. * Get container items count.
  171. *
  172. * @returns {nothing}
  173. */
  174. ui_items_get_count()
  175. {
  176. const table_body_elem = document.getElementById(this.get_dom_id())
  177. const tr_elems = table_body_elem.children
  178. return tr_elems.length
  179. }
  180. /**
  181. * Erase container items.
  182. *
  183. * @returns {nothing}
  184. */
  185. ui_items_clear()
  186. {
  187. const table_elem = this.get_dom_element()
  188. const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
  189.  
  190. while(table_body_elem.hasChildNodes())
  191. {
  192. const tr_elem = table_body_elem.lastChild
  193. this.delete_row_elem(tr_elem)
  194. table_body_elem.removeChild(tr_elem)
  195. }
  196. }
  197. /**
  198. * Append items to the container.
  199. *
  200. * @param {array} arg_items_array - items array.
  201. * @param {intege} arg_items_count - items count.
  202. *
  203. * @returns {nothing}
  204. */
  205. ui_items_append(arg_items_array, arg_items_count)
  206. {
  207. // console.log(context + ':ui_items_append:arg_items_array', arg_items_array, arg_items_count)
  208.  
  209. let arg_options = arg_options ? arg_options : {}
  210. arg_options.mode = 'append'
  211. this.update_rows(arg_items_array, arg_options)
  212. }
  213. /**
  214. * Prepend items to the container.
  215. *
  216. * @param {array} arg_items_array - items array.
  217. * @param {intege} arg_items_count - items count.
  218. *
  219. * @returns {nothing}
  220. */
  221. ui_items_prepend(arg_items_array, arg_items_count)
  222. {
  223. // console.log(context + ':ui_items_prepend:%s:count=%s:arg_items_array', this.get_name(), arg_items_count, arg_items_array)
  224. let arg_options = arg_options ? arg_options : {}
  225. arg_options.mode = 'prepend'
  226. this.update_rows(arg_items_array, arg_options)
  227. }
  228. /**
  229. * Replace container items.
  230. *
  231. * @param {array} arg_items_array - items array.
  232. * @param {intege} arg_items_count - items count.
  233. *
  234. * @returns {nothing}
  235. */
  236. ui_items_replace(arg_items_array/*, arg_items_count*/)
  237. {
  238. // console.log(context + ':ui_items_replace:arg_items_array', arg_items_array.length)
  239. // REMOVE ALL EXISTING ROWS
  240. this.ui_items_clear()
  241. let arg_options = arg_options ? arg_options : {}
  242. arg_options.mode = 'replace'
  243. this.update_rows(arg_items_array, arg_options)
  244. }
  245. /**
  246. * Insert items at container position index.
  247. *
  248. * @param {intege} arg_index - position index.
  249. * @param {array} arg_items_array - items array.
  250. * @param {intege} arg_items_count - items count.
  251. *
  252. * @returns {nothing}
  253. */
  254. ui_items_insert_at(arg_index, arg_items_array, arg_items_count)
  255. {
  256. assert( T.isArray(arg_items_array), context + ':ui_items_replace:bad items array')
  257. assert( T.isNumber(arg_items_count), context + ':ui_items_replace:bad items count')
  258. // NOT YET IMPLEMENTED
  259. }
  260. /**
  261. * Remove a row at given position.
  262. *
  263. * @param {number} arg_index - row index.
  264. *
  265. * @returns {nothing}
  266. */
  267. ui_items_remove_at_index(arg_index)
  268. {
  269. assert( T.isNumber(arg_index), context + ':ui_items_remove_at_index:bad index number')
  270.  
  271. const table_elem = this.get_dom_element()
  272. const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
  273. if (arg_index < 0 || arg_index >= table_body_elem.children.length)
  274. {
  275. console.warn(context + ':ui_items_remove_at_index:%s:bad item index=%s', this.get_name(), arg_index)
  276. return
  277. }
  278. const tr_elem = table_body_elem.children[arg_index]
  279. this.delete_row_elem(tr_elem)
  280. table_body_elem.removeChild(tr_elem)
  281. }
  282. /**
  283. * Remove a row at first position.
  284. *
  285. * @returns {nothing}
  286. */
  287. ui_items_remove_first()
  288. {
  289. const table_elem = this.get_dom_element()
  290. const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
  291. const tr_elem = table_body_elem.firstElementChild()
  292. this.delete_row_elem(tr_elem)
  293. table_body_elem.removeChild(tr_elem)
  294. }
  295. /**
  296. * Remove a row at last position.
  297. *
  298. * @param {integer} arg_count - items count to remove.
  299. *
  300. * @returns {nothing}
  301. */
  302. ui_items_remove_last(arg_count=1)
  303. {
  304. // console.log(context + ':ui_items_remove_last:arg_count', arg_count)
  305. if (arg_count <= 0)
  306. {
  307. return
  308. }
  309. const table_elem = this.get_dom_element()
  310. const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
  311. const tr_elems = table_body_elem.children
  312. const last_index = tr_elems.length - arg_count - 1
  313. let row_index = last_index - 1 >= 0 ? last_index - 1 : 0
  314. for( ; row_index < tr_elems.length ; row_index++)
  315. {
  316. const tr_elem = tr_elems[row_index]
  317. this.delete_row_elem(tr_elem)
  318. table_body_elem.removeChild(tr_elem)
  319. }
  320. }
  321. /**
  322. * Delete table rows DOM elements.
  323. *
  324. * @param {array} arg_rows_array - rows Element array.
  325. *
  326. * @returns {Element} - TD DOM Element.
  327. */
  328. delete_row_elem(arg_row_elem)
  329. {
  330. }
  331. /**
  332. * Build a row cell DOM element.
  333. *
  334. * @param {any} arg_cell_value - cell value.
  335. * @param {integer} arg_row_index - row index.
  336. * @param {integer} arg_column_index - column index.
  337. * @param {Document} arg_document - DOM document.
  338. *
  339. * @returns {Element} - TD DOM Element.
  340. */
  341. build_cell(arg_cell_value, arg_row_index, arg_column_index, arg_document)
  342. {
  343. const td_elem = arg_document.createElement('td')
  344.  
  345. td_elem.setAttribute('data-column-index', arg_column_index)
  346. td_elem.innerText = arg_cell_value
  347.  
  348. return td_elem
  349. }
  350.  
  351. /**
  352. * Build a table row DOM element.
  353. *
  354. * @param {array} arg_row_array - row values array.
  355. * @param {integer} arg_row_index - row index.
  356. * @param {integer} arg_max_cols - max columns number.
  357. * @param {integer} arg_depth - path depth.
  358. *
  359. * @returns {Element} - TD DOM Element.
  360. */
  361. build_row(arg_row_array, arg_row_index, arg_max_cols/*, arg_depth*/)
  362. {
  363. const this_document = this.get_dom_element().ownerDocument
  364. const row_elem = this_document.createElement('tr')
  365. row_elem.setAttribute('data-row-index', arg_row_index)
  366.  
  367. // DEBUG
  368. // console.log(context + ':build_row:rows_index=%i row_array max_cols', arg_row_index, arg_row_array, arg_max_cols)
  369. if( ! T.isArray(arg_row_array) )
  370. {
  371. console.warn(context + ':build_row:row_array is not an array at rows_index=%i', arg_row_index, arg_row_array)
  372. return undefined
  373. }
  374.  
  375. arg_row_array.forEach(
  376. (cell, index) => {
  377. if (arg_max_cols && index > arg_max_cols)
  378. {
  379. return
  380. }
  381.  
  382. const td_elem = this.build_cell(cell, arg_row_index, index, this_document)
  383. if (! td_elem)
  384. {
  385. console.warn(context + ':build_row:bad cell element at rows_index=%i at column_index=%i', arg_row_index, index)
  386. return
  387. }
  388.  
  389. row_elem.appendChild(td_elem)
  390. }
  391. )
  392. return row_elem
  393. }
  394.  
  395. /**
  396. * Build a table row DOM element.
  397. *
  398. * @param {Element} arg_body_element - table body element.
  399. * @param {array} arg_row_array - row values array.
  400. * @param {integer} arg_row_index - row index.
  401. * @param {integer} arg_max_rows - max rows number.
  402. * @param {integer} arg_max_cols - max columns number.
  403. * @param {string} arg_mode - fill mode:append/prepend
  404. * @param {string} arg_max_rows_action - action on max rows.
  405. * @param {integer} arg_depth - path depth.
  406. *
  407. * @returns {Element} - TD DOM Element.
  408. */
  409. 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)
  410. {
  411. const rows_count = arg_body_element.children.length
  412. const row_elem = this.build_row(arg_row_array, arg_row_index, arg_max_cols, arg_depth)
  413. if (! row_elem)
  414. {
  415. 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)
  416. return
  417. }
  418.  
  419. if (arg_max_rows && (rows_count + arg_row_index) > arg_max_rows)
  420. {
  421. if (arg_max_rows_action == 'remove_bottom')
  422. {
  423. // TODO
  424. console.warn('TODO remove_bottom')
  425. }
  426. else if (arg_max_rows_action == 'remove_top')
  427. {
  428. // TODO
  429. console.warn('TODO remove_top')
  430. }
  431. else
  432. {
  433. return
  434. }
  435. }
  436.  
  437. // console.log(context + ':update_rows:rows_index=%i mode=%s', row_index, arg_options.mode)
  438. if (arg_mode == 'prepend')
  439. {
  440. arg_body_element.insertBefore(row_elem, arg_body_element.firstChild )
  441. }
  442. else
  443. {
  444. arg_body_element.appendChild(row_elem)
  445. }
  446. }
  447. /**
  448. * Append or prepend a row.
  449. *
  450. * @param {array} arg_rows_array - rows array.
  451. * @param {object} arg_options - operation settigs (optional).
  452. *
  453. * @returns {nothing}
  454. */
  455. update_rows(arg_rows_array, arg_options)
  456. {
  457. const arg_table_id = this.get_dom_id()
  458. assert( T.isString(arg_table_id), context + ':update_rows:bad table id string:' + arg_table_id)
  459. assert( T.isArray(arg_rows_array), context + ':update_rows:bad rows array')
  460. const state = this.get_state()
  461. arg_options = arg_options ? arg_options : {}
  462. arg_options.mode = arg_options.mode ? arg_options.mode : 'append'
  463. const table_elem = this.get_dom_element()
  464. const table_body_elem = table_elem.getElementsByTagName( "tbody" )[0]
  465.  
  466. const max_cols = T.isNumber(state.max_columns) ? state.max_columns : undefined
  467. const max_rows = T.isNumber(state.max_rows) ? state.max_rows : undefined
  468. const max_rows_action = T.isString(state.max_rows_action) ? state.max_rows_action : undefined
  469.  
  470. let fields_count = this.get_state_value('fields_count', 0)
  471. if (fields_count == 0)
  472. {
  473. const headers = this.get_state_value('headers', [])
  474. if ( T.isArray(headers) && headers.length > 0 )
  475. {
  476. const last_headers = headers[headers.length - 1]
  477. if ( T.isArray(last_headers) )
  478. {
  479. fields_count = last_headers.length
  480. }
  481. }
  482. }
  483.  
  484. // DEBUG
  485. // console.log( context + ':update_rows:arg_rows_array=', arg_rows_array)
  486. // console.log( context + ':update_rows:rows_count=%i', rows_count)
  487. arg_rows_array.forEach(
  488. (arg_row_array, row_index) => {
  489.  
  490. const row_array = T.isArray(arg_row_array) ? arg_row_array : (fields_count == 1 ? [arg_row_array] : undefined)
  491. if (! row_array)
  492. {
  493. 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)
  494. return
  495. }
  496.  
  497. this.process_row_array(table_body_elem, row_array, row_index, max_rows, max_cols, arg_options.mode, max_rows_action)
  498. }
  499. )
  500. }
  501.  
  502.  
  503. /**
  504. * Update values on a table part.
  505. *
  506. * @param {object} arg_collection_def - plain object map of collection definition ({collection_name:"", collection_dom_id:""}.
  507. * @param {object} arg_collection_values - plain object map of collection key/value pairs.
  508. *
  509. * @returns {nothing}
  510. */
  511. update_section_collection(arg_collection_def, arg_collection_values)
  512. {
  513. const table_id = this.get_dom_id()
  514.  
  515. // console.log(context + ':update_section_collection:%s:def= values=', this.get_name(), arg_collection_def, arg_collection_values)
  516.  
  517. if (arg_collection_def && arg_collection_def.collection_name && arg_collection_def.collection_dom_id && arg_collection_values)
  518. {
  519. const arg_collection_name = arg_collection_def.collection_name
  520. const collection_elem = document.getElementById(arg_collection_def.collection_dom_id)
  521. if (!collection_elem)
  522. {
  523. // CALLED BY BINDING ON A VIEW WHICH IS NOT VISIBLE
  524. // console.log(context + ':update_section_collection:' + this.get_name() + ':collection element not found for id [' + arg_collection_def.collection_dom_id + ']')
  525. return
  526. }
  527.  
  528. var collection_dom_template_default = "<tr> <td></td> <td> {collection_key} </td> <td id='{collection_id}'>{collection_value}</td> </tr>"
  529. var collection_dom_template = arg_collection_def.collection_dom_template ? html_entities.decode(arg_collection_def.collection_dom_template) : collection_dom_template_default
  530. // DEBUG
  531. // console.log(context + ':update_section_collection:arg_collection_def.collection_dom_template=%s', arg_collection_def.collection_dom_template)
  532. // console.log(context + ':update_section_collection:collection_dom_template=%s', collection_dom_template)
  533.  
  534. var collection_key_safe = undefined
  535. var collection_value = undefined
  536. var collection_id = undefined
  537. var collection_value_elem = undefined
  538. var collection_value_html = undefined
  539. var collection_keys = Object.keys(arg_collection_values)
  540. var re = /[^a-zA-Z0-9]/gi
  541.  
  542. // console.log('update_metric_collection2:collection=%s keys= jqo=', arg_collection_name, collection_keys, arg_collection_jqo)
  543. collection_keys.forEach(
  544. function(collection_key)
  545. {
  546. collection_key_safe = collection_key.replace(re, '_')
  547. // console.log('update_metric_collection2:collection=%s loop on key=', arg_collection_name, collection_key)
  548.  
  549. collection_value = arg_collection_values[collection_key]
  550. collection_id = table_id + "_" + arg_collection_name + "_" + collection_key_safe
  551. collection_value_elem = document.getElementById(collection_id)
  552.  
  553. if (! collection_value_elem )
  554. {
  555. // console.log('update_metric_collection2:collection=%s loop on key=', collection_key)
  556.  
  557. collection_value_html = collection_dom_template.replace('{collection_key}', collection_key).replace('{collection_id}', collection_id).replace('{collection_value}', collection_value)
  558. // console.log(context + ':update_section_collection:html', collection_value_html)
  559. const tr_elem = document.createElement('tr')
  560. tr_elem.innerHTML = collection_value_html
  561. collection_elem.parentNode.insertBefore(tr_elem, collection_elem.nextSibling)
  562. collection_value_elem = document.getElementById(collection_id)
  563. }
  564. collection_value_elem.textContent = collection_value
  565. }
  566. )
  567. }
  568. }
  569. }