table.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. /**
  2. * table.js - table element for blessed
  3. * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
  4. * https://github.com/chjj/blessed
  5. */
  6. /**
  7. * Modules
  8. */
  9. var Node = require('./node');
  10. var Box = require('./box');
  11. /**
  12. * Table
  13. */
  14. function Table(options) {
  15. var self = this;
  16. if (!(this instanceof Node)) {
  17. return new Table(options);
  18. }
  19. options = options || {};
  20. options.shrink = true;
  21. options.style = options.style || {};
  22. options.style.border = options.style.border || {};
  23. options.style.header = options.style.header || {};
  24. options.style.cell = options.style.cell || {};
  25. options.align = options.align || 'center';
  26. // Regular tables do not get custom height (this would
  27. // require extra padding). Maybe add in the future.
  28. delete options.height;
  29. Box.call(this, options);
  30. this.pad = options.pad != null
  31. ? options.pad
  32. : 2;
  33. this.setData(options.rows || options.data);
  34. this.on('attach', function() {
  35. self.setContent('');
  36. self.setData(self.rows);
  37. });
  38. this.on('resize', function() {
  39. self.setContent('');
  40. self.setData(self.rows);
  41. self.screen.render();
  42. });
  43. }
  44. Table.prototype.__proto__ = Box.prototype;
  45. Table.prototype.type = 'table';
  46. Table.prototype._calculateMaxes = function() {
  47. var self = this;
  48. var maxes = [];
  49. if (this.detached) return;
  50. this.rows = this.rows || [];
  51. this.rows.forEach(function(row) {
  52. row.forEach(function(cell, i) {
  53. var clen = self.strWidth(cell);
  54. if (!maxes[i] || maxes[i] < clen) {
  55. maxes[i] = clen;
  56. }
  57. });
  58. });
  59. var total = maxes.reduce(function(total, max) {
  60. return total + max;
  61. }, 0);
  62. total += maxes.length + 1;
  63. // XXX There might be an issue with resizing where on the first resize event
  64. // width appears to be less than total if it's a percentage or left/right
  65. // combination.
  66. if (this.width < total) {
  67. delete this.position.width;
  68. }
  69. if (this.position.width != null) {
  70. var missing = this.width - total;
  71. var w = missing / maxes.length | 0;
  72. var wr = missing % maxes.length;
  73. maxes = maxes.map(function(max, i) {
  74. if (i === maxes.length - 1) {
  75. return max + w + wr;
  76. }
  77. return max + w;
  78. });
  79. } else {
  80. maxes = maxes.map(function(max) {
  81. return max + self.pad;
  82. });
  83. }
  84. return this._maxes = maxes;
  85. };
  86. Table.prototype.setRows =
  87. Table.prototype.setData = function(rows) {
  88. var self = this
  89. , text = ''
  90. , align = this.align;
  91. this.rows = rows || [];
  92. this._calculateMaxes();
  93. if (!this._maxes) return;
  94. this.rows.forEach(function(row, i) {
  95. var isFooter = i === self.rows.length - 1;
  96. row.forEach(function(cell, i) {
  97. var width = self._maxes[i];
  98. var clen = self.strWidth(cell);
  99. if (i !== 0) {
  100. text += ' ';
  101. }
  102. while (clen < width) {
  103. if (align === 'center') {
  104. cell = ' ' + cell + ' ';
  105. clen += 2;
  106. } else if (align === 'left') {
  107. cell = cell + ' ';
  108. clen += 1;
  109. } else if (align === 'right') {
  110. cell = ' ' + cell;
  111. clen += 1;
  112. }
  113. }
  114. if (clen > width) {
  115. if (align === 'center') {
  116. cell = cell.substring(1);
  117. clen--;
  118. } else if (align === 'left') {
  119. cell = cell.slice(0, -1);
  120. clen--;
  121. } else if (align === 'right') {
  122. cell = cell.substring(1);
  123. clen--;
  124. }
  125. }
  126. text += cell;
  127. });
  128. if (!isFooter) {
  129. text += '\n\n';
  130. }
  131. });
  132. delete this.align;
  133. this.setContent(text);
  134. this.align = align;
  135. };
  136. Table.prototype.render = function() {
  137. var self = this;
  138. var coords = this._render();
  139. if (!coords) return;
  140. this._calculateMaxes();
  141. if (!this._maxes) return coords;
  142. var lines = this.screen.lines
  143. , xi = coords.xi
  144. , yi = coords.yi
  145. , rx
  146. , ry
  147. , i;
  148. var dattr = this.sattr(this.style)
  149. , hattr = this.sattr(this.style.header)
  150. , cattr = this.sattr(this.style.cell)
  151. , battr = this.sattr(this.style.border);
  152. var width = coords.xl - coords.xi - this.iright
  153. , height = coords.yl - coords.yi - this.ibottom;
  154. // Apply attributes to header cells and cells.
  155. for (var y = this.itop; y < height; y++) {
  156. if (!lines[yi + y]) break;
  157. for (var x = this.ileft; x < width; x++) {
  158. if (!lines[yi + y][xi + x]) break;
  159. // Check to see if it's not the default attr. Allows for tags:
  160. if (lines[yi + y][xi + x][0] !== dattr) continue;
  161. if (y === this.itop) {
  162. lines[yi + y][xi + x][0] = hattr;
  163. } else {
  164. lines[yi + y][xi + x][0] = cattr;
  165. }
  166. lines[yi + y].dirty = true;
  167. }
  168. }
  169. if (!this.border || this.options.noCellBorders) return coords;
  170. // Draw border with correct angles.
  171. ry = 0;
  172. for (i = 0; i < self.rows.length + 1; i++) {
  173. if (!lines[yi + ry]) break;
  174. rx = 0;
  175. self._maxes.forEach(function(max, i) {
  176. rx += max;
  177. if (i === 0) {
  178. if (!lines[yi + ry][xi + 0]) return;
  179. // left side
  180. if (ry === 0) {
  181. // top
  182. lines[yi + ry][xi + 0][0] = battr;
  183. // lines[yi + ry][xi + 0][1] = '\u250c'; // '┌'
  184. } else if (ry / 2 === self.rows.length) {
  185. // bottom
  186. lines[yi + ry][xi + 0][0] = battr;
  187. // lines[yi + ry][xi + 0][1] = '\u2514'; // '└'
  188. } else {
  189. // middle
  190. lines[yi + ry][xi + 0][0] = battr;
  191. lines[yi + ry][xi + 0][1] = '\u251c'; // '├'
  192. // XXX If we alter iwidth and ileft for no borders - nothing should be written here
  193. if (!self.border.left) {
  194. lines[yi + ry][xi + 0][1] = '\u2500'; // '─'
  195. }
  196. }
  197. lines[yi + ry].dirty = true;
  198. } else if (i === self._maxes.length - 1) {
  199. if (!lines[yi + ry][xi + rx + 1]) return;
  200. // right side
  201. if (ry === 0) {
  202. // top
  203. rx++;
  204. lines[yi + ry][xi + rx][0] = battr;
  205. // lines[yi + ry][xi + rx][1] = '\u2510'; // '┐'
  206. } else if (ry / 2 === self.rows.length) {
  207. // bottom
  208. rx++;
  209. lines[yi + ry][xi + rx][0] = battr;
  210. // lines[yi + ry][xi + rx][1] = '\u2518'; // '┘'
  211. } else {
  212. // middle
  213. rx++;
  214. lines[yi + ry][xi + rx][0] = battr;
  215. lines[yi + ry][xi + rx][1] = '\u2524'; // '┤'
  216. // XXX If we alter iwidth and iright for no borders - nothing should be written here
  217. if (!self.border.right) {
  218. lines[yi + ry][xi + rx][1] = '\u2500'; // '─'
  219. }
  220. }
  221. lines[yi + ry].dirty = true;
  222. return;
  223. }
  224. if (!lines[yi + ry][xi + rx + 1]) return;
  225. // center
  226. if (ry === 0) {
  227. // top
  228. rx++;
  229. lines[yi + ry][xi + rx][0] = battr;
  230. lines[yi + ry][xi + rx][1] = '\u252c'; // '┬'
  231. // XXX If we alter iheight and itop for no borders - nothing should be written here
  232. if (!self.border.top) {
  233. lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
  234. }
  235. } else if (ry / 2 === self.rows.length) {
  236. // bottom
  237. rx++;
  238. lines[yi + ry][xi + rx][0] = battr;
  239. lines[yi + ry][xi + rx][1] = '\u2534'; // '┴'
  240. // XXX If we alter iheight and ibottom for no borders - nothing should be written here
  241. if (!self.border.bottom) {
  242. lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
  243. }
  244. } else {
  245. // middle
  246. if (self.options.fillCellBorders) {
  247. var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff;
  248. rx++;
  249. lines[yi + ry][xi + rx][0] = (battr & ~0x1ff) | lbg;
  250. } else {
  251. rx++;
  252. lines[yi + ry][xi + rx][0] = battr;
  253. }
  254. lines[yi + ry][xi + rx][1] = '\u253c'; // '┼'
  255. // rx++;
  256. }
  257. lines[yi + ry].dirty = true;
  258. });
  259. ry += 2;
  260. }
  261. // Draw internal borders.
  262. for (ry = 1; ry < self.rows.length * 2; ry++) {
  263. if (!lines[yi + ry]) break;
  264. rx = 0;
  265. self._maxes.slice(0, -1).forEach(function(max) {
  266. rx += max;
  267. if (!lines[yi + ry][xi + rx + 1]) return;
  268. if (ry % 2 !== 0) {
  269. if (self.options.fillCellBorders) {
  270. var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff;
  271. rx++;
  272. lines[yi + ry][xi + rx][0] = (battr & ~0x1ff) | lbg;
  273. } else {
  274. rx++;
  275. lines[yi + ry][xi + rx][0] = battr;
  276. }
  277. lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
  278. lines[yi + ry].dirty = true;
  279. } else {
  280. rx++;
  281. }
  282. });
  283. rx = 1;
  284. self._maxes.forEach(function(max) {
  285. while (max--) {
  286. if (ry % 2 === 0) {
  287. if (!lines[yi + ry]) break;
  288. if (!lines[yi + ry][xi + rx + 1]) break;
  289. if (self.options.fillCellBorders) {
  290. var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff;
  291. lines[yi + ry][xi + rx][0] = (battr & ~0x1ff) | lbg;
  292. } else {
  293. lines[yi + ry][xi + rx][0] = battr;
  294. }
  295. lines[yi + ry][xi + rx][1] = '\u2500'; // '─'
  296. lines[yi + ry].dirty = true;
  297. }
  298. rx++;
  299. }
  300. rx++;
  301. });
  302. }
  303. return coords;
  304. };
  305. /**
  306. * Expose
  307. */
  308. module.exports = Table;