TableAjax.php.p5UI__TableAjax.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. var DBG = DBG || false;
  2. var DBG1 = true;
  3. if (!global.p5VendorJs) throw "Missing p5 Vendor js libs";
  4. // if (!global.p5VendorJs.Redux) throw "Missing p5 Vendor js lib: Redux";
  5. var createReactClass = global.p5VendorJs.createReactClass;
  6. var h = global.p5VendorJs.React.createElement;
  7. var ReactDOM = global.p5VendorJs.ReactDOM;
  8. // var Redux = global.p5VendorJs.Redux;
  9. // var ReduxThunk = global.p5VendorJs.ReduxThunk;
  10. // var createStoreWithThunkMiddleware = Redux.applyMiddleware(ReduxThunk)(Redux.createStore); // TODO: to vendor.js
  11. var Defaults = {}
  12. var Defaults__baseCellStyle = {
  13. height: 26,
  14. padding: 4
  15. };
  16. Defaults.cellFontSize = 12;
  17. Defaults.rowsPerPage = 10;
  18. Defaults.cellLineHeight = 18;
  19. Defaults.baseCellStyle = Defaults__baseCellStyle;
  20. Defaults.baseStickyCellStyle = Object.assign({}, Defaults__baseCellStyle, {
  21. position: "absolute", top: "auto", left: "0",
  22. 'background-color': "#fff"
  23. });
  24. Defaults.baseHeaderCellStyle = {
  25. 'padding': "2px 5px",
  26. 'white-space': 'nowrap',
  27. 'line-height': Defaults.cellLineHeight+"px",
  28. 'font-size': Defaults.cellFontSize+"px",
  29. 'border-bottom-width': "1px",
  30. 'vertical-align': "bottom",
  31. };
  32. Defaults.primaryKeyLabel = "Nr";
  33. function p5Utils__mapToQuery(map, callback) {
  34. var mapCallback = ('function' === typeof callback) ? callback : function (key) {
  35. return '' + key + '=' + encodeURIComponent(map.get(key));
  36. };
  37. return Array.from(map.keys()).sort()
  38. .map(mapCallback)
  39. .join('&')
  40. }
  41. function p5Utils__mapToQueryWithKeyPrefix(map, keyPrefix, callback) {
  42. var mapCallback = ('function' === typeof callback) ? callback : function (key) {
  43. return '' + (keyPrefix || '') + key + '=' + encodeURIComponent(map.get(key));
  44. };
  45. return p5Utils__mapToQuery(map, mapCallback)
  46. }
  47. function p5Utils__objectToQueryWithKeyPrefix(obj, prefix, callback) {
  48. if (!obj) return '';
  49. var mapCallback = ('function' === typeof callback) ? callback : function (key) {
  50. return '' + key + '=' + encodeURIComponent(obj[key]);
  51. };
  52. return Object.keys(obj).sort()
  53. .map(mapCallback)
  54. .join('&')
  55. }
  56. var TableAjax_Feature_FunctionsCell = function (props) {
  57. DBG && console.warn('DBG::TableAjax_Feature_FunctionsCell::render', { props: props });
  58. return h('div', {
  59. style: props.style
  60. }, [
  61. (props.primaryKey) ? "F(" + props.primaryKey + ")" : null
  62. ]);
  63. };
  64. var TableAjax_Feature_SelectedCell = function (props) {
  65. DBG && console.warn('DBG::TableAjax_Feature_SelectedCell::render', { props: props });
  66. return h('div', {
  67. style: props.style
  68. }, [
  69. (props.primaryKey) ? h('input', { type: 'checkbox', title: props.primaryKey }) : null
  70. ]);
  71. };
  72. var TableAjax_Feature_PrimaryKeyCell = function (props) {
  73. return h('div', {
  74. style: props.style
  75. }, props.primaryKey);
  76. };
  77. var TableAjax_Filter_FunctionsCell = function (props) {
  78. DBG && console.warn('DBG::TableAjax_Filter_FunctionsCell::render', { props: props });
  79. return h('div', {
  80. style: props.style
  81. }, [
  82. "(x)"
  83. ]);
  84. };
  85. var TableAjax_Filter_SelectedCell = function (props) {
  86. DBG && console.warn('DBG::TableAjax_Filter_SelectedCell::render', { props: props });
  87. return h('div', {
  88. style: props.style
  89. }, [
  90. h('input', { type: 'checkbox' })
  91. ]);
  92. };
  93. var TableAjax_Filter_PrimaryKeyCell = function (props) {
  94. return h('div', {
  95. style: props.style
  96. }, [
  97. h('input', {
  98. type: "text",
  99. placeholder: "%",
  100. style: {
  101. 'box-sizing': "border-box",
  102. padding: "0 5px",
  103. width: "100%",
  104. height: "100%",
  105. 'border': "0px",
  106. 'border-bottom': "2px solid transparent",
  107. 'background-color': "#eee"
  108. }
  109. })
  110. ]);
  111. };
  112. var TableAjax_Label_FunctionsCell = function (props) {
  113. DBG && console.warn('DBG::TableAjax_Label_FunctionsCell::render', { props: props });
  114. return h('div', {
  115. style: props.style
  116. }, [
  117. "(icons)"
  118. ]);
  119. };
  120. var TableAjax_Label_SelectedCell = function (props) {
  121. DBG && console.warn('DBG::TableAjax_Label_SelectedCell::render', { props: props });
  122. return h('div', {
  123. style: props.style
  124. }, [
  125. h('input', { type: 'checkbox' })
  126. ]);
  127. };
  128. var TableAjax_Label_PrimaryKeyCell = function (props) {
  129. return h('div', {
  130. style: Object.assign({}, {
  131. display: "table",
  132. }, props.style)
  133. }, [
  134. h('th', { style: Defaults.baseHeaderCellStyle }, props.label),
  135. ]);
  136. };
  137. var TableAjax_Tbody = createReactClass({
  138. renderDataRow: function (value, rowIdx) {
  139. var allColsCount = 13; // TODO: (this.state.cols.length || 0) + 3;
  140. var renderRowCell = this.renderRowCell(rowIdx).bind(this);
  141. return h('tr', {},
  142. [
  143. this.renderStickyRowCell(rowIdx)
  144. ].concat(
  145. Array.apply(null, { length: allColsCount }).map(renderRowCell)
  146. )
  147. );
  148. },
  149. renderRowCell: function (rowIdx) {
  150. var _rowIdx = rowIdx;
  151. var _item = this.getItem(rowIdx);
  152. return function (value, cellIdx) {
  153. var value = (_item) ? Object.values(_item).slice(3)[ cellIdx ] : ''; // TODO: convert item to values Array by fields order - function itemToGridValuesList - defined in parent, passed by prop
  154. return h('td', {
  155. style: Object.assign({}, Defaults.baseCellStyle, {
  156. })
  157. }, value);
  158. }
  159. },
  160. renderStickyRowCell: function (rowIdx) {
  161. var cell1Width = this.props.stickyColWidths[0];
  162. var cell2Width = this.props.stickyColWidths[1];
  163. var cell3Width = this.props.stickyColWidths[2];
  164. var cell1Style = Object.assign({}, Defaults.baseStickyCellStyle, { position: "absolute", top: "auto", left: "0", width: (cell1Width - 1), height: Defaults.baseCellStyle.height - 1 });
  165. var cell2Style = Object.assign({}, Defaults.baseStickyCellStyle, { position: "absolute", top: "auto", left: cell1Width, width: (cell2Width - 1), height: Defaults.baseCellStyle.height - 1 });
  166. var cell3Style = Object.assign({}, Defaults.baseStickyCellStyle, { position: "absolute", top: "auto", left: cell1Width + cell2Width, width: (cell3Width - 1), height: Defaults.baseCellStyle.height - 1 });
  167. var item = this.getItem(rowIdx);
  168. var primaryKey = (item) ? item['@primaryKey'] : null;
  169. return h('td', {
  170. style: Object.assign({}, Defaults.baseCellStyle, {
  171. display: "block", position: "absolute", left: 0, top: "auto",
  172. width: (cell1Width + cell2Width + cell3Width),
  173. padding: 0,
  174. 'background-color': "#ddd"
  175. })
  176. }, [
  177. h(TableAjax_Feature_FunctionsCell, { style: cell1Style, primaryKey: primaryKey }),
  178. h(TableAjax_Feature_SelectedCell, { style: cell2Style, primaryKey: primaryKey }),
  179. h(TableAjax_Feature_PrimaryKeyCell, { style: cell3Style, primaryKey: primaryKey }),
  180. ]);
  181. },
  182. getItem: function (rowIdx) {
  183. return (this.props.rows.length > rowIdx) ? this.props.rows[rowIdx] : null; // { primaryKey: rowIdx } // TODO: real data
  184. },
  185. shouldComponentUpdate: function (nextProps, nextState) {
  186. // receivedRequestId
  187. var shouldUpdate = false;
  188. if (nextProps.receivedRequestId > this.props.receivedRequestId) shouldUpdate = true;
  189. if (nextProps.rowsPerPage != this.props.rowsPerPage) shouldUpdate = true;
  190. else if (nextProps.stickyColWidths.join(',') != this.props.stickyColWidths.join(',')) shouldUpdate = true;
  191. DBG && console.log("DBG:TableAjax_Tbody:shouldComponentUpdate", { shouldUpdate, props: this.props, nextProps, state: this.state, nextState });
  192. return shouldUpdate;
  193. },
  194. render: function () {
  195. DBG && console.log("DBG:TableAjax_Tbody:render");
  196. return h('tbody', {}, [
  197. Array.apply(null, { length: this.props.rowsPerPage }).map(this.renderDataRow)
  198. ]);
  199. }
  200. });
  201. // p5UI__TableAjax.props.dataStore: priv.options.tableDataStore,
  202. // p5UI__TableAjax.props.dataActions: priv.options.tableDataActions,
  203. // p5UI__TableAjax.props.filterStore: priv.options.filterStore,
  204. // p5UI__TableAjax.props.filterActions: priv.options.filterActions,
  205. // p5UI__TableAjax.props.selectedStore: priv.options.selectedStore,
  206. // p5UI__TableAjax.props.selectedActions: priv.options.selectedActions,
  207. var p5UI__TableAjax = createReactClass({
  208. // props.namespace
  209. // props.width - table max width
  210. // @doc: element.scrollLeft = intValue; // set scroll left for dom element
  211. // TODO: stickyCols = [ rowFunctions, selectFeature, primaryKey, ...custom fields ]
  212. _refContentEl: null,
  213. setContentElRef: function (el) {
  214. this._refContentEl = el;
  215. },
  216. _getStateFromDataStore: function () {
  217. var state = this.props.dataStore.getState();
  218. DBG && console.log('DBG::p5UI__TableAjax::_getStateFromDataStore', { state: state });
  219. return {
  220. width: state.width,
  221. rowsPerPage: state.rowsPerPage || Defaults.rowsPerPage,
  222. isLoading: state.isLoading,
  223. sentRequestId: state.sentRequestId,
  224. receivedRequestId: state.receivedRequestId,
  225. rows: state.rows,
  226. primaryKeyLabel: this.props.primaryKeyLabel || Defaults.primaryKeyLabel,
  227. };
  228. },
  229. getItem: function (rowIdx) {
  230. return (this.state.rows.length > rowIdx) ? this.state.rows[rowIdx] : null; // { primaryKey: rowIdx } // TODO: real data
  231. },
  232. getInitialState: function () {
  233. var cols = Array.apply(null, { length: 13 }).map(function (undefinedValue, cellIdx) {
  234. var label = "Col("+cellIdx+")" + ( cellIdx % 2 ? "<br>Col line 2..." : "" );
  235. return label;
  236. });
  237. var baseCellStyle = {
  238. height: 26,
  239. padding: 4,
  240. };
  241. var baseStickyCellStyle = Object.assign({}, baseCellStyle, {
  242. position: "absolute", top: "auto", left: "0",
  243. 'background-color': "#fff",
  244. });
  245. return Object.assign({
  246. isLoading: false,
  247. filter: null,
  248. cols: cols,
  249. data: [],
  250. rowsPerPage: 10,
  251. baseCellStyle: baseCellStyle,
  252. baseStickyCellStyle: baseStickyCellStyle,
  253. stickyColWidths: [
  254. 50,
  255. 25,
  256. 75 // 63 // TODO: depend on primaryKeyLabel width
  257. ]
  258. }, this._getStateFromDataStore());
  259. },
  260. componentDidMount: function () {
  261. DBG && console.log('DBG::p5UI__TableAjax::componentDidMount');
  262. this._unsubscribeDataStore = this.props.dataStore.subscribe(this._dataStoreUpdated)
  263. this._unsubscribeFilterStore = this.props.filterStore.subscribe(this._filterStoreUpdated)
  264. },
  265. componentWillUnmount: function () {
  266. this._unsubscribeDataStore()
  267. this._unsubscribeFilterStore()
  268. },
  269. _dataStoreUpdated: function () {
  270. DBG && console.log('DBG::p5UI__TableAjax::_dataStoreUpdated');
  271. this.setState(this._getStateFromDataStore())
  272. },
  273. _filterStoreUpdated: function () {
  274. DBG && console.log('DBG::p5UI__TableAjax::_filterStoreUpdated');
  275. var curFilterState = this._getStateFromFilterStore();
  276. var needFetchData = (
  277. !this.state.filter
  278. || curFilterState.filterQuery !== this.state.filter.filterQuery
  279. || curFilterState.specialFilterQuery !== this.state.filter.specialFilterQuery
  280. || curFilterState.currSortCol !== this.state.filter.currSortCol
  281. || curFilterState.currSortFlip !== this.state.filter.currSortFlip
  282. );
  283. if (needFetchData) {
  284. this.setState({ filter: curFilterState })
  285. this.props.dataStore.dispatch(this.props.dataActions.loadData(this.props.namespace, curFilterState))
  286. }
  287. },
  288. _getStateFromFilterStore: function () {
  289. var curFilterState = this.props.filterStore.getState();
  290. return {
  291. filterQuery: p5Utils__mapToQueryWithKeyPrefix(curFilterState.filter, 'f_'),
  292. specialFilterQuery: p5Utils__mapToQueryWithKeyPrefix(curFilterState.specialFilter, 'sf_'),
  293. currSortCol: curFilterState.currSortCol,
  294. currSortFlip: curFilterState.currSortFlip
  295. };
  296. },
  297. shouldComponentUpdate: function (nextProps, nextState) {
  298. DBG && console.log('DBG::p5UI__TableAjax::shouldComponentUpdate - TODO only when data changed?', { props: this.props, nextProps, state: this.state, nextState });
  299. return true;
  300. // var dataChanged = true; // TODO compare this.state.rows with nextState.rows array
  301. // var getPk = function (item) {
  302. // return item['@primaryKey'];
  303. // }
  304. // var listPks = this.state.rows.map(getPk).join(',');
  305. // var prevPks = nextState.rows.map(getPk).join(',');
  306. // DBG && console.log('DBG::p5UI__TableAjax::shouldComponentUpdate', { state: this.state, nextState, listPks, prevPks });
  307. // return (
  308. // this.state.isLoading !== nextState.isLoading
  309. // || this.state.width !== nextState.width
  310. // || dataChanged
  311. // );
  312. },
  313. getTheadCellHeight: function () {
  314. var maxLines = this.state.cols.reduce(function (ret, cell) {
  315. var label = cell;
  316. return Math.max(ret, label.replace('<br>', '###').replace('<br/>', '###').split("###").length);
  317. }, 1)
  318. return 2 * 2 + maxLines * Defaults.cellLineHeight;
  319. },
  320. renderTheadColNameRowCell: function (value, cellIdx) {
  321. var label = this.state.cols[cellIdx];
  322. return h('th', {
  323. style: Defaults.baseHeaderCellStyle,
  324. }, label.replace('<br>', '###<br>###').replace('<br/>', '###<br>###').split("###").map(function (txtOrBr) {
  325. return ('<br>' === txtOrBr) ? h('br') : txtOrBr;
  326. }));
  327. },
  328. renderTheadFilterRowCell: function (value, cellIdx) {
  329. return h('td', { style: { padding: 0 } }, [
  330. // <input class="filter" placeholder="%" type="text" value="" style="box-sizing: border-box; width: 100%; height: 21px; border-top: 0px; border-bottom: 2px solid transparent;">
  331. h('input', {
  332. type: "text",
  333. placeholder: "%",
  334. style: {
  335. 'box-sizing': "border-box",
  336. width: "100%",
  337. height: "26px",
  338. padding: "4px 5px 0 5px",
  339. 'font-size': "12px",
  340. 'border': 0,
  341. 'border-bottom': "2px solid transparent",
  342. 'background-color': "#eee",
  343. },
  344. })
  345. ]);
  346. },
  347. renderStickyTheadNameRowCell: function (item) {
  348. var headerCellHeight = this.getTheadCellHeight();
  349. var cell1Width = this.state.stickyColWidths[0];
  350. var cell2Width = this.state.stickyColWidths[1];
  351. var cell3Width = this.state.stickyColWidths[2];
  352. var cell1Style = Object.assign({}, this.state.baseStickyCellStyle, { position: "absolute", top: "auto", width: (cell1Width - 1), height: headerCellHeight });
  353. var cell2Style = Object.assign({}, this.state.baseStickyCellStyle, { position: "absolute", top: "auto", width: (cell2Width - 1), height: headerCellHeight });
  354. var cell3Style = Object.assign({}, this.state.baseStickyCellStyle, { position: "absolute", top: "auto", width: (cell3Width - 1), height: headerCellHeight });
  355. cell1Style.left = "0";
  356. cell2Style.left = cell1Width;
  357. cell3Style.left = cell1Width + cell2Width;
  358. var label = this.state.primaryKeyLabel;
  359. return h('td', {
  360. style: Object.assign({}, this.state.baseCellStyle, {
  361. display: "block", position: "absolute", left: 0, top: "auto",
  362. width: (cell1Width + cell2Width + cell3Width),
  363. height: headerCellHeight,
  364. padding: 0,
  365. 'background-color': "#ddd",
  366. })
  367. }, [
  368. h(TableAjax_Label_FunctionsCell, { style: cell1Style }),
  369. h(TableAjax_Label_SelectedCell, { style: cell2Style }),
  370. h(TableAjax_Label_PrimaryKeyCell, { style: cell3Style, label: label }),
  371. ]);
  372. },
  373. renderStickyTheadFilterRowCell: function (item) {
  374. var cell1Width = this.state.stickyColWidths[0];
  375. var cell2Width = this.state.stickyColWidths[1];
  376. var cell3Width = this.state.stickyColWidths[2];
  377. var cell1Style = Object.assign({}, this.state.baseStickyCellStyle, { position: "absolute", top: "auto", left: "0", width: (cell1Width - 1) });
  378. var cell2Style = Object.assign({}, this.state.baseStickyCellStyle, { position: "absolute", top: "auto", left: cell1Width, width: (cell2Width - 1) });
  379. var cell3Style = Object.assign({}, this.state.baseStickyCellStyle, { position: "absolute", top: "auto", left: cell1Width + cell2Width, width: (cell3Width - 1), padding: "none" });
  380. return h('td', {
  381. style: Object.assign({}, this.state.baseCellStyle, {
  382. display: "block", position: "absolute", left: 0, top: "auto",
  383. width: (cell1Width + cell2Width + cell3Width),
  384. height: this.state.baseCellStyle.height + 2,
  385. padding: 0,
  386. 'background-color': "#ddd",
  387. })
  388. }, [
  389. h(TableAjax_Filter_FunctionsCell, { style: cell1Style }),
  390. h(TableAjax_Filter_SelectedCell, { style: cell2Style }),
  391. h(TableAjax_Filter_PrimaryKeyCell, { style: cell3Style }),
  392. ]);
  393. },
  394. render: function () {
  395. DBG && console.log('DBG::p5UI__TableAjax::render', { state: this.state });
  396. var baseStyle = { 'border-top': "1px solid red", 'border-bottom': "1px solid red", 'min-height': "100px" } // TODO: DBG
  397. var allColsCount = 13; // TODO: (this.state.cols.length || 0) + 3;
  398. var widthTotal = this.state.width || 1200;
  399. var widthStickyCols = this.state.stickyColWidths.reduce(function (a, b) { return a + b; }, 0);
  400. var widthScrollableContent = widthTotal - widthStickyCols - 2;
  401. return h('div', {
  402. className: "p5UI__TableAjax",
  403. style: Object.assign(baseStyle, {
  404. 'background-color': "#ddd",
  405. })
  406. }, [
  407. h('div', { style: { 'background-color': "#f00", color: "#fff", padding: "3px 12px" } }, "namespace: '" + this.props.namespace + "' getTheadCellHeight("+this.getTheadCellHeight()+")"),
  408. h('div', {
  409. ref: this.setContentElRef,
  410. style: {
  411. 'margin-left': widthStickyCols + 2,
  412. // 'min-height': "300px",
  413. 'overflow': "scroll visible",
  414. 'padding-bottom': "1px",
  415. 'clear': "both",
  416. 'width': widthScrollableContent,
  417. 'background-color': "#fff",
  418. }
  419. }, [
  420. h('table', {
  421. className: "table table-bordered table-condensed",
  422. style: {
  423. 'margin-bottom': 0,
  424. 'margin-left': "-2px",
  425. }
  426. }, [
  427. h('thead', {}, [
  428. h('tr', {}, [ this.renderStickyTheadNameRowCell() ].concat( Array.apply(null, { length: allColsCount }).map(this.renderTheadColNameRowCell) )),
  429. h('tr', {}, [ this.renderStickyTheadFilterRowCell() ].concat( Array.apply(null, { length: allColsCount }).map(this.renderTheadFilterRowCell) ))
  430. ]),
  431. h(TableAjax_Tbody, {
  432. rowsPerPage: this.state.rowsPerPage,
  433. receivedRequestId: this.state.receivedRequestId,
  434. rows: this.state.rows,
  435. stickyColWidths: this.state.stickyColWidths,
  436. // store: this.props.dataStore
  437. })
  438. ])
  439. ]),
  440. h('hr', { style: { border: "5px solid red" } }),
  441. h('pre', { style: { padding: "6px 12px", border: "1px solid red", 'background-color': "#eee" } }, [
  442. "State: ",
  443. (this.state.isLoading) ? " loading... " : null,
  444. ]),
  445. h('div', { style: { padding: "6px 12px", border: "1px solid red", 'background-color': "#eee" } }, [
  446. "TEST btns: ",
  447. h('button', { onClick: this._testScrollContentTo100Left }, "scroll 100"),
  448. h('button', { onClick: this._testScrollContentTo200Left }, "scroll 200"),
  449. h('button', { onClick: this._testScrollContentTo0Left }, "scroll 0"),
  450. h('button', { onClick: this._testLoadData }, "load data"),
  451. ])
  452. ]);
  453. },
  454. _testLoadData: function () { this.props.dataStore.dispatch(this.props.dataActions.loadData(this.props.namespace)); },
  455. _testScrollContentTo100Left: function () { this._refContentEl.scrollLeft = 100; },
  456. _testScrollContentTo200Left: function () { this._refContentEl.scrollLeft = 200; },
  457. _testScrollContentTo0Left: function () { this._refContentEl.scrollLeft = 0; }
  458. });
  459. global.p5VendorJs['p5UI__TableAjax'] = p5UI__TableAjax;
  460. // export default p5UI__TableAjax