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 21(function ($) { 22 23 $.fn.fixedHeaderTable = function (method) { 24 25 // plugin's default options 26 var defaults = { 27 width: '100%', 28 height: '100%', 29 themeClass: 'fht-default', 30 borderCollapse: true, 31 fixedColumns: 0, // fixed first columns 32 fixedColumn: false, // For backward-compatibility 33 sortable: false, 34 autoShow: true, // hide table after its created 35 footer: false, // show footer 36 cloneHeadToFoot: false, // clone head and use as footer 37 autoResize: false, // resize table if its parent wrapper changes size 38 create: null // callback after plugin completes 39 }; 40 41 var settings = {}; 42 43 // public methods 44 var methods = { 45 init: function (options) { 46 settings = $.extend({}, defaults, options); 47 48 // iterate through all the DOM elements we are attaching the plugin to 49 return this.each(function () { 50 var $self = $(this); // reference the jQuery version of the current DOM element 51 52 if (helpers._isTable($self)) { 53 methods.setup.apply(this, Array.prototype.slice.call(arguments, 1)); 54 $.isFunction(settings.create) && settings.create.call(this); 55 } else { 56 $.error('Invalid table mark-up'); 57 } 58 }); 59 }, 60 61 /* 62 * Setup table structure for fixed headers and optional footer 63 */ 64 setup: function () { 65 var $self = $(this), 66 self = this, 67 $thead = $self.find('thead'), 68 $tfoot = $self.find('tfoot'), 69 tfootHeight = 0, 70 $wrapper, 71 $divHead, 72 $divBody, 73 $fixedBody, 74 widthMinusScrollbar; 75 76 settings.originalTable = $(this).clone(); 77 settings.includePadding = helpers._isPaddingIncludedWithWidth(); 78 settings.scrollbarOffset = helpers._getScrollbarWidth(); 79 settings.themeClassName = settings.themeClass; 80 81 if (settings.width.search && settings.width.search('%') > -1) { 82 widthMinusScrollbar = $self.parent().width() - settings.scrollbarOffset; 83 } else { 84 widthMinusScrollbar = settings.width - settings.scrollbarOffset; 85 } 86 87 $self.css({ 88 width: widthMinusScrollbar 89 }); 90 91 92 if (!$self.closest('.fht-table-wrapper').length) { 93 $self.addClass('fht-table'); 94 $self.wrap('<div class="fht-table-wrapper"></div>'); 95 } 96 97 $wrapper = $self.closest('.fht-table-wrapper'); 98 99 if(settings.fixedColumn == true && settings.fixedColumns <= 0) { 100 settings.fixedColumns = 1; 101 } 102 103 if (settings.fixedColumns > 0 && $wrapper.find('.fht-fixed-column').length == 0) { 104 $self.wrap('<div class="fht-fixed-body"></div>'); 105 106 $('<div class="fht-fixed-column"></div>').prependTo($wrapper); 107 108 $fixedBody = $wrapper.find('.fht-fixed-body'); 109 } 110 111 $wrapper.css({ 112 width: settings.width, 113 height: settings.height 114 }) 115 .addClass(settings.themeClassName); 116 117 if (!$self.hasClass('fht-table-init')) { 118 $self.wrap('<div class="fht-tbody"></div>'); 119 } 120 121 $divBody = $self.closest('.fht-tbody'); 122 123 var tableProps = helpers._getTableProps($self); 124 125 helpers._setupClone($divBody, tableProps.tbody); 126 127 if (!$self.hasClass('fht-table-init')) { 128 if (settings.fixedColumns > 0) { 129 $divHead = $('<div class="fht-thead"><table class="fht-table"></table></div>').prependTo($fixedBody); 130 } else { 131 $divHead = $('<div class="fht-thead"><table class="fht-table"></table></div>').prependTo($wrapper); 132 } 133 134 $divHead.find('table.fht-table') 135 .addClass(settings.originalTable.attr('class')) 136 .attr('style', settings.originalTable.attr('style')); 137 138 $thead.clone().appendTo($divHead.find('table')); 139 } else { 140 $divHead = $wrapper.find('div.fht-thead'); 141 } 142 143 helpers._setupClone($divHead, tableProps.thead); 144 145 $self.css({ 146 'margin-top': -$divHead.outerHeight(true) 147 }); 148 149 /* 150 * Check for footer 151 * Setup footer if present 152 */ 153 if (settings.footer == true) { 154 helpers._setupTableFooter($self, self, tableProps); 155 156 if (!$tfoot.length) { 157 $tfoot = $wrapper.find('div.fht-tfoot table'); 158 } 159 160 tfootHeight = $tfoot.outerHeight(true); 161 } 162 163 var tbodyHeight = $wrapper.height() - $thead.outerHeight(true) - tfootHeight - tableProps.border; 164 165 $divBody.css({ 166 'height': tbodyHeight 167 }); 168 169 $self.addClass('fht-table-init'); 170
171 if (typeof(settings.altClass) !== 'undefined') { 172 methods.altRows.apply(self); 173 } 174 175 if (settings.fixedColumns > 0) { 176 helpers._setupFixedColumn($self, self, tableProps); 177 } 178 179 if (!settings.autoShow) { 180 $wrapper.hide(); 181 } 182 183 helpers._bindScroll($divBody, tableProps); 184 185 return self; 186 }, 187 188 /* 189 * Resize the table 190 * Incomplete - not implemented yet 191 */ 192 resize: function() { 193 var self = this; 194 return self; 195 }, 196 197 /* 198 * Add CSS class to alternating rows 199 */ 200 altRows: function(arg1) { 201 var $self = $(this), 202 altClass = (typeof(arg1) !== 'undefined') ? arg1 : settings.altClass; 203 204 $self.closest('.fht-table-wrapper') 205 .find('tbody tr:odd:not(:hidden)') 206 .addClass(altClass); 207 }, 208 209 /* 210 * Show a hidden fixedHeaderTable table 211 */ 212 show: function(arg1, arg2, arg3) { 213 var $self = $(this), 214 self = this, 215 $wrapper = $self.closest('.fht-table-wrapper'); 216 217 // User provided show duration without a specific effect 218 if (typeof(arg1) !== 'undefined' && typeof(arg1) === 'number') { 219 $wrapper.show(arg1, function() { 220 $.isFunction(arg2) && arg2.call(this); 221 }); 222 223 return self; 224 225 } else if (typeof(arg1) !== 'undefined' && typeof(arg1) === 'string' && 226 typeof(arg2) !== 'undefined' && typeof(arg2) === 'number') { 227 // User provided show duration with an effect 228 229 $wrapper.show(arg1, arg2, function() { 230 $.isFunction(arg3) && arg3.call(this); 231 }); 232 233 return self; 234 235 } 236 237 $self.closest('.fht-table-wrapper') 238 .show(); 239 $.isFunction(arg1) && arg1.call(this); 240 241 return self; 242 }, 243 244 /* 245 * Hide a fixedHeaderTable table 246 */ 247 hide: function(arg1, arg2, arg3) { 248 var $self = $(this), 249 self = this, 250 $wrapper = $self.closest('.fht-table-wrapper'); 251 252 // User provided show duration without a specific effect 253 if (typeof(arg1) !== 'undefined' && typeof(arg1) === 'number') { 254 $wrapper.hide(arg1, function() { 255 $.isFunction(arg3) && arg3.call(this); 256 }); 257 258 return self; 259 } else if (typeof(arg1) !== 'undefined' && typeof(arg1) === 'string' && 260 typeof(arg2) !== 'undefined' && typeof(arg2) === 'number') { 261 262 $wrapper.hide(arg1, arg2, function() { 263 $.isFunction(arg3) && arg3.call(this); 264 }); 265 266 return self; 267 } 268 269 $self.closest('.fht-table-wrapper') 270 .hide(); 271 272 $.isFunction(arg3) && arg3.call(this); 273 274 275 276 return self; 277 }, 278 279 /* 280 * Destory fixedHeaderTable and return table to original state 281 */ 282 destroy: function() { 283 var $self = $(this), 284 self = this, 285 $wrapper = $self.closest('.fht-table-wrapper'); 286 287 $self.insertBefore($wrapper) 288 .removeAttr('style') 289 .append($wrapper.find('tfoot')) 290 .removeClass('fht-table fht-table-init') 291 .find('.fht-cell') 292 .remove(); 293 294 $wrapper.remove(); 295 296 return self; 297 } 298 299 }; 300 301 // private methods 302 var helpers = { 303 304 /* 305 * return boolean 306 * True if a thead and tbody exist. 307 */ 308 _isTable: function($obj) { 309 var $self = $obj, 310 hasTable = $self.is('table'), 311 hasThead = $self.find('thead').length > 0, 312 hasTbody = $self.find('tbody').length > 0; 313 314 if (hasTable && hasThead && hasTbody) { 315 return true; 316 } 317 318 return false; 319 320 }, 321 322 /* 323 * return void 324 * bind scroll event 325 */ 326 _bindScroll: function($obj) { 327 var $self = $obj, 328 $wrapper = $self.closest('.fht-table-wrapper'), 329 $thead = $self.siblings('.fht-thead'), 330 $tfoot = $self.siblings('.fht-tfoot'); 331 332 $self.bind('scroll', function() { 333 if (settings.fixedColumns > 0) { 334 var $fixedColumns = $wrapper.find('.fht-fixed-column'); 335 336 $fixedColumns.find('.fht-tbody table') 337 .css({ 338 'margin-top': -$self.scrollTop() 339 }); 340 } 341 342 $thead.find('table') 343 .css({ 344 'margin-left': -this.scrollLeft 345 }); 346 347 if (settings.footer || settings.cloneHeadToFoot) { 348 $tfoot.find('table') 349 .css({ 350 'margin-left': -this.scrollLeft 351 }); 352 } 353 }); 354 }, 355 356 /* 357 * return void 358 */ 359 _fixHeightWithCss: function ($obj, tableProps) { 360 if (settings.includePadding) { 361 $obj.css({ 362 'height': $obj.height() + tableProps.border 363 }); 364 } else { 365 $obj.css({ 366 'height': $obj.parent().height() + tableProps.border 367 }); 368 } 369 }, 370 371 /* 372 * return void 373 */ 374 _fixWidthWithCss: function($obj, tableProps, width) { 375 if (settings.includePadding) { 376 $obj.each(function() { 377 $(this).css({ 378 'width': width == undefined ? $(this).width() + tableProps.border : width + tableProps.border 379 }); 380 }); 381 } else { 382 $obj.each(function() { 383 $(this).css({ 384 'width': width == undefined ? $(this).parent().width() + tableProps.border : width + tableProps.border 385 }); 386 }); 387 } 388 389 }, 390 391 /* 392 * return void 393 */ 394 _setupFixedColumn: function ($obj, obj, tableProps) { 395 var $self = $obj, 396 $wrapper = $self.closest('.fht-table-wrapper'), 397 $fixedBody = $wrapper.find('.fht-fixed-body'), 398 $fixedColumn = $wrapper.find('.fht-fixed-column'), 399 $thead = $('<div class="fht-thead"><table class="fht-table"><thead><tr></tr></thead></table></div>'), 400 $tbody = $('<div class="fht-tbody"><table class="fht-table"><tbody></tbody></table></div>'), 401 $tfoot = $('<div class="fht-tfoot"><table class="fht-table"><tfoot><tr></tr></tfoot></table></div>'), 402 fixedBodyWidth = $wrapper.width(), 403 fixedBodyHeight = $fixedBody.find('.fht-tbody').height() - settings.scrollbarOffset, 404 $firstThChildren, 405 $firstTdChildren, 406 fixedColumnWidth, 407 $newRow, 408 firstTdChildrenSelector; 409 410 $thead.find('table.fht-table').addClass(settings.originalTable.attr('class')); 411 $tbody.find('table.fht-table').addClass(settings.originalTable.attr('class')); 412 $tfoot.find('table.fht-table').addClass(settings.originalTable.attr('class')); 413 414 $firstThChildren = $fixedBody.find('.fht-thead thead tr > *:lt(' + settings.fixedColumns + ')'); 415 fixedColumnWidth = settings.fixedColumns * tableProps.border; 416 $firstThChildren.each(function() { 417 fixedColumnWidth += $(this).outerWidth(true); 418 }); 419 420 // Fix cell heights 421 helpers._fixHeightWithCss($firstThChildren, tableProps); 422 helpers._fixWidthWithCss($firstThChildren, tableProps); 423 424 var tdWidths = []; 425 $firstThChildren.each(function() { 426 tdWidths.push($(this).width()); 427 }); 428 429 firstTdChildrenSelector = 'tbody tr > *:not(:nth-child(n+' + (settings.fixedColumns + 1) + '))'; 430 $firstTdChildren = $fixedBody.find(firstTdChildrenSelector) 431 .each(function(index) { 432 helpers._fixHeightWithCss($(this), tableProps); 433 helpers._fixWidthWithCss($(this), tableProps, tdWidths[index % settings.fixedColumns] ); 434 }); 435 436 // clone header 437 $thead.appendTo($fixedColumn) 438 .find('tr') 439 .append($firstThChildren.clone()); 440 441 $tbody.appendTo($fixedColumn) 442 .css({ 443 'margin-top': -1, 444 'height': fixedBodyHeight + tableProps.border 445 }); 446 447 $firstTdChildren.each(function(index) { 448 if (index % settings.fixedColumns == 0) { 449 $newRow = $('<tr></tr>').appendTo($tbody.find('tbody')); 450 451 if (settings.altClass && $(this).parent().hasClass(settings.altClass)) { 452 $newRow.addClass(settings.altClass); 453 } 454 } 455 456 $(this).clone() 457 .appendTo($newRow); 458 }); 459 460 // set width of fixed column wrapper 461 $fixedColumn.css({ 462 'height': 0, 463 'width': fixedColumnWidth 464 }); 465 466 467 // bind mousewheel events 468 var maxTop = $fixedColumn.find('.fht-tbody .fht-table').height() - $fixedColumn.find('.fht-tbody').height(); 469 $fixedColumn.find('.fht-tbody .fht-table').bind('mousewheel', function(event, delta, deltaX, deltaY) { 470 if (deltaY == 0) { 471 return; 472 } 473 var top = parseInt($(this).css('marginTop'), 10) + (deltaY > 0 ? 120 : -120); 474 if (top > 0) { 475 top = 0; 476 } 477 if (top < -maxTop) { 478 top = -maxTop; 479 } 480 $(this).css('marginTop', top); 481 $fixedBody.find('.fht-tbody').scrollTop(-top).scroll(); 482 return false;
483 }); 484 485 486 // set width of body table wrapper 487 $fixedBody.css({ 488 'width': fixedBodyWidth 489 }); 490 491 // setup clone footer with fixed column 492 if (settings.footer == true || settings.cloneHeadToFoot == true) { 493 var $firstTdFootChild = $fixedBody.find('.fht-tfoot tr > *:lt(' + settings.fixedColumns + ')'), 494 footwidth; 495 496 helpers._fixHeightWithCss($firstTdFootChild, tableProps); 497 $tfoot.appendTo($fixedColumn) 498 .find('tr') 499 .append($firstTdFootChild.clone()); 500 // Set (view width) of $tfoot div to width of table (this accounts for footers with a colspan) 501 footwidth = $tfoot.find('table').innerWidth(); 502 $tfoot.css({ 503 'top': settings.scrollbarOffset, 504 'width': footwidth 505 }); 506 } 507 }, 508 509 /* 510 * return void 511 */ 512 _setupTableFooter: function ($obj, obj, tableProps) { 513 var $self = $obj, 514 $wrapper = $self.closest('.fht-table-wrapper'), 515 $tfoot = $self.find('tfoot'), 516 $divFoot = $wrapper.find('div.fht-tfoot'); 517 518 if (!$divFoot.length) { 519 if (settings.fixedColumns > 0) { 520 $divFoot = $('<div class="fht-tfoot"><table class="fht-table"></table></div>').appendTo($wrapper.find('.fht-fixed-body')); 521 } else { 522 $divFoot = $('<div class="fht-tfoot"><table class="fht-table"></table></div>').appendTo($wrapper); 523 } 524 } 525 $divFoot.find('table.fht-table').addClass(settings.originalTable.attr('class')); 526 527 switch (true) { 528 case !$tfoot.length && settings.cloneHeadToFoot == true && settings.footer == true: 529 530 var $divHead = $wrapper.find('div.fht-thead'); 531 532 $divFoot.empty(); 533 $divHead.find('table') 534 .clone() 535 .appendTo($divFoot); 536 537 break; 538 case $tfoot.length && settings.cloneHeadToFoot == false && settings.footer == true: 539 540 $divFoot.find('table') 541 .append($tfoot) 542 .css({ 543 'margin-top': -tableProps.border 544 }); 545 546 helpers._setupClone($divFoot, tableProps.tfoot); 547 548 break; 549 } 550 551 }, 552 553 /* 554 * return object 555 * Widths of each thead cell and tbody cell for the first rows. 556 * Used in fixing widths for the fixed header and optional footer. 557 */ 558 _getTableProps: function($obj) { 559 var tableProp = { 560 thead: {}, 561 tbody: {}, 562 tfoot: {}, 563 border: 0 564 }, 565 borderCollapse = 1; 566 567 if (settings.borderCollapse == true) { 568 borderCollapse = 2; 569 } 570 571 tableProp.border = ($obj.find('th:first-child').outerWidth() - $obj.find('th:first-child').innerWidth()) / borderCollapse; 572 573 $obj.find('thead tr:first-child > *').each(function(index) { 574 tableProp.thead[index] = $(this).width() + tableProp.border; 575 }); 576 577 $obj.find('tfoot tr:first-child > *').each(function(index) { 578 tableProp.tfoot[index] = $(this).width() + tableProp.border; 579 }); 580 581 $obj.find('tbody tr:first-child > *').each(function(index) { 582 tableProp.tbody[index] = $(this).width() + tableProp.border; 583 }); 584 585 return tableProp; 586 }, 587 588 /* 589 * return void 590 * Fix widths of each cell in the first row of obj. 591 */ 592 _setupClone: function($obj, cellArray) { 593 var $self = $obj, 594 selector = ($self.find('thead').length) ? 595 'thead tr:first-child > *' : 596 ($self.find('tfoot').length) ? 597 'tfoot tr:first-child > *' : 598 'tbody tr:first-child > *', 599 $cell; 600 601 $self.find(selector).each(function(index) { 602 $cell = ($(this).find('div.fht-cell').length) ? $(this).find('div.fht-cell') : $('<div class="fht-cell"></div>').appendTo($(this)); 603 604 $cell.css({ 605 'width': parseInt(cellArray[index], 10) 606 }); 607 608 /* 609 * Fixed Header and Footer should extend the full width 610 * to align with the scrollbar of the body 611 */ 612 if (!$(this).closest('.fht-tbody').length && $(this).is(':last-child') && !$(this).closest('.fht-fixed-column').length) { 613 var padding = Math.max((($(this).innerWidth() - $(this).width()) / 2), settings.scrollbarOffset); 614 $(this).css({ 615 'padding-right': parseInt($(this).css('padding-right')) + padding + 'px' 616 }); 617 } 618 }); 619 }, 620 621 /* 622 * return boolean 623 * Determine how the browser calculates fixed widths with padding for tables 624 * true if width = padding + width 625 * false if width = width 626 */ 627 _isPaddingIncludedWithWidth: function() { 628 var $obj = $('<table class="fht-table"><tr><td style="padding: 10px; font-size: 10px;">test</td></tr></table>'),
629 defaultHeight, 630 newHeight; 631 632 $obj.addClass(settings.originalTable.attr('class')); 633 $obj.appendTo('body'); 634 635 defaultHeight = $obj.find('td').height(); 636 637 $obj.find('td') 638 .css('height', $obj.find('tr').height()); 639 640 newHeight = $obj.find('td').height(); 641 $obj.remove(); 642 643 if (defaultHeight != newHeight) { 644 return true; 645 } else { 646 return false; 647 } 648 649 }, 650 651 /* 652 * return int 653 * get the width of the browsers scroll bar 654 */ 655 _getScrollbarWidth: function() { 656 var scrollbarWidth = 0; 657 658 if (!scrollbarWidth) { 659 if (/msie/.test(navigator.userAgent.toLowerCase())) { 660 var $textarea1 = $('<textarea cols="10" rows="2"></textarea>') 661 .css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body'), 662 $textarea2 = $('<textarea cols="10" rows="2" style="overflow: hidden;"></textarea>') 663 .css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body'); 664 665 scrollbarWidth = $textarea1.width() - $textarea2.width() + 2; // + 2 for border offset 666 $textarea1.add($textarea2).remove(); 667 } else { 668 var $div = $('<div />') 669 .css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: -1000 }) 670 .prependTo('body').append('<div />').find('div') 671 .css({ width: '100%', height: 200 }); 672 673 scrollbarWidth = 100 - $div.width(); 674 $div.parent().remove(); 675 } 676 } 677 678 return scrollbarWidth; 679 } 680 681 }; 682 683 684 // if a method as the given argument exists 685 if (methods[method]) { 686 687 // call the respective method 688 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 689 690 // if an object is given as method OR nothing is given as argument 691 } else if (typeof method === 'object' || !method) { 692 693 // call the initialization method 694 return methods.init.apply(this, arguments); 695 696 // otherwise 697 } else { 698 699 // trigger an error 700 $.error('Method "' + method + '" does not exist in fixedHeaderTable plugin!'); 701 702 } 703 704 }; 705 706})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.