Saiku Table展示数据合并bug修复

Saiku以table的形式展示数据,如果点击了 非空的字段 按钮,则会自动进行数据合并,为空的数据行以及数据列都会自动隐藏掉。

首先我们应该定位问题:

1.查看接口返回值,会发现接口返回都正常,数值没有任何问题,所以我们能清楚的知道与后台没有关系。

2.从页面上定位问题,会发现是table渲染问题 : /saiku-ui/js/saiku/render/SaikuTableRenderer.js  (如果是编译好的saiku,请找到 saiku-server\tomcat\webapps\ROOT\js\saiku\render\SaikuTableRenderer.js  )

接下来我们来分析一下问题原因:

1.渲染页面主要是 SaikuTableRenderer.js文件中的此方法:

  SaikuTableRenderer.prototype.internalRender = function(allData, options)

2.数据内容都存放在<table>标签中,存放内容为 tableContent,tableContent包含所有的数据信息。

js代码里面主要有两层for循环,将数据取出然后以字符串形式拼凑到 tableContent 里面,最后返回到html页面中渲染。

第一次是循环取出行数据信息

for (var row = 0, rowLen = table.length; row < rowLen; row++)

第二层是第一层循环中的行数据中取出对应的列数据,然后拼凑为<tr>标记的行数据 rowContent 里面 

for (var col = 0, colLen = table[row].length; col < colLen; col++)

3.主要逻辑:

有个boolean类型的 same 标记,用来判断上下文数据信息是否一致,如果一致 same=true,后面就会自动合并

 var same = !headerSame && !isHeaderLowestLvl && (col == 0 || !topParentsDiffer(data, row, col)) && header.value === previousRow[col].value ;

我们主要来看一下这个值:header.value === previousRow[col].value ,意思就是说当前记录列值与上一条记录的列数据相同时,same就为true,让其自动合并

  自动合并的原理: 将当前记录的此列值置为空

   var value = (same ? "<div>&nbsp;</div>" : '<div rel="' + row + ":" + col + '">' + (sameAsPrevValue && Settings.ALLOW_TABLE_DATA_COLLAPSE ? '<span class="expander expanded" style="cursor: pointer;">▼</span>' : '' ) + header.value + '</div>');

不断的循环出当前行的列值,然后将列值拼凑为 rowContent

   rowContent += '<th class="' + cssclass + '" ' + (colspan > 0 ? ' colspan="' + colspan + '"' : "") + tipsy + '>' + value + '</th>';

但是下面还有个判断,就是当数据(DATA_CELL 就是标记度量值信息)为空时,saiku会将当前rowContent置为空 

  

这里三个条件分别是: 

  用户是否点击了隐藏空数据按钮,也就是我们开头说的 非空的字段 按钮 : option.hideEmpty

  当前数据信息 header.type属性 是否为 DATA_CELL 类型

  当前行数据的指标信息是否都为空 (这里上方有代码如判断,我没有全部截取图片): rowWithOnlyEmptyCells

这样子一来,问题就暴露啦

如果我有两个部门的数据信息,每个部门个 3条数据,第一个部门三条数据都完整,第二个部门第一条数据 指标值都为空,后面两条数据完整

以上逻辑就会这样判断:

  第一个部门数据正常展示,部门信息相同会自动合并,后面的数据也会正常展示,这个没问题

  第二个部门的数据,第一条数据 首先部门不会合并到第一个部门,因为当前数据的部门value值与上一条记录的部门value值不同,但是由于cell指标数据为空,所以 rowContent最后就会置为空,相当于不展示此条数据

  第二个部门第二条数据,部门数据会先判断当前数据的value是否与上一条数据的value相同,结果会发现相同,就会把当前数据的部门信息置为空,后面因为有cell指标数据,所以后面就会展示这条rowContent信息,但是此条数据不完整了,没有了部门名称信息。

  第二个部门第三条数据同第二条数据一样。

最后我们就会得出这样的结果:

  第一个部门的数据正常展示

  第二个部门的数据由于都没有部门信息就会自动合并到第一个部门去展示。

其次我们来讲一下处理方案:

能定位到问题,了解问题原因离解决问题就不远了哈哈哈哈

我这边的处理方案是增加一个boolean类型标记值 headerFlag,用于决定same值,因为其实数据展示的时候是否合并就是在用same的取值

这个headerFlag值定义在两个for循环以外,默认为true,意思就是默认情况下影响数据的合并展示

headerFlag定义

根据headerFlag的值来定义same值

当有rowContent被置为空的时候,就将headerFlag标记置为false,就是为了保证当前一行数据为空时,下一次不会自动将部门列信息置为空了

接下来我们就应该开始考虑什么时候讲headerFlag标记转为true了啦

有以下两种情况我们需要考虑到

>>>  当前行的列数据值不等于上一行数据的列数据值时,表示这是一次新的判断,我们将headerFlag置为true,一切按照原来的一样

>>> 当前tableContent中包含当前行列数据信息时,表示当前列已经存在了,我们可以让它去做合并了,就将headerFlag置为true.

这样子我们就能处理好这个table自动合并的bug了,可能还有更好的办法或者是有全的解法,如有还望不吝赐教哈,多谢~

