freeze-table.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /**
  2. * RWD Table with freezing head and columns for jQuery
  3. *
  4. * @author Nick Tsai <myintaer@gmail.com>
  5. * @version 1.1.2
  6. * @see https://github.com/yidas/jquery-freeze-table
  7. */
  8. (function ($, window) {
  9. 'use strict';
  10. /**
  11. * Main object
  12. *
  13. * @param {element} element
  14. * @param {object} options
  15. */
  16. var FreezeTable = function(element, options) {
  17. // Target element initialization
  18. this.$tableWrapper = $(element).first();
  19. // Options
  20. this.options = options || {};
  21. this.namespace = this.options.namespace || 'freeze-table';
  22. this.callback;
  23. this.shadow;
  24. this.fastMode;
  25. this.backgroundColor;
  26. // Caches
  27. this.$table = this.$tableWrapper.children("table");
  28. this.$headTableWrap;
  29. this.$columnTableWrap;
  30. this.fixedNavbarHeight;
  31. // Static class names for clone wraps
  32. this.headWrapClass = 'clone-head-table-wrap';
  33. this.columnWrapClass = 'clone-column-table-wrap';
  34. this.columnHeadWrapClass = 'clone-column-head-table-wrap';
  35. this.scrollBarWrapClass = 'clone-scroll-bar-wrap';
  36. this.init();
  37. return this;
  38. }
  39. /**
  40. * Initialization
  41. */
  42. FreezeTable.prototype.init = function() {
  43. // Element check
  44. if (!this.$table.length) {
  45. throw "The element must contain a table dom";
  46. }
  47. /**
  48. * Update Mode
  49. */
  50. if (this.options==='update') {
  51. this.destroy();
  52. this.options = this.$tableWrapper.data('freeze-table-data');
  53. }
  54. else if (this.options==='resize') {
  55. this.options = this.$tableWrapper.data('freeze-table-data');
  56. // Get selected FreezeTable's namespace
  57. this.namespace = this.options.namespace || this.namespace;
  58. this.resize();
  59. // Skip init for better performance usage
  60. return;
  61. }
  62. else {
  63. // Save to DOM data
  64. this.$tableWrapper.data('freeze-table-data', this.options);
  65. }
  66. /**
  67. * Options Setting
  68. */
  69. var options = this.options;
  70. var freezeHead = (typeof options.freezeHead !== 'undefined') ? options.freezeHead : true;
  71. var freezeColumn = (typeof options.freezeColumn !== 'undefined') ? options.freezeColumn : true;
  72. var freezeColumnHead = (typeof options.freezeColumnHead !== 'undefined') ? options.freezeColumnHead : true;
  73. var scrollBar = (typeof options.scrollBar !== 'undefined') ? options.scrollBar : false;
  74. var fixedNavbar = options.fixedNavbar || '.navbar-fixed-top';
  75. var callback = options.callback || null;
  76. this.namespace = this.options.namespace || this.namespace;
  77. this.shadow = (typeof options.shadow !== 'undefined') ? options.shadow : false;
  78. this.fastMode = (typeof options.fastMode !== 'undefined') ? options.fastMode : false;
  79. this.backgroundColor = options.backgroundColor || 'white';
  80. // Get navbar height for keeping fixed navbar
  81. this.fixedNavbarHeight = (fixedNavbar) ? $(fixedNavbar).outerHeight() || 0 : 0;
  82. // Check existence
  83. if (this.isInit()) {
  84. this.destroy();
  85. }
  86. // Release height of the table wrapper
  87. this.$tableWrapper.css('height', '100%')
  88. .css('min-height', '100%')
  89. .css('max-height', '100%');
  90. /**
  91. * Building
  92. */
  93. // Switch for freezeHead
  94. if (freezeHead) {
  95. this.buildHeadTable();
  96. }
  97. // Switch for freezeColumn
  98. if (freezeColumn) {
  99. this.buildColumnTable();
  100. // X scroll bar
  101. this.$tableWrapper.css('overflow-x', 'scroll');
  102. }
  103. // Switch for freezeColumnHead
  104. if (freezeColumnHead && freezeHead && freezeColumn) {
  105. this.buildColumnHeadTable();
  106. }
  107. // Switch for scrollBar
  108. if (scrollBar) {
  109. this.buildScrollBar();
  110. }
  111. // Initialization
  112. this.resize();
  113. // Callback
  114. if (typeof callback === 'function') {
  115. callback();
  116. }
  117. }
  118. /**
  119. * Freeze thead table
  120. */
  121. FreezeTable.prototype.buildHeadTable = function() {
  122. var that = this;
  123. // Clone the table as Fixed thead
  124. var $headTable = this.clone(this.$table);
  125. // Fast Mode
  126. if (this.fastMode) {
  127. var $headTable = this.simplifyHead($headTable);
  128. }
  129. var headWrapStyles = this.options.headWrapStyles || null;
  130. // Wrap the Fixed Column table
  131. this.$headTableWrap = $('<div class="'+this.headWrapClass+'"></div>')
  132. .append($headTable)
  133. .css('position', 'fixed')
  134. .css('overflow', 'hidden')
  135. .css('visibility', 'hidden')
  136. .css('top', 0 + this.fixedNavbarHeight)
  137. .css('z-index', 2);
  138. // Shadow option
  139. if (this.shadow) {
  140. this.$headTableWrap.css('box-shadow', '0px 6px 10px -5px rgba(159, 159, 160, 0.8)');
  141. }
  142. // Styles option
  143. if (headWrapStyles && typeof headWrapStyles === "object") {
  144. $.each(headWrapStyles, function(key, value) {
  145. that.$headTableWrap.css(key, value);
  146. });
  147. }
  148. // Add into target table wrap
  149. this.$tableWrapper.append(this.$headTableWrap);
  150. /**
  151. * Listener - Table scroll for effecting Freeze Column
  152. */
  153. this.$tableWrapper.on('scroll.'+this.namespace, function() {
  154. // this.$headTableWrap.css('left', this.$table.offset().left);
  155. that.$headTableWrap.scrollLeft($(this).scrollLeft());
  156. });
  157. /**
  158. * Listener - Window scroll for effecting freeze head table
  159. */
  160. $(window).on('scroll.'+this.namespace, function() {
  161. // Current container's top position
  162. var topPosition = $(window).scrollTop() + that.fixedNavbarHeight;
  163. // Detect Current container's top is in the table scope
  164. if (that.$table.offset().top - 1 <= topPosition && (that.$table.offset().top + that.$table.outerHeight() - 1) >= topPosition) {
  165. that.$headTableWrap.css('visibility', 'visible');
  166. } else {
  167. that.$headTableWrap.css('visibility', 'hidden');
  168. }
  169. });
  170. /**
  171. * Listener - Window resize for effecting freeze head table
  172. */
  173. $(window).on('resize.'+this.namespace, function() {
  174. that.$headTableWrap.css('width', that.$tableWrapper.width());
  175. that.$headTableWrap.css('height', that.$table.find("thead").outerHeight());
  176. });
  177. }
  178. /**
  179. * Freeze column table
  180. */
  181. FreezeTable.prototype.buildColumnTable = function() {
  182. var that = this;
  183. /**
  184. * Setting
  185. */
  186. var columnWrapStyles = this.options.columnWrapStyles || null;
  187. var columnNum = this.options.columnNum || 1;
  188. // Shadow option
  189. var defaultColumnBorderWidth = (this.shadow) ? 0 : 1;
  190. var columnBorderWidth = (typeof this.options.columnBorderWidth !== 'undefined') ? this.options.columnBorderWidth : defaultColumnBorderWidth;
  191. // Clone the table as Fixed Column table
  192. var $columnTable = this.clone(this.$table);
  193. // Wrap the Fixed Column table
  194. this.$columnTableWrap = $('<div class="'+this.columnWrapClass+'"></div>')
  195. .append($columnTable)
  196. .css('position', 'fixed')
  197. .css('overflow', 'hidden')
  198. .css('visibility', 'hidden')
  199. .css('z-index', 1);
  200. // Shadow option
  201. if (this.shadow) {
  202. this.$columnTableWrap.css('box-shadow', '6px 0px 10px -5px rgba(159, 159, 160, 0.8)');
  203. }
  204. // Styles option
  205. if (columnWrapStyles && typeof columnWrapStyles === "object") {
  206. $.each(columnWrapStyles, function(key, value) {
  207. that.$columnTableWrap.css(key, value);
  208. });
  209. }
  210. // Add into target table wrap
  211. this.$tableWrapper.append(this.$columnTableWrap);
  212. /**
  213. * Align the column wrap to current top
  214. */
  215. var align = function () {
  216. that.$columnTableWrap.css('top', that.$table.offset().top - $(window).scrollTop());
  217. }
  218. /**
  219. * Listener - Table scroll for effecting Freeze Column
  220. */
  221. this.$tableWrapper.on('scroll.'+this.namespace, function() {
  222. // Detect for horizontal scroll
  223. if ($(this).scrollLeft() > 0) {
  224. that.$columnTableWrap.css('visibility', 'visible');
  225. } else {
  226. that.$columnTableWrap.css('visibility', 'hidden');
  227. }
  228. });
  229. /**
  230. * Listener - Window resize for effecting tables
  231. */
  232. $(window).on('resize.'+this.namespace, function() {
  233. // Follows origin table's width
  234. $columnTable.width(that.$table.width());
  235. /**
  236. * Dynamic column calculation
  237. */
  238. // Get width by fixed column with number setting
  239. var width = 0 + columnBorderWidth;
  240. for (var i = 1; i <= columnNum; i++) {
  241. // th/td detection
  242. var th = that.$table.find('th:nth-child('+i+')').outerWidth();
  243. var addWidth = (th > 0) ? th : that.$table.find('td:nth-child('+i+')').outerWidth();
  244. width += addWidth;
  245. }
  246. that.$columnTableWrap.width(width);
  247. align();
  248. });
  249. /**
  250. * Listener - Window scroll for effecting freeze column table
  251. */
  252. $(window).on('scroll.'+this.namespace, function() {
  253. align();
  254. });
  255. }
  256. /**
  257. * Freeze column thead table
  258. */
  259. FreezeTable.prototype.buildColumnHeadTable = function() {
  260. var that = this;
  261. // Clone head table wrap
  262. var $columnHeadTableWrap = this.clone(this.$headTableWrap);
  263. // Fast Mode
  264. if (this.fastMode) {
  265. var $columnHeadTableWrap = this.simplifyHead($columnHeadTableWrap);
  266. }
  267. var columnHeadWrapStyles = this.options.columnHeadWrapStyles || null;
  268. $columnHeadTableWrap.removeClass(this.namespace)
  269. .addClass(this.columnHeadWrapClass)
  270. .css('z-index', 3);
  271. // Shadow option
  272. if (this.shadow) {
  273. $columnHeadTableWrap.css('box-shadow', 'none');
  274. }
  275. // Styles option
  276. if (columnHeadWrapStyles && typeof columnHeadWrapStyles === "object") {
  277. $.each(columnHeadWrapStyles, function(key, value) {
  278. $columnHeadTableWrap.css(key, value);
  279. });
  280. }
  281. // Add into target table wrap
  282. this.$tableWrapper.append($columnHeadTableWrap);
  283. /**
  284. * Detect column-head wrap to show or not
  285. */
  286. var detect = function () {
  287. // Current container's top position
  288. var topPosition = $(window).scrollTop() + that.fixedNavbarHeight;
  289. // Detect Current container's top is in the table scope
  290. // Plus tableWrapper scroll detection
  291. if (that.$table.offset().top - 1 <= topPosition && (that.$table.offset().top + that.$table.outerHeight() - 1) >= topPosition && that.$tableWrapper.scrollLeft() > 0) {
  292. $columnHeadTableWrap.css('visibility', 'visible');
  293. } else {
  294. $columnHeadTableWrap.css('visibility', 'hidden');
  295. }
  296. }
  297. /**
  298. * Listener - Window scroll for effecting Freeze column-head table
  299. */
  300. $(window).on('scroll.'+this.namespace, function() {
  301. detect();
  302. });
  303. /**
  304. * Listener - Table scroll for effecting Freeze column-head table
  305. */
  306. this.$tableWrapper.on('scroll.'+this.namespace, function() {
  307. detect();
  308. });
  309. /**
  310. * Listener - Window resize for effecting freeze column-head table
  311. */
  312. $(window).on('resize.'+this.namespace, function() {
  313. // Table synchronism
  314. $columnHeadTableWrap.find("> table").css('width', that.$table.width());
  315. $columnHeadTableWrap.css('width', that.$columnTableWrap.width());
  316. $columnHeadTableWrap.css('height', that.$table.find("thead").outerHeight());
  317. });
  318. }
  319. /**
  320. * Freeze scroll bar
  321. */
  322. FreezeTable.prototype.buildScrollBar = function() {
  323. var that = this;
  324. var theadHeight = this.$table.find("thead").outerHeight();
  325. // Scroll wrap container
  326. var $scrollBarContainer = $('<div class="'+this.scrollBarWrapClass+'"></div>')
  327. .css('width', this.$table.width())
  328. .css('height', 1);
  329. // Wrap the Fixed Column table
  330. var $scrollBarWrap = $('<div class="'+this.scrollBarWrapClass+'"></div>')
  331. .css('position', 'fixed')
  332. .css('overflow-x', 'scroll')
  333. .css('visibility', 'hidden')
  334. .css('bottom', 0)
  335. .css('z-index', 2)
  336. .css('width', this.$tableWrapper.width())
  337. .css('height', 20);
  338. // Add into target table wrap
  339. $scrollBarWrap.append($scrollBarContainer);
  340. this.$tableWrapper.append($scrollBarWrap);
  341. /**
  342. * Listener - Freeze scroll bar effected Table
  343. */
  344. $scrollBarWrap.on('scroll.'+this.namespace, function() {
  345. that.$tableWrapper.scrollLeft($(this).scrollLeft());
  346. });
  347. /**
  348. * Listener - Table scroll for effecting Freeze scroll bar
  349. */
  350. this.$tableWrapper.on('scroll.'+this.namespace, function() {
  351. // this.$headTableWrap.css('left', $table.offset().left);
  352. $scrollBarWrap.scrollLeft($(this).scrollLeft());
  353. });
  354. /**
  355. * Listener - Window scroll for effecting scroll bar
  356. */
  357. $(window).on('scroll.'+this.namespace, function() {
  358. // Current container's top position
  359. var bottomPosition = $(window).scrollTop() + $(window).height() - theadHeight + that.fixedNavbarHeight;
  360. // Detect Current container's top is in the table scope
  361. if (that.$table.offset().top - 1 <= bottomPosition && (that.$table.offset().top + that.$table.outerHeight() - 1) >= bottomPosition) {
  362. $scrollBarWrap.css('visibility', 'visible');
  363. } else {
  364. $scrollBarWrap.css('visibility', 'hidden');
  365. }
  366. });
  367. /**
  368. * Listener - Window resize for effecting scroll bar
  369. */
  370. $(window).on('resize.'+this.namespace, function() {
  371. // Update width
  372. $scrollBarContainer.css('width', that.$table.width())
  373. // Update Wrap
  374. $scrollBarWrap.css('width', that.$tableWrapper.width());
  375. });
  376. }
  377. /**
  378. * Clone element
  379. *
  380. * @param {element} element
  381. */
  382. FreezeTable.prototype.clone = function (element) {
  383. return $(element).clone()
  384. .removeAttr('id') // Remove ID
  385. .css('background-color', this.backgroundColor); // Bootstrap background-color transparent problem
  386. }
  387. /**
  388. * simplify cloned head table
  389. *
  390. * @param {element} table Table element
  391. */
  392. FreezeTable.prototype.simplifyHead = function (table) {
  393. var that = this;
  394. var $headTable = $(table);
  395. // Remove non-display DOM
  396. $headTable.find("> tr, > tbody, > tfoot").remove();
  397. // Each th/td width synchronism
  398. $.each($headTable.find("> thead > tr:nth-child(1) >"), function (key, value) {
  399. var width = that.$table.find("> thead > tr:nth-child(1) > :nth-child("+parseInt(key+1)+")").outerWidth();
  400. $(this).css('width', width);
  401. });
  402. return $headTable;
  403. }
  404. /**
  405. * Detect is already initialized
  406. */
  407. FreezeTable.prototype.isInit = function() {
  408. // Check existence DOM
  409. if (this.$tableWrapper.find("."+this.headWrapClass).length)
  410. return true;
  411. if (this.$tableWrapper.find("."+this.columnWrapClass).length)
  412. return true;
  413. if (this.$tableWrapper.find("."+this.columnHeadWrapClass).length)
  414. return true;
  415. if (this.$tableWrapper.find("."+this.scrollBarWrapClass).length)
  416. return true;
  417. return false;
  418. }
  419. /**
  420. * Unbind all events by same namespace
  421. */
  422. FreezeTable.prototype.unbind = function() {
  423. $(window).off('resize.'+this.namespace);
  424. $(window).off('scroll.'+this.namespace);
  425. this.$tableWrapper.off('scroll.'+this.namespace);
  426. }
  427. /**
  428. * Destroy Freeze Table by same namespace
  429. */
  430. FreezeTable.prototype.destroy = function() {
  431. this.unbind();
  432. this.$tableWrapper.find("."+this.headWrapClass).remove();
  433. this.$tableWrapper.find("."+this.columnWrapClass).remove();
  434. this.$tableWrapper.find("."+this.columnHeadWrapClass).remove();
  435. this.$tableWrapper.find("."+this.scrollBarWrapClass).remove();
  436. }
  437. /**
  438. * Resize trigger for current same namespace
  439. */
  440. FreezeTable.prototype.resize = function() {
  441. $(window).trigger('resize.'+this.namespace);
  442. $(window).trigger('scroll.'+this.namespace);
  443. this.$tableWrapper.trigger('scroll.'+this.namespace);
  444. return true;
  445. }
  446. /**
  447. * Update for Dynamic Content
  448. */
  449. FreezeTable.prototype.update = function() {
  450. // Same as re-new object
  451. this.options = 'update';
  452. this.init();
  453. return this;
  454. }
  455. /**
  456. * Interface
  457. */
  458. // Class for single element
  459. window.FreezeTable = FreezeTable;
  460. // jQuery interface
  461. $.fn.freezeTable = function (options) {
  462. // Single/Multiple mode
  463. if (this.length === 1) {
  464. return new FreezeTable(this, options)
  465. }
  466. else if (this.length > 1) {
  467. var result = [];
  468. // Multiple elements bundle
  469. this.each(function () {
  470. result.push(new FreezeTable(this, options));
  471. });
  472. return result;
  473. }
  474. return false;
  475. }
  476. })(jQuery, window);