jquery.fixedheadertable.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. /*!
  2. * jquery.fixedHeaderTable. The jQuery fixedHeaderTable plugin
  3. *
  4. * Copyright (c) 2013 Mark Malek
  5. * http://fixedheadertable.com
  6. *
  7. * Licensed under MIT
  8. * http://www.opensource.org/licenses/mit-license.php
  9. *
  10. * http://docs.jquery.com/Plugins/Authoring
  11. * jQuery authoring guidelines
  12. *
  13. * Launch : October 2009
  14. * Version : 1.3
  15. * Released: May 9th, 2011
  16. *
  17. *
  18. * all CSS sizing (width,height) is done in pixels (px)
  19. */
  20. (function ($) {
  21. $.fn.fixedHeaderTable = function (method) {
  22. // plugin's default options
  23. var defaults = {
  24. width: '100%',
  25. height: '100%',
  26. themeClass: 'fht-default',
  27. borderCollapse: true,
  28. fixedColumns: 0, // fixed first columns
  29. fixedColumn: false, // For backward-compatibility
  30. sortable: false,
  31. autoShow: true, // hide table after its created
  32. footer: false, // show footer
  33. cloneHeadToFoot: false, // clone head and use as footer
  34. autoResize: false, // resize table if its parent wrapper changes size
  35. create: null // callback after plugin completes
  36. };
  37. var settings = {};
  38. // public methods
  39. var methods = {
  40. init: function (options) {
  41. settings = $.extend({}, defaults, options);
  42. // iterate through all the DOM elements we are attaching the plugin to
  43. return this.each(function () {
  44. var $self = $(this); // reference the jQuery version of the current DOM element
  45. if (helpers._isTable($self)) {
  46. methods.setup.apply(this, Array.prototype.slice.call(arguments, 1));
  47. $.isFunction(settings.create) && settings.create.call(this);
  48. } else {
  49. $.error('Invalid table mark-up');
  50. }
  51. });
  52. },
  53. /*
  54. * Setup table structure for fixed headers and optional footer
  55. */
  56. setup: function () {
  57. var $self = $(this),
  58. self = this,
  59. $thead = $self.find('thead'),
  60. $tfoot = $self.find('tfoot'),
  61. tfootHeight = 0,
  62. $wrapper,
  63. $divHead,
  64. $divBody,
  65. $fixedBody,
  66. widthMinusScrollbar;
  67. settings.originalTable = $(this).clone();
  68. settings.includePadding = helpers._isPaddingIncludedWithWidth();
  69. settings.scrollbarOffset = helpers._getScrollbarWidth();
  70. settings.themeClassName = settings.themeClass;
  71. if (settings.width.search('%') > -1) {
  72. widthMinusScrollbar = $self.parent().width() - settings.scrollbarOffset;
  73. } else {
  74. widthMinusScrollbar = settings.width - settings.scrollbarOffset;
  75. }
  76. $self.css({
  77. width: widthMinusScrollbar
  78. });
  79. if (!$self.closest('.fht-table-wrapper').length) {
  80. $self.addClass('fht-table');
  81. $self.wrap('<div class="fht-table-wrapper"></div>');
  82. }
  83. $wrapper = $self.closest('.fht-table-wrapper');
  84. if(settings.fixedColumn == true && settings.fixedColumns <= 0) {
  85. settings.fixedColumns = 1;
  86. }
  87. if (settings.fixedColumns > 0 && $wrapper.find('.fht-fixed-column').length == 0) {
  88. $self.wrap('<div class="fht-fixed-body"></div>');
  89. $('<div class="fht-fixed-column"></div>').prependTo($wrapper);
  90. $fixedBody = $wrapper.find('.fht-fixed-body');
  91. }
  92. $wrapper.css({
  93. width: settings.width,
  94. height: settings.height
  95. })
  96. .addClass(settings.themeClassName);
  97. if (!$self.hasClass('fht-table-init')) {
  98. $self.wrap('<div class="fht-tbody"></div>');
  99. }
  100. $divBody = $self.closest('.fht-tbody');
  101. var tableProps = helpers._getTableProps($self);
  102. helpers._setupClone($divBody, tableProps.tbody);
  103. if (!$self.hasClass('fht-table-init')) {
  104. if (settings.fixedColumns > 0) {
  105. $divHead = $('<div class="fht-thead"><table class="fht-table"></table></div>').prependTo($fixedBody);
  106. } else {
  107. $divHead = $('<div class="fht-thead"><table class="fht-table"></table></div>').prependTo($wrapper);
  108. }
  109. $divHead.find('table.fht-table')
  110. .addClass(settings.originalTable.attr('class'))
  111. .attr('style', settings.originalTable.attr('style'));
  112. $thead.clone().appendTo($divHead.find('table'));
  113. } else {
  114. $divHead = $wrapper.find('div.fht-thead');
  115. }
  116. helpers._setupClone($divHead, tableProps.thead);
  117. $self.css({
  118. 'margin-top': -$divHead.outerHeight(true)
  119. });
  120. /*
  121. * Check for footer
  122. * Setup footer if present
  123. */
  124. if (settings.footer == true) {
  125. helpers._setupTableFooter($self, self, tableProps);
  126. if (!$tfoot.length) {
  127. $tfoot = $wrapper.find('div.fht-tfoot table');
  128. }
  129. tfootHeight = $tfoot.outerHeight(true);
  130. }
  131. var tbodyHeight = $wrapper.height() - $thead.outerHeight(true) - tfootHeight - tableProps.border;
  132. $divBody.css({
  133. 'height': tbodyHeight
  134. });
  135. $self.addClass('fht-table-init');
  136. if (typeof(settings.altClass) !== 'undefined') {
  137. methods.altRows.apply(self);
  138. }
  139. if (settings.fixedColumns > 0) {
  140. helpers._setupFixedColumn($self, self, tableProps);
  141. }
  142. if (!settings.autoShow) {
  143. $wrapper.hide();
  144. }
  145. helpers._bindScroll($divBody, tableProps);
  146. return self;
  147. },
  148. /*
  149. * Resize the table
  150. * Incomplete - not implemented yet
  151. */
  152. resize: function() {
  153. var self = this;
  154. return self;
  155. },
  156. /*
  157. * Add CSS class to alternating rows
  158. */
  159. altRows: function(arg1) {
  160. var $self = $(this),
  161. altClass = (typeof(arg1) !== 'undefined') ? arg1 : settings.altClass;
  162. $self.closest('.fht-table-wrapper')
  163. .find('tbody tr:odd:not(:hidden)')
  164. .addClass(altClass);
  165. },
  166. /*
  167. * Show a hidden fixedHeaderTable table
  168. */
  169. show: function(arg1, arg2, arg3) {
  170. var $self = $(this),
  171. self = this,
  172. $wrapper = $self.closest('.fht-table-wrapper');
  173. // User provided show duration without a specific effect
  174. if (typeof(arg1) !== 'undefined' && typeof(arg1) === 'number') {
  175. $wrapper.show(arg1, function() {
  176. $.isFunction(arg2) && arg2.call(this);
  177. });
  178. return self;
  179. } else if (typeof(arg1) !== 'undefined' && typeof(arg1) === 'string' &&
  180. typeof(arg2) !== 'undefined' && typeof(arg2) === 'number') {
  181. // User provided show duration with an effect
  182. $wrapper.show(arg1, arg2, function() {
  183. $.isFunction(arg3) && arg3.call(this);
  184. });
  185. return self;
  186. }
  187. $self.closest('.fht-table-wrapper')
  188. .show();
  189. $.isFunction(arg1) && arg1.call(this);
  190. return self;
  191. },
  192. /*
  193. * Hide a fixedHeaderTable table
  194. */
  195. hide: function(arg1, arg2, arg3) {
  196. var $self = $(this),
  197. self = this,
  198. $wrapper = $self.closest('.fht-table-wrapper');
  199. // User provided show duration without a specific effect
  200. if (typeof(arg1) !== 'undefined' && typeof(arg1) === 'number') {
  201. $wrapper.hide(arg1, function() {
  202. $.isFunction(arg3) && arg3.call(this);
  203. });
  204. return self;
  205. } else if (typeof(arg1) !== 'undefined' && typeof(arg1) === 'string' &&
  206. typeof(arg2) !== 'undefined' && typeof(arg2) === 'number') {
  207. $wrapper.hide(arg1, arg2, function() {
  208. $.isFunction(arg3) && arg3.call(this);
  209. });
  210. return self;
  211. }
  212. $self.closest('.fht-table-wrapper')
  213. .hide();
  214. $.isFunction(arg3) && arg3.call(this);
  215. return self;
  216. },
  217. /*
  218. * Destory fixedHeaderTable and return table to original state
  219. */
  220. destroy: function() {
  221. var $self = $(this),
  222. self = this,
  223. $wrapper = $self.closest('.fht-table-wrapper');
  224. $self.insertBefore($wrapper)
  225. .removeAttr('style')
  226. .append($wrapper.find('tfoot'))
  227. .removeClass('fht-table fht-table-init')
  228. .find('.fht-cell')
  229. .remove();
  230. $wrapper.remove();
  231. return self;
  232. }
  233. };
  234. // private methods
  235. var helpers = {
  236. /*
  237. * return boolean
  238. * True if a thead and tbody exist.
  239. */
  240. _isTable: function($obj) {
  241. var $self = $obj,
  242. hasTable = $self.is('table'),
  243. hasThead = $self.find('thead').length > 0,
  244. hasTbody = $self.find('tbody').length > 0;
  245. if (hasTable && hasThead && hasTbody) {
  246. return true;
  247. }
  248. return false;
  249. },
  250. /*
  251. * return void
  252. * bind scroll event
  253. */
  254. _bindScroll: function($obj) {
  255. var $self = $obj,
  256. $wrapper = $self.closest('.fht-table-wrapper'),
  257. $thead = $self.siblings('.fht-thead'),
  258. $tfoot = $self.siblings('.fht-tfoot');
  259. $self.bind('scroll', function() {
  260. if (settings.fixedColumns > 0) {
  261. var $fixedColumns = $wrapper.find('.fht-fixed-column');
  262. $fixedColumns.find('.fht-tbody table')
  263. .css({
  264. 'margin-top': -$self.scrollTop()
  265. });
  266. }
  267. $thead.find('table')
  268. .css({
  269. 'margin-left': -this.scrollLeft
  270. });
  271. if (settings.footer || settings.cloneHeadToFoot) {
  272. $tfoot.find('table')
  273. .css({
  274. 'margin-left': -this.scrollLeft
  275. });
  276. }
  277. });
  278. },
  279. /*
  280. * return void
  281. */
  282. _fixHeightWithCss: function ($obj, tableProps) {
  283. if (settings.includePadding) {
  284. $obj.css({
  285. 'height': $obj.height() + tableProps.border
  286. });
  287. } else {
  288. $obj.css({
  289. 'height': $obj.parent().height() + tableProps.border
  290. });
  291. }
  292. },
  293. /*
  294. * return void
  295. */
  296. _fixWidthWithCss: function($obj, tableProps, width) {
  297. if (settings.includePadding) {
  298. $obj.each(function() {
  299. $(this).css({
  300. 'width': width == undefined ? $(this).width() + tableProps.border : width + tableProps.border
  301. });
  302. });
  303. } else {
  304. $obj.each(function() {
  305. $(this).css({
  306. 'width': width == undefined ? $(this).parent().width() + tableProps.border : width + tableProps.border
  307. });
  308. });
  309. }
  310. },
  311. /*
  312. * return void
  313. */
  314. _setupFixedColumn: function ($obj, obj, tableProps) {
  315. var $self = $obj,
  316. $wrapper = $self.closest('.fht-table-wrapper'),
  317. $fixedBody = $wrapper.find('.fht-fixed-body'),
  318. $fixedColumn = $wrapper.find('.fht-fixed-column'),
  319. $thead = $('<div class="fht-thead"><table class="fht-table"><thead><tr></tr></thead></table></div>'),
  320. $tbody = $('<div class="fht-tbody"><table class="fht-table"><tbody></tbody></table></div>'),
  321. $tfoot = $('<div class="fht-tfoot"><table class="fht-table"><tfoot><tr></tr></tfoot></table></div>'),
  322. fixedBodyWidth = $wrapper.width(),
  323. fixedBodyHeight = $fixedBody.find('.fht-tbody').height() - settings.scrollbarOffset,
  324. $firstThChildren,
  325. $firstTdChildren,
  326. fixedColumnWidth,
  327. $newRow,
  328. firstTdChildrenSelector;
  329. $thead.find('table.fht-table').addClass(settings.originalTable.attr('class'));
  330. $tbody.find('table.fht-table').addClass(settings.originalTable.attr('class'));
  331. $tfoot.find('table.fht-table').addClass(settings.originalTable.attr('class'));
  332. $firstThChildren = $fixedBody.find('.fht-thead thead tr > *:lt(' + settings.fixedColumns + ')');
  333. fixedColumnWidth = settings.fixedColumns * tableProps.border;
  334. $firstThChildren.each(function() {
  335. fixedColumnWidth += $(this).outerWidth(true);
  336. });
  337. // Fix cell heights
  338. helpers._fixHeightWithCss($firstThChildren, tableProps);
  339. helpers._fixWidthWithCss($firstThChildren, tableProps);
  340. var tdWidths = [];
  341. $firstThChildren.each(function() {
  342. tdWidths.push($(this).width());
  343. });
  344. firstTdChildrenSelector = 'tbody tr > *:not(:nth-child(n+' + (settings.fixedColumns + 1) + '))';
  345. $firstTdChildren = $fixedBody.find(firstTdChildrenSelector)
  346. .each(function(index) {
  347. helpers._fixHeightWithCss($(this), tableProps);
  348. helpers._fixWidthWithCss($(this), tableProps, tdWidths[index % settings.fixedColumns] );
  349. });
  350. // clone header
  351. $thead.appendTo($fixedColumn)
  352. .find('tr')
  353. .append($firstThChildren.clone());
  354. $tbody.appendTo($fixedColumn)
  355. .css({
  356. 'margin-top': -1,
  357. 'height': fixedBodyHeight + tableProps.border
  358. });
  359. $firstTdChildren.each(function(index) {
  360. if (index % settings.fixedColumns == 0) {
  361. $newRow = $('<tr></tr>').appendTo($tbody.find('tbody'));
  362. if (settings.altClass && $(this).parent().hasClass(settings.altClass)) {
  363. $newRow.addClass(settings.altClass);
  364. }
  365. }
  366. $(this).clone()
  367. .appendTo($newRow);
  368. });
  369. // set width of fixed column wrapper
  370. $fixedColumn.css({
  371. 'height': 0,
  372. 'width': fixedColumnWidth
  373. });
  374. // bind mousewheel events
  375. var maxTop = $fixedColumn.find('.fht-tbody .fht-table').height() - $fixedColumn.find('.fht-tbody').height();
  376. $fixedColumn.find('.fht-tbody .fht-table').bind('mousewheel', function(event, delta, deltaX, deltaY) {
  377. if (deltaY == 0) {
  378. return;
  379. }
  380. var top = parseInt($(this).css('marginTop'), 10) + (deltaY > 0 ? 120 : -120);
  381. if (top > 0) {
  382. top = 0;
  383. }
  384. if (top < -maxTop) {
  385. top = -maxTop;
  386. }
  387. $(this).css('marginTop', top);
  388. $fixedBody.find('.fht-tbody').scrollTop(-top).scroll();
  389. return false;
  390. });
  391. // set width of body table wrapper
  392. $fixedBody.css({
  393. 'width': fixedBodyWidth
  394. });
  395. // setup clone footer with fixed column
  396. if (settings.footer == true || settings.cloneHeadToFoot == true) {
  397. var $firstTdFootChild = $fixedBody.find('.fht-tfoot tr > *:lt(' + settings.fixedColumns + ')'),
  398. footwidth;
  399. helpers._fixHeightWithCss($firstTdFootChild, tableProps);
  400. $tfoot.appendTo($fixedColumn)
  401. .find('tr')
  402. .append($firstTdFootChild.clone());
  403. // Set (view width) of $tfoot div to width of table (this accounts for footers with a colspan)
  404. footwidth = $tfoot.find('table').innerWidth();
  405. $tfoot.css({
  406. 'top': settings.scrollbarOffset,
  407. 'width': footwidth
  408. });
  409. }
  410. },
  411. /*
  412. * return void
  413. */
  414. _setupTableFooter: function ($obj, obj, tableProps) {
  415. var $self = $obj,
  416. $wrapper = $self.closest('.fht-table-wrapper'),
  417. $tfoot = $self.find('tfoot'),
  418. $divFoot = $wrapper.find('div.fht-tfoot');
  419. if (!$divFoot.length) {
  420. if (settings.fixedColumns > 0) {
  421. $divFoot = $('<div class="fht-tfoot"><table class="fht-table"></table></div>').appendTo($wrapper.find('.fht-fixed-body'));
  422. } else {
  423. $divFoot = $('<div class="fht-tfoot"><table class="fht-table"></table></div>').appendTo($wrapper);
  424. }
  425. }
  426. $divFoot.find('table.fht-table').addClass(settings.originalTable.attr('class'));
  427. switch (true) {
  428. case !$tfoot.length && settings.cloneHeadToFoot == true && settings.footer == true:
  429. var $divHead = $wrapper.find('div.fht-thead');
  430. $divFoot.empty();
  431. $divHead.find('table')
  432. .clone()
  433. .appendTo($divFoot);
  434. break;
  435. case $tfoot.length && settings.cloneHeadToFoot == false && settings.footer == true:
  436. $divFoot.find('table')
  437. .append($tfoot)
  438. .css({
  439. 'margin-top': -tableProps.border
  440. });
  441. helpers._setupClone($divFoot, tableProps.tfoot);
  442. break;
  443. }
  444. },
  445. /*
  446. * return object
  447. * Widths of each thead cell and tbody cell for the first rows.
  448. * Used in fixing widths for the fixed header and optional footer.
  449. */
  450. _getTableProps: function($obj) {
  451. var tableProp = {
  452. thead: {},
  453. tbody: {},
  454. tfoot: {},
  455. border: 0
  456. },
  457. borderCollapse = 1;
  458. if (settings.borderCollapse == true) {
  459. borderCollapse = 2;
  460. }
  461. tableProp.border = ($obj.find('th:first-child').outerWidth() - $obj.find('th:first-child').innerWidth()) / borderCollapse;
  462. $obj.find('thead tr:first-child > *').each(function(index) {
  463. tableProp.thead[index] = $(this).width() + tableProp.border;
  464. });
  465. $obj.find('tfoot tr:first-child > *').each(function(index) {
  466. tableProp.tfoot[index] = $(this).width() + tableProp.border;
  467. });
  468. $obj.find('tbody tr:first-child > *').each(function(index) {
  469. tableProp.tbody[index] = $(this).width() + tableProp.border;
  470. });
  471. return tableProp;
  472. },
  473. /*
  474. * return void
  475. * Fix widths of each cell in the first row of obj.
  476. */
  477. _setupClone: function($obj, cellArray) {
  478. var $self = $obj,
  479. selector = ($self.find('thead').length) ?
  480. 'thead tr:first-child > *' :
  481. ($self.find('tfoot').length) ?
  482. 'tfoot tr:first-child > *' :
  483. 'tbody tr:first-child > *',
  484. $cell;
  485. $self.find(selector).each(function(index) {
  486. $cell = ($(this).find('div.fht-cell').length) ? $(this).find('div.fht-cell') : $('<div class="fht-cell"></div>').appendTo($(this));
  487. $cell.css({
  488. 'width': parseInt(cellArray[index], 10)
  489. });
  490. /*
  491. * Fixed Header and Footer should extend the full width
  492. * to align with the scrollbar of the body
  493. */
  494. if (!$(this).closest('.fht-tbody').length && $(this).is(':last-child') && !$(this).closest('.fht-fixed-column').length) {
  495. var padding = Math.max((($(this).innerWidth() - $(this).width()) / 2), settings.scrollbarOffset);
  496. $(this).css({
  497. 'padding-right': parseInt($(this).css('padding-right')) + padding + 'px'
  498. });
  499. }
  500. });
  501. },
  502. /*
  503. * return boolean
  504. * Determine how the browser calculates fixed widths with padding for tables
  505. * true if width = padding + width
  506. * false if width = width
  507. */
  508. _isPaddingIncludedWithWidth: function() {
  509. var $obj = $('<table class="fht-table"><tr><td style="padding: 10px; font-size: 10px;">test</td></tr></table>'),
  510. defaultHeight,
  511. newHeight;
  512. $obj.addClass(settings.originalTable.attr('class'));
  513. $obj.appendTo('body');
  514. defaultHeight = $obj.find('td').height();
  515. $obj.find('td')
  516. .css('height', $obj.find('tr').height());
  517. newHeight = $obj.find('td').height();
  518. $obj.remove();
  519. if (defaultHeight != newHeight) {
  520. return true;
  521. } else {
  522. return false;
  523. }
  524. },
  525. /*
  526. * return int
  527. * get the width of the browsers scroll bar
  528. */
  529. _getScrollbarWidth: function() {
  530. var scrollbarWidth = 0;
  531. if (!scrollbarWidth) {
  532. if (/msie/.test(navigator.userAgent.toLowerCase())) {
  533. var $textarea1 = $('<textarea cols="10" rows="2"></textarea>')
  534. .css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body'),
  535. $textarea2 = $('<textarea cols="10" rows="2" style="overflow: hidden;"></textarea>')
  536. .css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body');
  537. scrollbarWidth = $textarea1.width() - $textarea2.width() + 2; // + 2 for border offset
  538. $textarea1.add($textarea2).remove();
  539. } else {
  540. var $div = $('<div />')
  541. .css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: -1000 })
  542. .prependTo('body').append('<div />').find('div')
  543. .css({ width: '100%', height: 200 });
  544. scrollbarWidth = 100 - $div.width();
  545. $div.parent().remove();
  546. }
  547. }
  548. return scrollbarWidth;
  549. }
  550. };
  551. // if a method as the given argument exists
  552. if (methods[method]) {
  553. // call the respective method
  554. return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
  555. // if an object is given as method OR nothing is given as argument
  556. } else if (typeof method === 'object' || !method) {
  557. // call the initialization method
  558. return methods.init.apply(this, arguments);
  559. // otherwise
  560. } else {
  561. // trigger an error
  562. $.error('Method "' + method + '" does not exist in fixedHeaderTable plugin!');
  563. }
  564. };
  565. })(jQuery);