最后提供完整的 SaikuTableRenderer.js文件(修改后的)

  1. function SaikuTableRenderer(data, options) {
  2. this._data = data;
  3. this._options = _.extend({}, SaikuRendererOptions, options);
  4. }
  5.  
  6. function getAxisLevelsName(data, axisName) {
  7. var queryData = data.query.queryModel.axes[axisName].hierarchies;
  8. var len = queryData.length;
  9. var arrLevels = [];
  10.  
  11. for (var i = 0; i < len; i++) {
  12. for (var level in queryData[i].levels) {
  13. if (queryData[i].levels.hasOwnProperty(level)) {
  14. if (Settings.COLUMN_TITLE_TABLE_USE_LEVEL_CAPTION_NAME) {
  15. arrLevels.push(queryData[i].levels[level].caption);
  16. }
  17. else {
  18. arrLevels.push(level);
  19. }
  20. }
  21. }
  22. }
  23.  
  24. return arrLevels;
  25. }
  26.  
  27. function setStyleNegativeNumber(value) {
  28. var className = '';
  29.  
  30. if (Settings.STYLE_NEGATIVE_NUMBER && parseFloat(value) < 0) {
  31. className = ' style_negative_number ';
  32. }
  33.  
  34. return className;
  35. }
  36.  
  37. function getAxisSize(data, axisName) {
  38. var queryData = data.query.queryModel.axes[axisName].hierarchies;
  39. var len = queryData.length;
  40. var axisSize = 0;
  41.  
  42. for (var i = 0; i < len; i++) {
  43. axisSize += _.size(queryData[i].levels);
  44. }
  45.  
  46. return axisSize;
  47. }
  48.  
  49. function getDomColumnsLevelsName(htmlObject) {
  50. var $htmlObject = $(htmlObject.closest('.workspace')
  51. .find('.workspace_fields')
  52. .find('.columns.axis_fields')
  53. .find('.hierarchy')
  54. .find('.d_level'));
  55. var arrLevels = [];
  56.  
  57. $.each($htmlObject, function(key, level) {
  58. if ($(level).attr('style') === 'display: list-item;') {
  59. if (Settings.COLUMN_TITLE_TABLE_USE_LEVEL_CAPTION_NAME) {
  60. arrLevels.push($(level).find('.level').attr('title'));
  61. }
  62. else {
  63. arrLevels.push($(level).find('.level').attr('level'));
  64. }
  65. }
  66. });
  67.  
  68. return arrLevels;
  69. }
  70.  
  71. /*table render method*/
  72. SaikuTableRenderer.prototype.render = function(data, options) {
  73. var self = this;
  74. if (data) {
  75. this._data = data;
  76. }
  77. if (options) {
  78. this._options = _.extend({}, SaikuRendererOptions, options);
  79. }
  80.  
  81. if (typeof this._data == "undefined") {
  82. return;
  83. }
  84.  
  85. if (this._data != null && this._data.error != null) {
  86. return;
  87. }
  88. if (this._data == null || (this._data.cellset && this._data.cellset.length === 0)) {
  89. return;
  90. }
  91.  
  92. this.hideEmpty = this._options.hideEmpty;
  93.  
  94. if (this._options.htmlObject) {
  95. // $(this._options.htmlObject).stickyTableHeaders("destroy");
  96.  
  97. // in case we have some left over scrollers
  98. if (self._options.hasOwnProperty('batch')) {
  99. $(self._options.htmlObject).parent().parent().unbind('scroll');
  100. }
  101.  
  102. _.defer(function(that) {
  103. if (self._options.hasOwnProperty('batch') && !self._options.hasOwnProperty('batchSize')) {
  104. self._options['batchSize'] = 1000;
  105. }
  106.  
  107. // the key method to render data by table form. 20190423
  108. var html = self.internalRender(self._data, self._options);
  109. $(self._options.htmlObject).html(html);
  110. // Render the totals summary
  111. $('#totals_summary').remove(); // Remove one previous totals div, if present
  112. $(self._options.htmlObject).after(self.renderSummary(data)); // Render the new summary
  113.  
  114. // $(self._options.htmlObject).stickyTableHeaders( { container: self._options.htmlObject.parent().parent(), fixedOffset: self._options.htmlObject.parent().parent().offset().top });
  115.  
  116. _.defer(function(that) {
  117. if (self._options.hasOwnProperty('batch') && self._options.hasBatchResult) {
  118. var batchRow = 0;
  119. var batchIsRunning = false;
  120. var batchIntervalSize = self._options.hasOwnProperty('batchIntervalSize') ? self._options.batchIntervalSize : 20;
  121. var batchIntervalTime = self._options.hasOwnProperty('batchIntervalTime') ? self._options.batchIntervalTime : 20;
  122.  
  123. var len = self._options.batchResult.length;
  124.  
  125. var batchInsert = function() {
  126. // maybe add check for reach table bottom - ($('.workspace_results').scrollTop() , $('.workspace_results table').height()
  127. if (!batchIsRunning && len > 0 && batchRow < len) {
  128. batchIsRunning = true;
  129. var batchContent = "";
  130. var startb = batchRow;
  131. for (var i = 0; batchRow < len && i < batchIntervalSize ; i++, batchRow++) {
  132. batchContent += self._options.batchResult[batchRow];
  133. }
  134. if (batchRow > startb) {
  135. $(self._options.htmlObject).append( $(batchContent));
  136. }
  137. batchIsRunning = false;
  138. }
  139. if (batchRow >= len) {
  140. $(self._options.htmlObject).parent().parent().unbind('scroll');
  141. }
  142. };
  143.  
  144. var lazyBatchInsert = _.debounce(batchInsert, batchIntervalTime);
  145. $(self._options.htmlObject).parent().parent().scroll(function () {
  146. lazyBatchInsert();
  147. });
  148. }
  149. });
  150. return html;
  151. });
  152. } else {
  153. var html = this.internalRender(this._data, self._options);
  154. return html;
  155. }
  156.  
  157. };
  158.  
  159. SaikuTableRenderer.prototype.clear = function(data, options) {
  160. var self = this;
  161. if (this._options && this._options.htmlObject && this._options.hasOwnProperty('batch')) {
  162. $(self._options.htmlObject).parent().parent().unbind('scroll');
  163. }
  164.  
  165. };
  166.  
  167. SaikuTableRenderer.prototype.processData = function(data, options) {
  168. this._hasProcessed = true;
  169. };
  170.  
  171. function genTotalDataCells(currentIndex, cellIndex, scanSums, scanIndexes, lists) {
  172. var contents = '';
  173. var lists = lists[ROWS];
  174.  
  175. for (var i = scanSums.length - 1; i >= 0; i--) {
  176. if (currentIndex == scanSums[i]) {
  177. var currentListNode = lists[i][scanIndexes[i]];
  178. for (var m = 0; m < currentListNode.cells.length; m++) {
  179. contents += '<td class="data total">' + currentListNode.cells[m][cellIndex].value + '</td>';
  180. }
  181.  
  182. scanIndexes[i]++;
  183. if (scanIndexes[i] < lists[i].length)
  184. scanSums[i] += lists[i][scanIndexes[i]].width;
  185. }
  186. }
  187.  
  188. return contents;
  189. }
  190.  
  191. function genTotalHeaderCells(currentIndex, bottom, scanSums, scanIndexes, lists, wrapContent) {
  192. var contents = '';
  193. for (var i = bottom; i >= 0; i--) {
  194. if (currentIndex == scanSums[i]) {
  195. var currentListNode = lists[i][scanIndexes[i]];
  196. var cssClass;
  197. if (i == 0 && bottom == 1)
  198. cssClass = "col";
  199. else if (i == bottom)
  200. cssClass = "col_total_corner";
  201. else if (i == bottom - 1 && currentListNode.captions)
  202. cssClass = "col_total_first";
  203. else cssClass = "col_null";
  204.  
  205. for (var m = 0; m < currentListNode.cells.length; m++) {
  206. var text = ' ';
  207. if (bottom == lists.length - 1) {
  208. if (currentListNode.captions) {
  209. text = lists[i][scanIndexes[i]].captions[m];
  210. }
  211. if (i == 0 && scanIndexes[i] == 0) {
  212. if (currentListNode.captions)
  213. text += " ";
  214. else text = "";
  215. text += (wrapContent ? "<span class='i18n'>Grand Total</span>" : "Grand Total");
  216. }
  217. }
  218. contents += '<th class="' + cssClass + '">'
  219. + (wrapContent ? '<div>' + text + '</div>' : text ) + '</th>';
  220. }
  221. scanIndexes[i]++;
  222. if (scanIndexes[i] < lists[i].length)
  223. scanSums[i] += lists[i][scanIndexes[i]].width;
  224. }
  225. }
  226. return contents;
  227. }
  228.  
  229. function totalIntersectionCells(currentIndex, bottom, scanSums, scanIndexes, lists) {
  230. var contents = '';
  231. for (var i = bottom; i >= 0; i--) {
  232. if (currentIndex == scanSums[i]) {
  233. var currentListNode = lists[i][scanIndexes[i]];
  234. var cssClass = "data total";
  235. for (var m = 0; m < currentListNode.cells.length; m++) {
  236. var text = ' ';
  237. contents += '<td class="' + cssClass + '">' + text + '</td>';
  238. }
  239. scanIndexes[i]++;
  240. if (scanIndexes[i] < lists[i].length)
  241. scanSums[i] += lists[i][scanIndexes[i]].width;
  242. }
  243. }
  244. return contents;
  245. }
  246.  
  247. function isNextTotalsRow(currentIndex, scanSums, scanIndexes, totalsLists, wrapContent) {
  248. var colLists = totalsLists[COLUMNS];
  249. var colScanSums = scanSums[COLUMNS];
  250. var colScanIndexes = scanIndexes[COLUMNS];
  251. var bottom = colLists.length - 2;
  252. var contents = -1;
  253. for (var i = bottom; i >= 0; i--) {
  254. if (currentIndex == colScanSums[i]) {
  255. for (var m = 0; m < colLists[i][colScanIndexes[i]].cells.length; m++) {
  256. contents += '<tr>';
  257. for (var j = 0; j <= bottom; j++) {
  258. var cssClass;
  259. var text = ' ';
  260. if (i == 0 && j == 0)
  261. cssClass = 'row';
  262. else if (i == j + 1){
  263. cssClass = 'row_total_corner';
  264. return j;
  265. }
  266. else if (i == j && colLists[i][colScanIndexes[i]].captions) {
  267. cssClass = 'row_total_first';
  268. } else if (i < j + 1)
  269. cssClass = 'row_total';
  270. else
  271. cssClass = 'row_null';
  272. if (j == bottom ) {
  273. if (colLists[i][colScanIndexes[i]].captions) {
  274. text = colLists[i][colScanIndexes[i]].captions[m];
  275. }
  276. if (i == 0 && colScanIndexes[i] == 0) {
  277. if (colLists[i][colScanIndexes[i]].captions)
  278. text += " ";
  279. else text = "";
  280. text += (wrapContent ? "<span class='i18n'>Grand Total</span>" : "Grand Total");
  281. }
  282. }
  283.  
  284. }
  285. }
  286. }
  287. }
  288. return -1;
  289. }
  290.  
  291. function genTotalHeaderRowCells(currentIndex, scanSums, scanIndexes, totalsLists, wrapContent) {
  292. var colLists = totalsLists[COLUMNS];
  293. var colScanSums = scanSums[COLUMNS];
  294. var colScanIndexes = scanIndexes[COLUMNS];
  295. var bottom = colLists.length - 2;
  296. var contents = '';
  297. for (var i = bottom; i >= 0; i--) {
  298. if (currentIndex == colScanSums[i]) {
  299. for (var m = 0; m < colLists[i][colScanIndexes[i]].cells.length; m++) {
  300. contents += '<tr>';
  301. for (var j = 0; j <= bottom; j++) {
  302. var cssClass;
  303. var text = ' ';
  304. if (i == 0 && j == 0)
  305. cssClass = 'row';
  306. else if (i == j + 1)
  307. cssClass = 'row_total_corner';
  308. else if (i == j && colLists[i][colScanIndexes[i]].captions) {
  309. cssClass = 'row_total_first';
  310. } else if (i < j + 1)
  311. cssClass = 'row_total';
  312. else
  313. cssClass = 'row_null';
  314. if (j == bottom ) {
  315. if (colLists[i][colScanIndexes[i]].captions) {
  316. text = colLists[i][colScanIndexes[i]].captions[m];
  317. }
  318. if (i == 0 && colScanIndexes[i] == 0) {
  319. if (colLists[i][colScanIndexes[i]].captions)
  320. text += " ";
  321. else text = "";
  322. text += (wrapContent ? "<span class='i18n'>Grand Total</span>" : "Grand Total");
  323. }
  324. }
  325. contents += '<th class="' + cssClass + '">'
  326. + (wrapContent ? '<div>' + text + '</div>' : text ) + '</th>';
  327.  
  328. }
  329.  
  330. var scanIndexes = {};
  331. var scanSums = {};
  332.  
  333. if (totalsLists[ROWS]) {
  334. for (var z = 0; z < totalsLists[ROWS].length; z++) {
  335. scanIndexes[z] = 0;
  336. scanSums[z] = totalsLists[ROWS][z][scanIndexes[z]].width;
  337. }
  338. }
  339.  
  340. for (var k = 0; k < colLists[i][colScanIndexes[i]].cells[m].length; k++) {
  341. contents += '<td class="data total">' + colLists[i][colScanIndexes[i]].cells[m][k].value + '</td>';
  342.  
  343. if (totalsLists[ROWS]) {
  344. contents += totalIntersectionCells(k + 1, totalsLists[ROWS].length - 1, scanSums, scanIndexes, totalsLists[ROWS]);
  345. }
  346. }
  347.  
  348. contents += '</tr>';
  349. }
  350.  
  351. colScanIndexes[i]++;
  352.  
  353. if (colScanIndexes[i] < colLists[i].length) {
  354. colScanSums[i] += colLists[i][colScanIndexes[i]].width;
  355. }
  356. }
  357. }
  358. return contents;
  359. }
  360.  
  361. var ROWS = "ROWS";
  362. var COLUMNS = "COLUMNS";
  363.  
  364. function nextParentsDiffer(data, row, col) {
  365. while (row-- > 0) {
  366. if (data[row][col].properties.uniquename != data[row][col + 1].properties.uniquename)
  367. return true;
  368. }
  369. return false;
  370. }
  371.  
  372. function topParentsDiffer(data, row, col) {
  373. while (col-- > 0)
  374. if (data[row][col].properties.uniquename != data[row - 1][col].properties.uniquename)
  375. return true;
  376. return false;
  377. }
  378.  
  379. /**
  380. * This function is intended to traverse the totals arrays and cleanup empty
  381. * totals. This will optimize the query result on screen, displaying just the
  382. * needed cells.
  383. * @param dirs The direction array ['ROWS', 'COLUMNS']
  384. * @param totalsLists The totals from allData.rowTotalsLists and allData.colTotalsLists.
  385. */
  386. function cleanupTotals(dirs, totalsLists) {
  387. // For each direction (ROWS/COLUMNS)
  388. for (var dirIndex = 0; dirIndex < dirs.length; dirIndex++) {
  389. var dir = dirs[dirIndex];
  390.  
  391. // If there are defined totals
  392. if (totalsLists[dir]) {
  393. var isEmpty = true; // A flag to indicate if this total is empty
  394. for (var row = 0; row < totalsLists[dir].length; row++) {
  395. var totalsInfoArray = totalsLists[dir][row];
  396. for (var totalIndex = 0; totalIndex < totalsInfoArray.length; totalIndex++) {
  397. var cells = totalsLists[dir][row][totalIndex].cells;
  398. for (var cellIndex = 0; cellIndex < cells.length; cellIndex++) {
  399. var cellArray = cells[cellIndex];
  400. // For each total cell
  401. for (var i = 0; i < cellArray.length; i++) {
  402. var cell = cellArray[i];
  403. // If it contains a value different from empty
  404. if (cell.value !== '-') {
  405. isEmpty = false; // So, this total is not empty
  406. }
  407. }
  408. }
  409. }
  410. }
  411.  
  412. if (isEmpty) { // If this total is empty
  413. totalsLists[dir] = null; // Remove it
  414. }
  415. }
  416. }
  417. }
  418.  
  419. /*the main method to render data by table form. 20190423*/
  420. SaikuTableRenderer.prototype.internalRender = function(allData, options) {
  421. var tableContent = "";
  422. var rowContent = "";
  423. var data = allData.cellset;
  424.  
  425. var newRowContent = '';
  426. var arrRowData = [];
  427. var objRowData = [];
  428.  
  429. var table = data ? data : [];
  430. var colSpan;
  431. var colValue;
  432. var isHeaderLowestLvl;
  433. var isBody = false;
  434. var firstColumn;
  435. var isLastColumn, isLastRow;
  436. var nextHeader;
  437. var processedRowHeader = false;
  438. var lowestRowLvl = 0;
  439. var rowGroups = [];
  440. var batchSize = null;
  441. var batchStarted = false;
  442. var isColHeader = false, isColHeaderDone = false;
  443. var resultRows = [];
  444. var wrapContent = true;
  445. if (options) {
  446. batchSize = options.hasOwnProperty('batchSize') ? options.batchSize : null;
  447. wrapContent = options.hasOwnProperty('wrapContent') ? options.wrapContent : true;
  448. }
  449. var totalsLists = {};
  450. totalsLists[COLUMNS] = allData.rowTotalsLists;
  451. totalsLists[ROWS] = allData.colTotalsLists;
  452.  
  453. var scanSums = {};
  454. var scanIndexes = {};
  455.  
  456. var dirs = [ROWS, COLUMNS];
  457.  
  458. var hasMeasures = allData.query && allData.query.queryModel && allData.query.queryModel.details
  459. ? allData.query.queryModel.details.measures.length
  460. : 0;
  461.  
  462. if (typeof this._options.htmlObject === 'object' &&
  463. Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE &&
  464. hasMeasures > 0 &&
  465. allData.query.type === 'QUERYMODEL' &&
  466. allData.query.queryModel.details.axis === 'COLUMNS' &&
  467. allData.query.queryModel.details.location === 'BOTTOM') {
  468.  
  469. var arrColumnTitleTable = getAxisLevelsName(allData, COLUMNS);
  470. var arrDomColumnTitleTable = getDomColumnsLevelsName(this._options.htmlObject);
  471. var colspanColumnTitleTable = getAxisSize(allData, ROWS);
  472. var auxColumnTitleTable = 0;
  473.  
  474. if (arrColumnTitleTable.length === arrDomColumnTitleTable.length) {
  475. arrColumnTitleTable = arrDomColumnTitleTable;
  476. }
  477. else {
  478. arrColumnTitleTable = _.intersection(arrDomColumnTitleTable, arrColumnTitleTable);
  479. }
  480. }
  481.  
  482. for (var i = 0; i < dirs.length; i++) {
  483. scanSums[dirs[i]] = new Array();
  484. scanIndexes[dirs[i]] = new Array();
  485. }
  486.  
  487. // Here we cleaup the empty totals
  488. cleanupTotals(dirs, totalsLists);
  489.  
  490. if (totalsLists[COLUMNS]) {
  491. for (var i = 0; i < totalsLists[COLUMNS].length; i++) {
  492. scanIndexes[COLUMNS][i] = 0;
  493. scanSums[COLUMNS][i] = totalsLists[COLUMNS][i][scanIndexes[COLUMNS][i]].width;
  494. }
  495. }
  496.  
  497. var headerFlag=true;// add this flag to solve the bug when same data to merge。 20190423
  498.  
  499. for (var row = 0, rowLen = table.length; row < rowLen; row++) {
  500. var rowShifted = row - allData.topOffset;
  501. colSpan = 1;
  502. colValue = "";
  503. isHeaderLowestLvl = false;
  504. isLastColumn = false;
  505. isLastRow = false;
  506. isColHeader = false;
  507. var headerSame = false;
  508.  
  509. if (totalsLists[ROWS]) {
  510. for (var i = 0; i < totalsLists[ROWS].length; i++) {
  511. scanIndexes[ROWS][i] = 0;
  512. scanSums[ROWS][i] = totalsLists[ROWS][i][scanIndexes[ROWS][i]].width;
  513. }
  514. }
  515.  
  516. rowWithOnlyEmptyCells = true;
  517. rowContent = "<tr>";
  518. var header = null;
  519.  
  520. if (row === 0) {
  521. rowContent = "<thead>" + rowContent;
  522. }
  523.  
  524. if (typeof this._options.htmlObject === 'object' &&
  525. Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE &&
  526. hasMeasures > 0 &&
  527. allData.query.type === 'QUERYMODEL' &&
  528. allData.query.queryModel.details.axis === 'COLUMNS' &&
  529. allData.query.queryModel.details.location === 'BOTTOM' &&
  530. auxColumnTitleTable < arrColumnTitleTable.length) {
  531.  
  532. rowContent += '<th class="row_header" style="text-align: right;" colspan="' + colspanColumnTitleTable + '" title="' + arrColumnTitleTable[auxColumnTitleTable] + '">'
  533. + (wrapContent ? '<div>' + arrColumnTitleTable[auxColumnTitleTable] + '</div>' : arrColumnTitleTable[auxColumnTitleTable])
  534. + '</th>';
  535.  
  536. auxColumnTitleTable += 1;
  537. }
  538.  
  539. for (var col = 0, colLen = table[row].length; col < colLen; col++) {
  540. var colShifted = col - allData.leftOffset;
  541. header = data[row][col];
  542.  
  543. if (header.type === "COLUMN_HEADER") {
  544. isColHeader = true;
  545. }
  546.  
  547. // If the cell is a column header and is null (top left of table)
  548. if (header.type === "COLUMN_HEADER" && header.value === "null" && (firstColumn == null || col < firstColumn)) {
  549. if (((!Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE || (Settings.ALLOW_AXIS_COLUMN_TITLE_TABLE && allData.query.queryModel.details.location !== 'BOTTOM')) || hasMeasures === 0) ||
  550. allData.query.type === 'MDX') {
  551. rowContent += '<th class="all_null"> </th>';
  552. }
  553. } // If the cell is a column header and isn't null (column header of table)
  554. else if (header.type === "COLUMN_HEADER") {
  555. if (firstColumn == null) {
  556. firstColumn = col;
  557. }
  558. if (table[row].length == col+1)
  559. isLastColumn = true;
  560. else
  561. nextHeader = data[row][col+1];
  562.  
  563. if (isLastColumn) {
  564. // Last column in a row...
  565. if (header.value == "null") {
  566. rowContent += '<th class="col_null"> </th>';
  567. } else {
  568. if (totalsLists[ROWS])
  569. colSpan = totalsLists[ROWS][row + 1][scanIndexes[ROWS][row + 1]].span;
  570. rowContent += '<th class="col" style="text-align: center;" colspan="' + colSpan + '" title="' + header.value + '">'
  571. + (wrapContent ? '<div rel="' + row + ":" + col +'">' + header.value + '</div>' : header.value)
  572. + '</th>';
  573. }
  574.  
  575. } else {
  576. // All the rest...
  577. var groupChange = (col > 1 && row > 1 && !isHeaderLowestLvl && col > firstColumn) ?
  578. data[row-1][col+1].value != data[row-1][col].value || data[row-1][col+1].properties.uniquename != data[row-1][col].properties.uniquename
  579. : false;
  580.  
  581. var maxColspan = colSpan > 999 ? true : false;
  582. if (header.value != nextHeader.value || nextParentsDiffer(data, row, col) || isHeaderLowestLvl || groupChange || maxColspan) {
  583. if (header.value == "null") {
  584. rowContent += '<th class="col_null" colspan="' + colSpan + '"> </th>';
  585. } else {
  586. if (totalsLists[ROWS])
  587. colSpan = totalsLists[ROWS][row + 1][scanIndexes[ROWS][row + 1]].span;
  588. rowContent += '<th class="col" style="text-align: center;" colspan="' + (colSpan == 0 ? 1 : colSpan) + '" title="' + header.value + '">'
  589. + (wrapContent ? '<div rel="' + row + ":" + col +'">' + header.value + '</div>' : header.value)
  590. + '</th>';
  591. }
  592. colSpan = 1;
  593. } else {
  594. colSpan++;
  595. }
  596. }
  597. if (totalsLists[ROWS])
  598. rowContent += genTotalHeaderCells(col - allData.leftOffset + 1, row + 1, scanSums[ROWS], scanIndexes[ROWS], totalsLists[ROWS], wrapContent);
  599. } // If the cell is a row header and is null (grouped row header)
  600. else if (header.type === "ROW_HEADER" && header.value === "null") {
  601. rowContent += '<th class="row_null"> </th>';
  602. } // If the cell is a row header and isn't null (last row header)
  603. else if (header.type === "ROW_HEADER") {
  604. if (lowestRowLvl == col)
  605. isHeaderLowestLvl = true;
  606. else
  607. nextHeader = data[row][col+1];
  608.  
  609. var previousRow = data[row - 1];
  610. var nextRow = data[row + 1];
  611. // when same set fixed value is false ,It means the same data will not merge。table data will show row by row.20190423
  612. //var same=false;
  613.  
  614. /*judge the current value and previousRow value,
  615. if equals ,all set comeback,set the headerFlag is true,
  616. we can judge the data as usual. 20190423*/
  617. if(header.value !== previousRow[col].value){
  618. headerFlag =true;
  619. }
  620.  
  621. /*judge the tableContent include value or not, if include ,
  622. set the headerFlag value is true to avoid repeat datas showed in table.20190423*/
  623. if(tableContent.indexOf(header.value) > -1 ){
  624. headerFlag =true;
  625. }
  626.  
  627. /*add headerFlag to judge the data is same ,then control the data merge wheather or not.20190423 */
  628. var same = !headerSame && !isHeaderLowestLvl && (col == 0 || !topParentsDiffer(data, row, col))
  629. && header.value === previousRow[col].value && headerFlag;
  630.  
  631. headerSame = !same;
  632. var sameAsPrevValue = false;
  633. if(Settings.ALLOW_TABLE_DATA_COLLAPSE){
  634. if (row > 0 && row < rowLen - 1) {
  635. if (totalsLists[ROWS] == null || (col <= colLen - totalsLists[ROWS].length - 1)) {
  636. var checkOther = true;
  637. if (totalsLists[COLUMNS] && rowShifted >= 0 && col <= isNextTotalsRow(rowShifted + 1, scanSums, scanIndexes, totalsLists, wrapContent)) {
  638. sameAsPrevValue = true;
  639. checkOther = false;
  640. }
  641. if (checkOther && nextRow[col].value == header.value) {
  642. if (col > 0) {
  643. for (var j = 0; j < col; j++) {
  644. if (nextRow[j].value == data[row][j].value) {
  645. sameAsPrevValue = true;
  646. } else {
  647. sameAsPrevValue = false;
  648. break;
  649. }
  650. }
  651. } else {
  652. sameAsPrevValue = true;
  653. }
  654. }
  655. }
  656. } else if(row > 0 && row == rowLen - 1) {
  657. if (totalsLists[COLUMNS] && rowShifted >= 0 && col <= isNextTotalsRow(rowShifted + 1, scanSums, scanIndexes, totalsLists, wrapContent)) {
  658. sameAsPrevValue = true;
  659. }
  660. }
  661. }
  662. var value = (same ? "<div> </div>" : '<div rel="' + row + ":" + col + '">'
  663. + (sameAsPrevValue && Settings.ALLOW_TABLE_DATA_COLLAPSE ? '<span class="expander expanded" style="cursor: pointer;">▼</span>' : '' ) + header.value + '</div>');
  664. if (!wrapContent) {
  665. value = (same ? " " : header.value );
  666. }
  667. var tipsy = "";
  668. /* var tipsy = ' original-title="';
  669. if (!same && header.metaproperties) {
  670. for (key in header.metaproperties) {
  671. if (key.substring(0,1) != "$" && key.substring(1,2).toUpperCase() != key.substring(1,2)) {
  672. tipsy += "<b>" + safe_tags_replace(key) + "</b> : " + safe_tags_replace(header.metaproperties[key]) + "<br>";
  673. }
  674. }
  675. }
  676. tipsy += '"';
  677. */
  678. var cssclass = (same ? "row_null" : "row");
  679. var colspan = 0;
  680.  
  681. if (!isHeaderLowestLvl && (typeof nextHeader == "undefined" || nextHeader.value === "null")) {
  682. colspan = 1;
  683. var group = header.properties.dimension;
  684. var level = header.properties.level;
  685. var groupWidth = (group in rowGroups ? rowGroups[group].length - rowGroups[group].indexOf(level) : 1);
  686. for (var k = col + 1; colspan < groupWidth && k <= (lowestRowLvl+1) && data[row][k] !== "null"; k++) {
  687. colspan = k - col;
  688. }
  689. col = col + colspan -1;
  690. }
  691.  
  692. /*when the content is to long ,we will set new line to show it.*/
  693. // eg value: <div rel="3:0">新業務及保單行政部</div>
  694. if(cssclass == "row" && value.length>0){
  695. var startPos = value.indexOf('>'); //find start position of the tag. eg: <div rel="3:0">
  696. var endPos = value.lastIndexOf('<'); //find end position of the tag. eg: </div>
  697. var tmpValue = value.substr( startPos+1 ,endPos-startPos-1); // get the content value. eg: 新業務及保單行政部
  698. //将value值每隔40个字自动加上换行符
  699. //each 40 character add one <br> tag to get new line.
  700. if(tmpValue.length>120){
  701. tmpValue = tmpValue.substr(0,40)+"<br/>"+tmpValue.substr(40,40)+"<br/>"+tmpValue.substr(80,40)+"<br/>"+tmpValue.substr(120,tmpValue.length-120);
  702. }else if(tmpValue.length>80){
  703. tmpValue = tmpValue.substr(0,40)+"<br/>"+tmpValue.substr(40,40)+"<br/>"+tmpValue.substr(80,tmpValue.length-80);
  704. }else if(tmpValue.length>40){
  705. tmpValue = tmpValue.substr(0,40)+"<br/>"+tmpValue.substr(40,tmpValue.length-40);
  706. }
  707.  
  708. // compared with old value, this value only add <br> tag for show data in table more beautiful.
  709. value = value.substr(0,startPos+1) + tmpValue + value.substr(endPos,value.length-endPos);
  710. }
  711. rowContent += '<th class="' + cssclass + '" ' + (colspan > 0 ? ' colspan="' + colspan + '"' : "") + tipsy + '>' + value + '</th>';
  712. }
  713. else if (header.type === "ROW_HEADER_HEADER") {
  714. var hierName = function(data) {
  715. var hier = data.properties.hierarchy;
  716. var name = hier.replace(/[\[\]]/gi, '').split('.')[1]
  717. ? hier.replace(/[\[\]]/gi, '').split('.')[1]
  718. : hier.replace(/[\[\]]/gi, '').split('.')[0];
  719.  
  720. return name;
  721. };
  722. var arrPosRowData = [];
  723.  
  724. if (_.contains(arrRowData, header.value)) {
  725. for (var i = 0; i < arrRowData.length; i++) {
  726. if (arrRowData[i] === header.value) {
  727. arrPosRowData.push(i);
  728. }
  729. }
  730.  
  731. arrPosRowData.push(col);
  732. }
  733.  
  734. rowContent += '<th class="row_header">' + (wrapContent ? '<div>' + header.value + '</div>' : header.value) + '</th>';
  735.  
  736. arrRowData.push(header.value);
  737. objRowData.push({
  738. name: header.value,
  739. hierName: hierName(header) + '/' + header.value
  740. });
  741.  
  742. isHeaderLowestLvl = true;
  743. processedRowHeader = true;
  744. lowestRowLvl = col;
  745. if (header.properties.hasOwnProperty("dimension")) {
  746. var group = header.properties.dimension;
  747. if (!(group in rowGroups)) {
  748. rowGroups[group] = [];
  749. }
  750. rowGroups[group].push(header.properties.level);
  751. }
  752.  
  753. if (arrPosRowData.length > 0) {
  754. var aux = 0;
  755.  
  756. rowContent = '<tr>';
  757.  
  758. if (row === 0) {
  759. rowContent = '<thead>' + rowContent;
  760. }
  761.  
  762. for (var i = 0; i < objRowData.length; i++) {
  763. if (arrPosRowData[aux] === i) {
  764. newRowContent += '<th class="row_header">' + (wrapContent ? '<div>' + objRowData[i].hierName + '</div>' : objRowData[i].hierName) + '</th>';
  765. aux += 1;
  766. }
  767. else {
  768. newRowContent += '<th class="row_header">' + (wrapContent ? '<div>' + objRowData[i].name + '</div>' : objRowData[i].name) + '</th>';
  769. }
  770. }
  771.  
  772. rowContent += newRowContent;
  773. }
  774. } // If the cell is a normal data cell
  775. else if (header.type === "DATA_CELL") {
  776. batchStarted = true;
  777. var color = "";
  778. var val = _.isEmpty(header.value) ? Settings.EMPTY_VALUE_CHARACTER : header.value;
  779. var arrow = "";
  780.  
  781. if (header.properties.hasOwnProperty('image')) {
  782. var img_height = header.properties.hasOwnProperty('image_height') ? " height='" + header.properties.image_height + "'" : "";
  783. var img_width = header.properties.hasOwnProperty('image_width') ? " width='" + header.properties.image_width + "'" : "";
  784. val = "<img " + img_height + " " + img_width + " style='padding-left: 5px' src='" + header.properties.image + "' border='0'>";
  785. }
  786.  
  787. // Just apply formatting to non-empty cells
  788. if (val !== '-' && val !== '' && header.properties.hasOwnProperty('style')) {
  789. color = " style='background-color: " + header.properties.style + "' ";
  790. }
  791. if (header.properties.hasOwnProperty('link')) {
  792. val = "<a target='__blank' href='" + header.properties.link + "'>" + val + "</a>";
  793. }
  794. if (header.properties.hasOwnProperty('arrow')) {
  795. arrow = "<img height='10' width='10' style='padding-left: 5px' src='./images/arrow-" + header.properties.arrow + ".gif' border='0'>";
  796. }
  797.  
  798. if (val !== '-' && val !== '') {
  799. rowWithOnlyEmptyCells = false;
  800. }
  801.  
  802. rowContent += '<td class="data" ' + color + '>'
  803. + (wrapContent ? '<div class="datadiv '+ setStyleNegativeNumber(header.properties.raw) + '" alt="' + header.properties.raw + '" rel="' + header.properties.position + '">' : "")
  804. + val + arrow
  805. + (wrapContent ? '</div>' : '') + '</td>';
  806. if (totalsLists[ROWS])
  807. rowContent += genTotalDataCells(colShifted + 1, rowShifted, scanSums[ROWS], scanIndexes[ROWS], totalsLists, wrapContent);
  808. }
  809. }
  810. rowContent += "</tr>";
  811.  
  812. // Change it to let hideEmpty true by default
  813. if (options.hideEmpty && header.type === "DATA_CELL" && rowWithOnlyEmptyCells) {
  814. /*when data_cell is null,set the headerFlag is false ,
  815. to fix the problem data merge inccrrect.
  816. */
  817. headerFlag=false;
  818. rowContent = '';
  819. }
  820.  
  821. var totals = "";
  822. if (totalsLists[COLUMNS] && rowShifted >= 0) {
  823. totals += genTotalHeaderRowCells(rowShifted + 1, scanSums, scanIndexes, totalsLists, wrapContent);
  824. }
  825. if (batchStarted && batchSize) {
  826. if (row <= batchSize) {
  827. if (!isColHeader && !isColHeaderDone) {
  828. tableContent += "</thead><tbody>";
  829. isColHeaderDone = true;
  830. }
  831. tableContent += rowContent;
  832. if (totals.length > 0) {
  833. tableContent += totals;
  834. }
  835.  
  836. } else {
  837. resultRows.push(rowContent);
  838. if (totals.length > 0) {
  839. resultRows.push(totals);
  840. }
  841.  
  842. }
  843. } else {
  844. if (!isColHeader && !isColHeaderDone) {
  845. tableContent += "</thead><tbody>";
  846. isColHeaderDone = true;
  847. }
  848. tableContent += rowContent;
  849. if (totals.length > 0) {
  850. tableContent += totals;
  851. }
  852. }
  853. }
  854. if (options) {
  855. options['batchResult'] = resultRows;
  856. options['hasBatchResult'] = resultRows.length > 0;
  857. }
  858. return "<table>" + tableContent + "</tbody></table>";
  859. };
  860.  
  861. SaikuTableRenderer.prototype.renderSummary = function(data) {
  862. if (data && data.query) {
  863. var hasSomethingToRender = false;
  864. var measures = data.query.queryModel.details
  865. ? data.query.queryModel.details.measures
  866. : [];
  867. var summaryData = {};
  868.  
  869. for (var i = 0; i < measures.length; i++) {
  870. var m = measures[i];
  871. if (m.aggregators) {
  872. for (var j = 0; j < m.aggregators.length; j++) {
  873. var a = m.aggregators[j];
  874. if (a.indexOf('_') > 0) {
  875. var tokens = a.split('_');
  876. var aggregator = tokens[0];
  877. var axis = tokens[1];
  878.  
  879. if (aggregator !== 'nil' && aggregator !== 'not') {
  880. hasSomethingToRender = true;
  881. aggregator = aggregator.capitalizeFirstLetter();
  882. if (!(axis in summaryData)) summaryData[axis] = [];
  883. summaryData[axis].push(m.name + ": " + aggregator);
  884. }
  885. }
  886. }
  887. }
  888. }
  889.  
  890. if (hasSomethingToRender) {
  891. var summary = "<div id='totals_summary'><br/>";
  892.  
  893. $.each(summaryData, function(key, aggregators) {
  894. summary += "<h3>" + key.capitalizeFirstLetter();
  895. for (var i = 0; i < aggregators.length; i++) {
  896. summary += "<br/> " + aggregators[i];
  897. }
  898. summary += "</h3>";
  899. });
  900.  
  901. return summary + "</div>";
  902. }
  903. }
  904.  
  905. return "";
  906. };
  907.  
  908. String.prototype.capitalizeFirstLetter = function() {
  909. return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
  910. }

  

