TableAjax.php.p5UI__TableAjax.js 18 KB

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