2 * ©2010-2014 SpryMedia Ltd - datatables.net/license
6 * @summary FixedColumns
7 * @description Freeze columns in place on a scrolling DataTable
9 * @file dataTables.fixedColumns.js
10 * @author SpryMedia Ltd (www.sprymedia.co.uk)
11 * @contact www.sprymedia.co.uk/contact
12 * @copyright Copyright 2010-2014 SpryMedia Ltd.
14 * This source file is free software, available under the following license:
15 * MIT license - http://datatables.net/license/mit
17 * This source file is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
21 * For details please refer to: http://www.datatables.net
25 (function(window, document, undefined) {
28 var factory = function( $, DataTable ) {
32 * When making use of DataTables' x-axis scrolling feature, you may wish to
33 * fix the left most column in place. This plug-in for DataTables provides
34 * exactly this option (note for non-scrolling tables, please use the
35 * FixedHeader plug-in, which can fix headers, footers and columns). Key
38 * * Freezes the left or right most columns to the side of the table
39 * * Option to freeze two or more columns
40 * * Full integration with DataTables' scrolling options
41 * * Speed - FixedColumns is fast in its operation
46 * @param {object} dt DataTables instance. With DataTables 1.10 this can also
47 * be a jQuery collection, a jQuery selector, DataTables API instance or
49 * @param {object} [init={}] Configuration object for FixedColumns. Options are
50 * defined by {@link FixedColumns.defaults}
52 * @requires jQuery 1.7+
53 * @requires DataTables 1.8.0+
56 * var table = $('#example').dataTable( {
59 * new $.fn.dataTable.fixedColumns( table );
61 var FixedColumns = function ( dt, init ) {
64 /* Sanity check - you just know it will happen */
65 if ( ! ( this instanceof FixedColumns ) )
67 alert( "FixedColumns warning: FixedColumns must be initialised with the 'new' keyword." );
71 if ( typeof init == 'undefined' )
76 // Use the DataTables Hungarian notation mapping method, if it exists to
77 // provide forwards compatibility for camel case variables
78 var camelToHungarian = $.fn.dataTable.camelToHungarian;
79 if ( camelToHungarian ) {
80 camelToHungarian( FixedColumns.defaults, FixedColumns.defaults, true );
81 camelToHungarian( FixedColumns.defaults, init );
84 // v1.10 allows the settings object to be got form a number of sources
85 var dtSettings = $.fn.dataTable.Api ?
86 new $.fn.dataTable.Api( dt ).settings()[0] :
90 * Settings object which contains customisable information for FixedColumns instance
92 * @extends FixedColumns.defaults
97 * DataTables settings objects
99 * @default Obtained from DataTables instance
104 * Number of columns in the DataTable - stored for quick access
106 * @default Obtained from DataTables instance
108 "iTableColumns": dtSettings.aoColumns.length,
111 * Original outer widths of the columns as rendered by DataTables - used to calculate
112 * the FixedColumns grid bounding box
119 * Original inner widths of the columns as rendered by DataTables - used to apply widths
129 * DOM elements used by the class instance
136 * DataTables scrolling element
143 * DataTables header table
150 * DataTables body table
157 * DataTables footer table
164 * Display grid elements
169 * Grid wrapper. This is the container element for the 3x3 grid
176 * DataTables scrolling element. This element is the DataTables
177 * component in the display grid (making up the main table - i.e.
178 * not the fixed columns).
185 * Left fixed column grid components
196 * Right fixed column grid components
213 * Left column cloned table nodes
218 * Cloned header table
232 * Cloned footer table
240 * Right column cloned table nodes
245 * Cloned header table
259 * Cloned footer table
268 /* Attach the instance to the DataTables instance so it can be accessed easily */
269 dtSettings._oFixedColumns = this;
272 if ( ! dtSettings._bInitComplete )
274 dtSettings.oApi._fnCallbackReg( dtSettings, 'aoInitComplete', function () {
275 that._fnConstruct( init );
280 this._fnConstruct( init );
286 FixedColumns.prototype = /** @lends FixedColumns.prototype */{
287 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
289 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
292 * Update the fixed columns - including headers and footers. Note that FixedColumns will
293 * automatically update the display whenever the host DataTable redraws.
296 * var table = $('#example').dataTable( {
299 * var fc = new $.fn.dataTable.fixedColumns( table );
301 * // at some later point when the table has been manipulated....
304 "fnUpdate": function ()
306 this._fnDraw( true );
311 * Recalculate the resizes of the 3x3 grid that FixedColumns uses for display of the table.
312 * This is useful if you update the width of the table container. Note that FixedColumns will
313 * perform this function automatically when the window.resize event is fired.
316 * var table = $('#example').dataTable( {
319 * var fc = new $.fn.dataTable.fixedColumns( table );
321 * // Resize the table container and then have FixedColumns adjust its layout....
322 * $('#content').width( 1200 );
323 * fc.fnRedrawLayout();
325 "fnRedrawLayout": function ()
328 this._fnGridLayout();
334 * Mark a row such that it's height should be recalculated when using 'semiauto' row
335 * height matching. This function will have no effect when 'none' or 'auto' row height
337 * @param {Node} nTr TR element that should have it's height recalculated
340 * var table = $('#example').dataTable( {
343 * var fc = new $.fn.dataTable.fixedColumns( table );
345 * // manipulate the table - mark the row as needing an update then update the table
346 * // this allows the redraw performed by DataTables fnUpdate to recalculate the row
348 * fc.fnRecalculateHeight();
349 * table.fnUpdate( $('#example tbody tr:eq(0)')[0], ["insert date", 1, 2, 3 ... ]);
351 "fnRecalculateHeight": function ( nTr )
353 delete nTr._DTTC_iHeight;
354 nTr.style.height = 'auto';
359 * Set the height of a given row - provides cross browser compatibility
360 * @param {Node} nTarget TR element that should have it's height recalculated
361 * @param {int} iHeight Height in pixels to set
364 * var table = $('#example').dataTable( {
367 * var fc = new $.fn.dataTable.fixedColumns( table );
369 * // You may want to do this after manipulating a row in the fixed column
370 * fc.fnSetRowHeight( $('#example tbody tr:eq(0)')[0], 50 );
372 "fnSetRowHeight": function ( nTarget, iHeight )
374 nTarget.style.height = iHeight+"px";
379 * Get data index information about a row or cell in the table body.
380 * This function is functionally identical to fnGetPosition in DataTables,
381 * taking the same parameter (TH, TD or TR node) and returning exactly the
382 * the same information (data index information). THe difference between
383 * the two is that this method takes into account the fixed columns in the
384 * table, so you can pass in nodes from the master table, or the cloned
385 * tables and get the index position for the data in the main table.
386 * @param {node} node TR, TH or TD element to get the information about
387 * @returns {int} If nNode is given as a TR, then a single index is
388 * returned, or if given as a cell, an array of [row index, column index
389 * (visible), column index (all)] is given.
391 "fnGetPosition": function ( node )
394 var inst = this.s.dt.oInstance;
396 if ( ! $(node).parents('.DTFC_Cloned').length )
398 // Not in a cloned table
399 return inst.fnGetPosition( node );
403 // Its in the cloned table, so need to look up position
404 if ( node.nodeName.toLowerCase() === 'tr' ) {
405 idx = $(node).index();
406 return inst.fnGetPosition( $('tr', this.s.dt.nTBody)[ idx ] );
410 var colIdx = $(node).index();
411 idx = $(node.parentNode).index();
412 var row = inst.fnGetPosition( $('tr', this.s.dt.nTBody)[ idx ] );
417 inst.oApi._fnVisibleToColumnIndex( this.s.dt, colIdx )
425 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
426 * Private methods (they are of course public in JS, but recommended as private)
427 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
430 * Initialisation for FixedColumns
431 * @param {Object} oInit User settings for initialisation
435 "_fnConstruct": function ( oInit )
440 /* Sanity checking */
441 if ( typeof this.s.dt.oInstance.fnVersionCheck != 'function' ||
442 this.s.dt.oInstance.fnVersionCheck( '1.8.0' ) !== true )
444 alert( "FixedColumns "+FixedColumns.VERSION+" required DataTables 1.8.0 or later. "+
445 "Please upgrade your DataTables installation" );
449 if ( this.s.dt.oScroll.sX === "" )
451 this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "FixedColumns is not needed (no "+
452 "x-scrolling in DataTables enabled), so no action will be taken. Use 'FixedHeader' for "+
453 "column fixing when scrolling is not enabled" );
457 /* Apply the settings from the user / defaults */
458 this.s = $.extend( true, this.s, FixedColumns.defaults, oInit );
460 /* Set up the DOM as we need it and cache nodes */
461 var classes = this.s.dt.oClasses;
462 this.dom.grid.dt = $(this.s.dt.nTable).parents('div.'+classes.sScrollWrapper)[0];
463 this.dom.scroller = $('div.'+classes.sScrollBody, this.dom.grid.dt )[0];
465 /* Set up the DOM that we want for the fixed column layout grid */
472 // When the body is scrolled - scroll the left and right columns
474 .on( 'mouseover.DTFC touchstart.DTFC', function () {
475 mouseController = 'main';
477 .on( 'scroll.DTFC', function () {
478 if ( mouseController === 'main' ) {
479 if ( that.s.iLeftColumns > 0 ) {
480 that.dom.grid.left.liner.scrollTop = that.dom.scroller.scrollTop;
482 if ( that.s.iRightColumns > 0 ) {
483 that.dom.grid.right.liner.scrollTop = that.dom.scroller.scrollTop;
488 var wheelType = 'onwheel' in document.createElement('div') ?
492 if ( that.s.iLeftColumns > 0 ) {
493 // When scrolling the left column, scroll the body and right column
494 $(that.dom.grid.left.liner)
495 .on( 'mouseover.DTFC touchstart.DTFC', function () {
496 mouseController = 'left';
498 .on( 'scroll.DTFC', function () {
499 if ( mouseController === 'left' ) {
500 that.dom.scroller.scrollTop = that.dom.grid.left.liner.scrollTop;
501 if ( that.s.iRightColumns > 0 ) {
502 that.dom.grid.right.liner.scrollTop = that.dom.grid.left.liner.scrollTop;
506 .on( wheelType, function(e) { // xxx update the destroy as well
507 // Pass horizontal scrolling through
508 var xDelta = e.type === 'wheel' ?
509 -e.originalEvent.deltaX :
510 e.originalEvent.wheelDeltaX;
511 that.dom.scroller.scrollLeft -= xDelta;
515 if ( that.s.iRightColumns > 0 ) {
516 // When scrolling the right column, scroll the body and the left column
517 $(that.dom.grid.right.liner)
518 .on( 'mouseover.DTFC touchstart.DTFC', function () {
519 mouseController = 'right';
521 .on( 'scroll.DTFC', function () {
522 if ( mouseController === 'right' ) {
523 that.dom.scroller.scrollTop = that.dom.grid.right.liner.scrollTop;
524 if ( that.s.iLeftColumns > 0 ) {
525 that.dom.grid.left.liner.scrollTop = that.dom.grid.right.liner.scrollTop;
529 .on( wheelType, function(e) {
530 // Pass horizontal scrolling through
531 var xDelta = e.type === 'wheel' ?
532 -e.originalEvent.deltaX :
533 e.originalEvent.wheelDeltaX;
534 that.dom.scroller.scrollLeft -= xDelta;
538 $(window).on( 'resize.DTFC', function () {
539 that._fnGridLayout.call( that );
542 var bFirstDraw = true;
543 var jqTable = $(this.s.dt.nTable);
546 .on( 'draw.dt.DTFC', function () {
547 that._fnDraw.call( that, bFirstDraw );
550 .on( 'column-sizing.dt.DTFC', function () {
552 that._fnGridLayout( that );
554 .on( 'column-visibility.dt.DTFC', function () {
556 that._fnGridLayout( that );
557 that._fnDraw( true );
559 .on( 'destroy.dt.DTFC', function () {
560 jqTable.off( 'column-sizing.dt.DTFC destroy.dt.DTFC draw.dt.DTFC' );
562 $(that.dom.scroller).off( 'scroll.DTFC mouseover.DTFC' );
563 $(window).off( 'resize.DTFC' );
565 $(that.dom.grid.left.liner).off( 'scroll.DTFC mouseover.DTFC '+wheelType );
566 $(that.dom.grid.left.wrapper).remove();
568 $(that.dom.grid.right.liner).off( 'scroll.DTFC mouseover.DTFC '+wheelType );
569 $(that.dom.grid.right.wrapper).remove();
572 /* Get things right to start with - note that due to adjusting the columns, there must be
573 * another redraw of the main table. It doesn't need to be a full redraw however.
575 this._fnGridLayout();
576 this.s.dt.oInstance.fnDraw(false);
581 * Calculate the column widths for the grid layout
585 "_fnColCalc": function ()
591 this.s.aiInnerWidths = [];
592 this.s.aiOuterWidths = [];
594 $.each( this.s.dt.aoColumns, function (i, col) {
598 if ( ! th.filter(':visible').length ) {
599 that.s.aiInnerWidths.push( 0 );
600 that.s.aiOuterWidths.push( 0 );
604 // Inner width is used to assign widths to cells
605 // Outer width is used to calculate the container
606 var iWidth = th.outerWidth();
608 // When working with the left most-cell, need to add on the
609 // table's border to the outerWidth, since we need to take
610 // account of it, but it isn't in any cell
611 if ( that.s.aiOuterWidths.length === 0 ) {
612 border = $(that.s.dt.nTable).css('border-left-width');
613 iWidth += typeof border === 'string' ? 1 : parseInt( border, 10 );
616 // Likewise with the final column on the right
617 if ( that.s.aiOuterWidths.length === that.s.dt.aoColumns.length-1 ) {
618 border = $(that.s.dt.nTable).css('border-right-width');
619 iWidth += typeof border === 'string' ? 1 : parseInt( border, 10 );
622 that.s.aiOuterWidths.push( iWidth );
623 that.s.aiInnerWidths.push( th.width() );
625 if ( i < that.s.iLeftColumns )
627 iLeftWidth += iWidth;
630 if ( that.s.iTableColumns-that.s.iRightColumns <= i )
632 iRightWidth += iWidth;
637 this.s.iLeftWidth = iLeftWidth;
638 this.s.iRightWidth = iRightWidth;
643 * Set up the DOM for the fixed column. The way the layout works is to create a 1x3 grid
644 * for the left column, the DataTable (for which we just reuse the scrolling element DataTable
645 * puts into the DOM) and the right column. In each of he two fixed column elements there is a
646 * grouping wrapper element and then a head, body and footer wrapper. In each of these we then
647 * place the cloned header, body or footer tables. This effectively gives as 3x3 grid structure.
651 "_fnGridSetup": function ()
654 var oOverflow = this._fnDTOverflow();
657 this.dom.body = this.s.dt.nTable;
658 this.dom.header = this.s.dt.nTHead.parentNode;
659 this.dom.header.parentNode.parentNode.style.position = "relative";
662 $('<div class="DTFC_ScrollWrapper" style="position:relative; clear:both;">'+
663 '<div class="DTFC_LeftWrapper" style="position:absolute; top:0; left:0;">'+
664 '<div class="DTFC_LeftHeadWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
665 '<div class="DTFC_LeftBodyWrapper" style="position:relative; top:0; left:0; overflow:hidden;">'+
666 '<div class="DTFC_LeftBodyLiner" style="position:relative; top:0; left:0; overflow-y:scroll;"></div>'+
668 '<div class="DTFC_LeftFootWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
670 '<div class="DTFC_RightWrapper" style="position:absolute; top:0; left:0;">'+
671 '<div class="DTFC_RightHeadWrapper" style="position:relative; top:0; left:0;">'+
672 '<div class="DTFC_RightHeadBlocker DTFC_Blocker" style="position:absolute; top:0; bottom:0;"></div>'+
674 '<div class="DTFC_RightBodyWrapper" style="position:relative; top:0; left:0; overflow:hidden;">'+
675 '<div class="DTFC_RightBodyLiner" style="position:relative; top:0; left:0; overflow-y:scroll;"></div>'+
677 '<div class="DTFC_RightFootWrapper" style="position:relative; top:0; left:0;">'+
678 '<div class="DTFC_RightFootBlocker DTFC_Blocker" style="position:absolute; top:0; bottom:0;"></div>'+
682 var nLeft = nSWrapper.childNodes[0];
683 var nRight = nSWrapper.childNodes[1];
685 this.dom.grid.dt.parentNode.insertBefore( nSWrapper, this.dom.grid.dt );
686 nSWrapper.appendChild( this.dom.grid.dt );
688 this.dom.grid.wrapper = nSWrapper;
690 if ( this.s.iLeftColumns > 0 )
692 this.dom.grid.left.wrapper = nLeft;
693 this.dom.grid.left.head = nLeft.childNodes[0];
694 this.dom.grid.left.body = nLeft.childNodes[1];
695 this.dom.grid.left.liner = $('div.DTFC_LeftBodyLiner', nSWrapper)[0];
697 nSWrapper.appendChild( nLeft );
700 if ( this.s.iRightColumns > 0 )
702 this.dom.grid.right.wrapper = nRight;
703 this.dom.grid.right.head = nRight.childNodes[0];
704 this.dom.grid.right.body = nRight.childNodes[1];
705 this.dom.grid.right.liner = $('div.DTFC_RightBodyLiner', nSWrapper)[0];
707 block = $('div.DTFC_RightHeadBlocker', nSWrapper)[0];
708 block.style.width = oOverflow.bar+"px";
709 block.style.right = -oOverflow.bar+"px";
710 this.dom.grid.right.headBlock = block;
712 block = $('div.DTFC_RightFootBlocker', nSWrapper)[0];
713 block.style.width = oOverflow.bar+"px";
714 block.style.right = -oOverflow.bar+"px";
715 this.dom.grid.right.footBlock = block;
717 nSWrapper.appendChild( nRight );
720 if ( this.s.dt.nTFoot )
722 this.dom.footer = this.s.dt.nTFoot.parentNode;
723 if ( this.s.iLeftColumns > 0 )
725 this.dom.grid.left.foot = nLeft.childNodes[2];
727 if ( this.s.iRightColumns > 0 )
729 this.dom.grid.right.foot = nRight.childNodes[2];
736 * Style and position the grid used for the FixedColumns layout
740 "_fnGridLayout": function ()
742 var oGrid = this.dom.grid;
743 var iWidth = $(oGrid.wrapper).width();
744 var iBodyHeight = $(this.s.dt.nTable.parentNode).outerHeight();
745 var iFullHeight = $(this.s.dt.nTable.parentNode.parentNode).outerHeight();
746 var oOverflow = this._fnDTOverflow();
748 iLeftWidth = this.s.iLeftWidth,
749 iRightWidth = this.s.iRightWidth,
751 var scrollbarAdjust = function ( node, width ) {
752 if ( ! oOverflow.bar ) {
753 // If there is no scrollbar (Macs) we need to hide the auto scrollbar
754 node.style.width = (width+20)+"px";
755 node.style.paddingRight = "20px";
756 node.style.boxSizing = "border-box";
759 // Otherwise just overflow by the scrollbar
760 node.style.width = (width+oOverflow.bar)+"px";
764 // When x scrolling - don't paint the fixed columns over the x scrollbar
767 iBodyHeight -= oOverflow.bar;
770 oGrid.wrapper.style.height = iFullHeight+"px";
772 if ( this.s.iLeftColumns > 0 )
774 oGrid.left.wrapper.style.width = iLeftWidth+"px";
775 oGrid.left.wrapper.style.height = "1px";
776 oGrid.left.body.style.height = iBodyHeight+"px";
777 if ( oGrid.left.foot ) {
778 oGrid.left.foot.style.top = (oOverflow.x ? oOverflow.bar : 0)+"px"; // shift footer for scrollbar
781 scrollbarAdjust( oGrid.left.liner, iLeftWidth );
782 oGrid.left.liner.style.height = iBodyHeight+"px";
785 if ( this.s.iRightColumns > 0 )
787 iRight = iWidth - iRightWidth;
790 iRight -= oOverflow.bar;
793 oGrid.right.wrapper.style.width = iRightWidth+"px";
794 oGrid.right.wrapper.style.left = iRight+"px";
795 oGrid.right.wrapper.style.height = "1px";
796 oGrid.right.body.style.height = iBodyHeight+"px";
797 if ( oGrid.right.foot ) {
798 oGrid.right.foot.style.top = (oOverflow.x ? oOverflow.bar : 0)+"px";
801 scrollbarAdjust( oGrid.right.liner, iRightWidth );
802 oGrid.right.liner.style.height = iBodyHeight+"px";
804 oGrid.right.headBlock.style.display = oOverflow.y ? 'block' : 'none';
805 oGrid.right.footBlock.style.display = oOverflow.y ? 'block' : 'none';
811 * Get information about the DataTable's scrolling state - specifically if the table is scrolling
812 * on either the x or y axis, and also the scrollbar width.
813 * @returns {object} Information about the DataTables scrolling state with the properties:
817 "_fnDTOverflow": function ()
819 var nTable = this.s.dt.nTable;
820 var nTableScrollBody = nTable.parentNode;
824 "bar": this.s.dt.oScroll.iBarWidth
827 if ( nTable.offsetWidth > nTableScrollBody.clientWidth )
832 if ( nTable.offsetHeight > nTableScrollBody.clientHeight )
842 * Clone and position the fixed columns
844 * @param {Boolean} bAll Indicate if the header and footer should be updated as well (true)
847 "_fnDraw": function ( bAll )
849 this._fnGridLayout();
850 this._fnCloneLeft( bAll );
851 this._fnCloneRight( bAll );
853 /* Draw callback function */
854 if ( this.s.fnDrawCallback !== null )
856 this.s.fnDrawCallback.call( this, this.dom.clone.left, this.dom.clone.right );
859 /* Event triggering */
860 $(this).trigger( 'draw.dtfc', {
861 "leftClone": this.dom.clone.left,
862 "rightClone": this.dom.clone.right
868 * Clone the right columns
870 * @param {Boolean} bAll Indicate if the header and footer should be updated as well (true)
873 "_fnCloneRight": function ( bAll )
875 if ( this.s.iRightColumns <= 0 ) {
883 for ( i=this.s.iTableColumns-this.s.iRightColumns ; i<this.s.iTableColumns ; i++ ) {
884 if ( this.s.dt.aoColumns[i].bVisible ) {
889 this._fnClone( this.dom.clone.right, this.dom.grid.right, aiColumns, bAll );
894 * Clone the left columns
896 * @param {Boolean} bAll Indicate if the header and footer should be updated as well (true)
899 "_fnCloneLeft": function ( bAll )
901 if ( this.s.iLeftColumns <= 0 ) {
909 for ( i=0 ; i<this.s.iLeftColumns ; i++ ) {
910 if ( this.s.dt.aoColumns[i].bVisible ) {
915 this._fnClone( this.dom.clone.left, this.dom.grid.left, aiColumns, bAll );
920 * Make a copy of the layout object for a header or footer element from DataTables. Note that
921 * this method will clone the nodes in the layout object.
922 * @returns {Array} Copy of the layout array
923 * @param {Object} aoOriginal Layout array from DataTables (aoHeader or aoFooter)
924 * @param {Object} aiColumns Columns to copy
927 "_fnCopyLayout": function ( aoOriginal, aiColumns )
933 for ( var i=0, iLen=aoOriginal.length ; i<iLen ; i++ )
936 aRow.nTr = $(aoOriginal[i].nTr).clone(true, true)[0];
938 for ( var j=0, jLen=this.s.iTableColumns ; j<jLen ; j++ )
940 if ( $.inArray( j, aiColumns ) === -1 )
945 var iCloned = $.inArray( aoOriginal[i][j].cell, aCloned );
946 if ( iCloned === -1 )
948 var nClone = $(aoOriginal[i][j].cell).clone(true, true)[0];
949 aClones.push( nClone );
950 aCloned.push( aoOriginal[i][j].cell );
954 "unique": aoOriginal[i][j].unique
960 "cell": aClones[ iCloned ],
961 "unique": aoOriginal[i][j].unique
966 aReturn.push( aRow );
974 * Clone the DataTable nodes and place them in the DOM (sized correctly)
976 * @param {Object} oClone Object containing the header, footer and body cloned DOM elements
977 * @param {Object} oGrid Grid object containing the display grid elements for the cloned
978 * column (left or right)
979 * @param {Array} aiColumns Column indexes which should be operated on from the DataTable
980 * @param {Boolean} bAll Indicate if the header and footer should be updated as well (true)
983 "_fnClone": function ( oClone, oGrid, aiColumns, bAll )
986 i, iLen, j, jLen, jq, nTarget, iColumn, nClone, iIndex, aoCloneLayout,
987 jqCloneThead, aoFixedHeader,
995 if ( oClone.header !== null )
997 oClone.header.parentNode.removeChild( oClone.header );
999 oClone.header = $(this.dom.header).clone(true, true)[0];
1000 oClone.header.className += " DTFC_Cloned";
1001 oClone.header.style.width = "100%";
1002 oGrid.head.appendChild( oClone.header );
1004 /* Copy the DataTables layout cache for the header for our floating column */
1005 aoCloneLayout = this._fnCopyLayout( dt.aoHeader, aiColumns );
1006 jqCloneThead = $('>thead', oClone.header);
1007 jqCloneThead.empty();
1009 /* Add the created cloned TR elements to the table */
1010 for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
1012 jqCloneThead[0].appendChild( aoCloneLayout[i].nTr );
1015 /* Use the handy _fnDrawHead function in DataTables to do the rowspan/colspan
1016 * calculations for us
1018 dt.oApi._fnDrawHead( dt, aoCloneLayout, true );
1022 /* To ensure that we copy cell classes exactly, regardless of colspan, multiple rows
1023 * etc, we make a copy of the header from the DataTable again, but don't insert the
1024 * cloned cells, just copy the classes across. To get the matching layout for the
1025 * fixed component, we use the DataTables _fnDetectHeader method, allowing 1:1 mapping
1027 aoCloneLayout = this._fnCopyLayout( dt.aoHeader, aiColumns );
1030 dt.oApi._fnDetectHeader( aoFixedHeader, $('>thead', oClone.header)[0] );
1032 for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
1034 for ( j=0, jLen=aoCloneLayout[i].length ; j<jLen ; j++ )
1036 aoFixedHeader[i][j].cell.className = aoCloneLayout[i][j].cell.className;
1038 // If jQuery UI theming is used we need to copy those elements as well
1039 $('span.DataTables_sort_icon', aoFixedHeader[i][j].cell).each( function () {
1040 this.className = $('span.DataTables_sort_icon', aoCloneLayout[i][j].cell)[0].className;
1045 this._fnEqualiseHeights( 'thead', this.dom.header, oClone.header );
1050 if ( this.s.sHeightMatch == 'auto' )
1052 /* Remove any heights which have been applied already and let the browser figure it out */
1053 $('>tbody>tr', that.dom.body).css('height', 'auto');
1056 if ( oClone.body !== null )
1058 oClone.body.parentNode.removeChild( oClone.body );
1062 oClone.body = $(this.dom.body).clone(true)[0];
1063 oClone.body.className += " DTFC_Cloned";
1064 oClone.body.style.paddingBottom = dt.oScroll.iBarWidth+"px";
1065 oClone.body.style.marginBottom = (dt.oScroll.iBarWidth*2)+"px"; /* For IE */
1066 if ( oClone.body.getAttribute('id') !== null )
1068 oClone.body.removeAttribute('id');
1071 $('>thead>tr', oClone.body).empty();
1072 $('>tfoot', oClone.body).remove();
1074 var nBody = $('tbody', oClone.body)[0];
1076 if ( dt.aiDisplay.length > 0 )
1078 /* Copy the DataTables' header elements to force the column width in exactly the
1079 * same way that DataTables does it - have the header element, apply the width and
1082 var nInnerThead = $('>thead>tr', oClone.body)[0];
1083 for ( iIndex=0 ; iIndex<aiColumns.length ; iIndex++ )
1085 iColumn = aiColumns[iIndex];
1087 nClone = $(dt.aoColumns[iColumn].nTh).clone(true)[0];
1088 nClone.innerHTML = "";
1090 var oStyle = nClone.style;
1091 oStyle.paddingTop = "0";
1092 oStyle.paddingBottom = "0";
1093 oStyle.borderTopWidth = "0";
1094 oStyle.borderBottomWidth = "0";
1096 oStyle.width = that.s.aiInnerWidths[iColumn]+"px";
1098 nInnerThead.appendChild( nClone );
1101 /* Add in the tbody elements, cloning form the master table */
1102 $('>tbody>tr', that.dom.body).each( function (z) {
1103 var n = this.cloneNode(false);
1104 n.removeAttribute('id');
1105 var i = that.s.dt.oFeatures.bServerSide===false ?
1106 that.s.dt.aiDisplay[ that.s.dt._iDisplayStart+z ] : z;
1107 var aTds = that.s.dt.aoData[ i ].anCells || $(this).children('td, th');
1109 for ( iIndex=0 ; iIndex<aiColumns.length ; iIndex++ )
1111 iColumn = aiColumns[iIndex];
1113 if ( aTds.length > 0 )
1115 nClone = $( aTds[iColumn] ).clone(true, true)[0];
1116 n.appendChild( nClone );
1119 nBody.appendChild( n );
1124 $('>tbody>tr', that.dom.body).each( function (z) {
1125 nClone = this.cloneNode(true);
1126 nClone.className += ' DTFC_NoData';
1127 $('td', nClone).html('');
1128 nBody.appendChild( nClone );
1132 oClone.body.style.width = "100%";
1133 oClone.body.style.margin = "0";
1134 oClone.body.style.padding = "0";
1136 // Interop with Scroller - need to use a height forcing element in the
1137 // scrolling area in the same way that Scroller does in the body scroll.
1138 if ( dt.oScroller !== undefined )
1140 var scrollerForcer = dt.oScroller.dom.force;
1142 if ( ! oGrid.forcer ) {
1143 oGrid.forcer = scrollerForcer.cloneNode( true );
1144 oGrid.liner.appendChild( oGrid.forcer );
1147 oGrid.forcer.style.height = scrollerForcer.style.height;
1151 oGrid.liner.appendChild( oClone.body );
1153 this._fnEqualiseHeights( 'tbody', that.dom.body, oClone.body );
1158 if ( dt.nTFoot !== null )
1162 if ( oClone.footer !== null )
1164 oClone.footer.parentNode.removeChild( oClone.footer );
1166 oClone.footer = $(this.dom.footer).clone(true, true)[0];
1167 oClone.footer.className += " DTFC_Cloned";
1168 oClone.footer.style.width = "100%";
1169 oGrid.foot.appendChild( oClone.footer );
1171 /* Copy the footer just like we do for the header */
1172 aoCloneLayout = this._fnCopyLayout( dt.aoFooter, aiColumns );
1173 var jqCloneTfoot = $('>tfoot', oClone.footer);
1174 jqCloneTfoot.empty();
1176 for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
1178 jqCloneTfoot[0].appendChild( aoCloneLayout[i].nTr );
1180 dt.oApi._fnDrawHead( dt, aoCloneLayout, true );
1184 aoCloneLayout = this._fnCopyLayout( dt.aoFooter, aiColumns );
1185 var aoCurrFooter=[];
1187 dt.oApi._fnDetectHeader( aoCurrFooter, $('>tfoot', oClone.footer)[0] );
1189 for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
1191 for ( j=0, jLen=aoCloneLayout[i].length ; j<jLen ; j++ )
1193 aoCurrFooter[i][j].cell.className = aoCloneLayout[i][j].cell.className;
1197 this._fnEqualiseHeights( 'tfoot', this.dom.footer, oClone.footer );
1200 /* Equalise the column widths between the header footer and body - body get's priority */
1201 var anUnique = dt.oApi._fnGetUniqueThs( dt, $('>thead', oClone.header)[0] );
1202 $(anUnique).each( function (i) {
1203 iColumn = aiColumns[i];
1204 this.style.width = that.s.aiInnerWidths[iColumn]+"px";
1207 if ( that.s.dt.nTFoot !== null )
1209 anUnique = dt.oApi._fnGetUniqueThs( dt, $('>tfoot', oClone.footer)[0] );
1210 $(anUnique).each( function (i) {
1211 iColumn = aiColumns[i];
1212 this.style.width = that.s.aiInnerWidths[iColumn]+"px";
1219 * From a given table node (THEAD etc), get a list of TR direct child elements
1220 * @param {Node} nIn Table element to search for TR elements (THEAD, TBODY or TFOOT element)
1221 * @returns {Array} List of TR elements found
1224 "_fnGetTrNodes": function ( nIn )
1227 for ( var i=0, iLen=nIn.childNodes.length ; i<iLen ; i++ )
1229 if ( nIn.childNodes[i].nodeName.toUpperCase() == "TR" )
1231 aOut.push( nIn.childNodes[i] );
1239 * Equalise the heights of the rows in a given table node in a cross browser way
1241 * @param {String} nodeName Node type - thead, tbody or tfoot
1242 * @param {Node} original Original node to take the heights from
1243 * @param {Node} clone Copy the heights to
1246 "_fnEqualiseHeights": function ( nodeName, original, clone )
1248 if ( this.s.sHeightMatch == 'none' && nodeName !== 'thead' && nodeName !== 'tfoot' )
1254 i, iLen, iHeight, iHeight2, iHeightOriginal, iHeightClone,
1255 rootOriginal = original.getElementsByTagName(nodeName)[0],
1256 rootClone = clone.getElementsByTagName(nodeName)[0],
1257 jqBoxHack = $('>'+nodeName+'>tr:eq(0)', original).children(':first'),
1258 iBoxHack = jqBoxHack.outerHeight() - jqBoxHack.height(),
1259 anOriginal = this._fnGetTrNodes( rootOriginal ),
1260 anClone = this._fnGetTrNodes( rootClone ),
1263 for ( i=0, iLen=anClone.length ; i<iLen ; i++ )
1265 iHeightOriginal = anOriginal[i].offsetHeight;
1266 iHeightClone = anClone[i].offsetHeight;
1267 iHeight = iHeightClone > iHeightOriginal ? iHeightClone : iHeightOriginal;
1269 if ( this.s.sHeightMatch == 'semiauto' )
1271 anOriginal[i]._DTTC_iHeight = iHeight;
1274 heights.push( iHeight );
1277 for ( i=0, iLen=anClone.length ; i<iLen ; i++ )
1279 anClone[i].style.height = heights[i]+"px";
1280 anOriginal[i].style.height = heights[i]+"px";
1287 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1289 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1292 * FixedColumns default settings for initialisation
1293 * @name FixedColumns.defaults
1297 FixedColumns.defaults = /** @lends FixedColumns.defaults */{
1299 * Number of left hand columns to fix in position
1304 * var = $('#example').dataTable( {
1307 * new $.fn.dataTable.fixedColumns( table, {
1314 * Number of right hand columns to fix in position
1319 * var table = $('#example').dataTable( {
1322 * new $.fn.dataTable.fixedColumns( table, {
1329 * Draw callback function which is called when FixedColumns has redrawn the fixed assets
1330 * @type function(object, object):void
1334 * var table = $('#example').dataTable( {
1337 * new $.fn.dataTable.fixedColumns( table, {
1338 * "drawCallback": function () {
1339 * alert( "FixedColumns redraw" );
1343 "fnDrawCallback": null,
1346 * Height matching algorthim to use. This can be "none" which will result in no height
1347 * matching being applied by FixedColumns (height matching could be forced by CSS in this
1348 * case), "semiauto" whereby the height calculation will be performed once, and the result
1349 * cached to be used again (fnRecalculateHeight can be used to force recalculation), or
1350 * "auto" when height matching is performed on every draw (slowest but must accurate)
1355 * var table = $('#example').dataTable( {
1358 * new $.fn.dataTable.fixedColumns( table, {
1359 * "heightMatch": "auto"
1362 "sHeightMatch": "semiauto"
1368 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1370 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1373 * FixedColumns version
1374 * @name FixedColumns.version
1379 FixedColumns.version = "3.0.4";
1383 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1384 * Fired events (for documentation)
1385 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1389 * Event fired whenever FixedColumns redraws the fixed columns (i.e. clones the table elements from the main DataTable). This will occur whenever the DataTable that the FixedColumns instance is attached does its own draw.
1390 * @name FixedColumns#draw.dtfc
1392 * @param {event} e jQuery event object
1393 * @param {object} o Event parameters from FixedColumns
1394 * @param {object} o.leftClone Instance's object dom.clone.left for easy reference. This object contains references to the left fixed clumn column's nodes
1395 * @param {object} o.rightClone Instance's object dom.clone.right for easy reference. This object contains references to the right fixed clumn column's nodes
1399 // Make FixedColumns accessible from the DataTables instance
1400 $.fn.dataTable.FixedColumns = FixedColumns;
1401 $.fn.DataTable.FixedColumns = FixedColumns;
1404 return FixedColumns;
1408 // Define as an AMD module if possible
1409 if ( typeof define === 'function' && define.amd ) {
1410 define( ['jquery', 'datatables'], factory );
1412 else if ( typeof exports === 'object' ) {
1414 factory( require('jquery'), require('datatables') );
1416 else if ( jQuery && !jQuery.fn.dataTable.FixedColumns ) {
1417 // Otherwise simply initialise as normal, stopping multiple evaluation
1418 factory( jQuery, jQuery.fn.dataTable );
1422 })(window, document);