Saiku Table展示数据合并bug修复(二十五)的更多相关文章

  1. Saiku控制页面展示的数据过长自动换行(二十四)

    Saiku控制页面展示的数据过长自动换行 目前用到saiku来展示数据,发现数据文本过长也不会自动换行,然而用户那边又需要换行(会好看些),所以就来改一改源码啦 首先我们使用谷歌浏览器 inspect ...

  2. JAVA基础再回首(二十五)——Lock锁的使用、死锁问题、多线程生产者和消费者、线程池、匿名内部类使用多线程、定时器、面试题

    JAVA基础再回首(二十五)--Lock锁的使用.死锁问题.多线程生产者和消费者.线程池.匿名内部类使用多线程.定时器.面试题 版权声明:转载必须注明本文转自程序猿杜鹏程的博客:http://blog ...

  3. JAVA之旅(二十五)——文件复制,字符流的缓冲区,BufferedWriter,BufferedReader,通过缓冲区复制文件,readLine工作原理,自定义readLine

    JAVA之旅(二十五)--文件复制,字符流的缓冲区,BufferedWriter,BufferedReader,通过缓冲区复制文件,readLine工作原理,自定义readLine 我们继续IO上个篇 ...

  4. Java进阶(二十五)Java连接mysql数据库(底层实现)

    Java进阶(二十五)Java连接mysql数据库(底层实现) 前言 很长时间没有系统的使用java做项目了.现在需要使用java完成一个实验,其中涉及到java连接数据库.让自己来写,记忆中已无从搜 ...

  5. 二十五种网页加速方法和seo优化技巧

    一.使用良好的结构 可扩展 HTML (XHTML) 具有许多优势,但是其缺点也很明显.XHTML 可能使您的页面更加符合标准,但是它大量使用标记(强制性的 <start> 和 <e ...

  6. C#学习基础概念二十五问

    C#学习基础概念二十五问 1.静态变量和非静态变量的区别?2.const 和 static readonly 区别?3.extern 是什么意思?4.abstract 是什么意思?5.internal ...

  7. [转载]Windows&nbsp;Server&nbsp;2008&nbsp;R2&nbsp;之二十五AD&nbsp;RMS信任策略

    原文地址:Windows Server 2008 R2 之二十五AD RMS信任策略作者:从心开始 可以通过添加信任策略,让 AD RMS 可以处理由不同的 AD RMS 群集进行权限保护的内容的授权 ...

  8. 微信小程序把玩(二十五)loading组件

    原文:微信小程序把玩(二十五)loading组件 loading通常使用在请求网络数据时的一种方式,通过hidden属性设置显示与否 主要属性: wxml <!----> <butt ...

  9. FreeSql (二十五)延时加载

    FreeSql 支持导航属性延时加载,即当我们需要用到的时候才进行加载(读取),支持1对1.多对1.1对多.多对多关系的导航属性. 当我们希望浏览某条订单信息的时候,才显示其对应的订单详细记录时,我们 ...

随机推荐

  1. puppeteer实现线上服务器任意区域截图

    整个九月份由于业务繁重以及玩心颇重,一直没有机会来写一篇博文.而且笔者于十月一日将会举办人生大事--婚礼,现在家里筹办过程中只能抽出零碎的时间来写这篇文章. 关于服务端截图,这种使用场景非常少见,大多 ...

  2. OpenCV自带dnn的Example研究(2)— colorization

    这个博客系列,简单来说,今天我们就是要研究 6个文件,看看在最新的OpenCV中,它们是如何发挥作用的. 在配置使用的过程中,需要注意使用较高版本的VS避免编译器兼容问题:由于DNN程序的运行依赖于训 ...

  3. Java基础学习-Random类和Java数组

    1.随机数类(Random) package com.denniscui;   import java.util.Random; /*  * Random:用于产生随机数  *  * 使用步骤:  * ...

  4. Iview的小经验

    1.动态控制form验证的小红星 HTML部分: <FormItem :class="{requireStar:bankFlag1}"> CSS部分: /*动态必填项c ...

  5. Bootstrap3基础 栅格系统 1行最多12列

      内容 参数   OS   Windows 10 x64   browser   Firefox 65.0.2   framework     Bootstrap 3.3.7   editor    ...

  6. StreamReader 和 StreamWriter 简单调用

    /* ######### ############ ############# ## ########### ### ###### ##### ### ####### #### ### ####### ...

  7. speech

    1.李开复:一个人的成功,15%靠专业知识,其余15%人际沟通,公众演讲,以及影响他人的能力 2.演讲是一门遗憾的艺术 3.没有准备就等于准备失败 4.追求完美,就是在追求完蛋 5.宁可千日无机会,不 ...

  8. OO随笔

    第一次作业——多项式计算 1.自我程序分析 第一次作业是多项式计算,只使用了一个多项式类.第一次接触面向对象的程序,还比较生疏,不是很能理解面向对象的思想.将读入,处理,计算,都放到了main函数中, ...

  9. rematch:当你受不了redux繁琐写法的时候,是时候了解一波rematch了

    前言: 前段时间学习完react后,刚好就接到公司一个react项目的迭代,顺便巩固一下前段时间的学习成果.项目使用的是redux+react-router,将所有的数据都放在redux中,异步处理数 ...

  10. reactiveCocoa使用注意点

    @RACSubject信号 注意点:1如果一个页面需要多次发送这个消息,那么似乎会暴露一个bug,信号不会被销毁,等到发送第二个信号 时,第一个信号仍然会被发送,导致错误,比如一个tableView的 ...