Add datatables-1.9.4 and jquery-1.10.2 libraries
[proteocache.git] / webapp / resources / datatables-1.9.4 / extras / FixedColumns / media / js / FixedColumns.js
1 /**
2  * @summary     FixedColumns
3  * @description Freeze columns in place on a scrolling DataTable
4  * @file        FixedColumns.js
5  * @version     2.0.3
6  * @author      Allan Jardine (www.sprymedia.co.uk)
7  * @license     GPL v2 or BSD 3 point style
8  * @contact     www.sprymedia.co.uk/contact
9  *
10  * @copyright Copyright 2010-2011 Allan Jardine, all rights reserved.
11  *
12  * This source file is free software, under either the GPL v2 license or a
13  * BSD style license, available at:
14  *   http://datatables.net/license_gpl2
15  *   http://datatables.net/license_bsd
16  */
17
18
19 /* Global scope for FixedColumns */
20 var FixedColumns;
21
22 (function($, window, document) {
23
24
25 /** 
26  * When making use of DataTables' x-axis scrolling feature, you may wish to 
27  * fix the left most column in place. This plug-in for DataTables provides 
28  * exactly this option (note for non-scrolling tables, please use the  
29  * FixedHeader plug-in, which can fix headers, footers and columns). Key 
30  * features include:
31  *   <ul class="limit_length">
32  *     <li>Freezes the left or right most columns to the side of the table</li>
33  *     <li>Option to freeze two or more columns</li>
34  *     <li>Full integration with DataTables' scrolling options</li>
35  *     <li>Speed - FixedColumns is fast in its operation</li>
36  *   </ul>
37  *
38  *  @class
39  *  @constructor
40  *  @param {object} oDT DataTables instance
41  *  @param {object} [oInit={}] Configuration object for FixedColumns. Options are defined by {@link FixedColumns.defaults}
42  * 
43  *  @requires jQuery 1.3+
44  *  @requires DataTables 1.8.0+
45  * 
46  *  @example
47  *      var oTable = $('#example').dataTable( {
48  *              "sScrollX": "100%"
49  *      } );
50  *      new FixedColumns( oTable );
51  */
52 FixedColumns = function ( oDT, oInit ) {
53         /* Sanity check - you just know it will happen */
54         if ( ! this instanceof FixedColumns )
55         {
56                 alert( "FixedColumns warning: FixedColumns must be initialised with the 'new' keyword." );
57                 return;
58         }
59         
60         if ( typeof oInit == 'undefined' )
61         {
62                 oInit = {};
63         }
64         
65         /**
66          * Settings object which contains customisable information for FixedColumns instance
67          * @namespace
68          * @extends FixedColumns.defaults
69          */
70         this.s = {
71                 /** 
72                  * DataTables settings objects
73                  *  @type     object
74                  *  @default  Obtained from DataTables instance
75                  */
76                 "dt": oDT.fnSettings(),
77                 
78                 /** 
79                  * Number of columns in the DataTable - stored for quick access
80                  *  @type     int
81                  *  @default  Obtained from DataTables instance
82                  */
83                 "iTableColumns": oDT.fnSettings().aoColumns.length,
84                 
85                 /** 
86                  * Original widths of the columns as rendered by DataTables
87                  *  @type     array.<int>
88                  *  @default  []
89                  */
90                 "aiWidths": [],
91                 
92                 /** 
93                  * Flag to indicate if we are dealing with IE6/7 as these browsers need a little hack
94                  * in the odd place
95                  *  @type     boolean
96                  *  @default  Automatically calculated
97                  *  @readonly
98                  */
99                 "bOldIE": ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0"))
100         };
101         
102         
103         /**
104          * DOM elements used by the class instance
105          * @namespace
106          * 
107          */
108         this.dom = {
109                 /**
110                  * DataTables scrolling element
111                  *  @type     node
112                  *  @default  null
113                  */
114                 "scroller": null,
115                 
116                 /**
117                  * DataTables header table
118                  *  @type     node
119                  *  @default  null
120                  */
121                 "header": null,
122                 
123                 /**
124                  * DataTables body table
125                  *  @type     node
126                  *  @default  null
127                  */
128                 "body": null,
129                 
130                 /**
131                  * DataTables footer table
132                  *  @type     node
133                  *  @default  null
134                  */
135                 "footer": null,
136
137                 /**
138                  * Display grid elements
139                  * @namespace
140                  */
141                 "grid": {
142                         /**
143                          * Grid wrapper. This is the container element for the 3x3 grid
144                          *  @type     node
145                          *  @default  null
146                          */
147                         "wrapper": null,
148
149                         /**
150                          * DataTables scrolling element. This element is the DataTables
151                          * component in the display grid (making up the main table - i.e.
152                          * not the fixed columns).
153                          *  @type     node
154                          *  @default  null
155                          */
156                         "dt": null,
157
158                         /**
159                          * Left fixed column grid components
160                          * @namespace
161                          */
162                         "left": {
163                                 "wrapper": null,
164                                 "head": null,
165                                 "body": null,
166                                 "foot": null
167                         },
168
169                         /**
170                          * Right fixed column grid components
171                          * @namespace
172                          */
173                         "right": {
174                                 "wrapper": null,
175                                 "head": null,
176                                 "body": null,
177                                 "foot": null
178                         }
179                 },
180                 
181                 /**
182                  * Cloned table nodes
183                  * @namespace
184                  */
185                 "clone": {
186                         /**
187                          * Left column cloned table nodes
188                          * @namespace
189                          */
190                         "left": {
191                                 /**
192                                  * Cloned header table
193                                  *  @type     node
194                                  *  @default  null
195                                  */
196                                 "header": null,
197                         
198                                 /**
199                                  * Cloned body table
200                                  *  @type     node
201                                  *  @default  null
202                                  */
203                                 "body": null,
204                         
205                                 /**
206                                  * Cloned footer table
207                                  *  @type     node
208                                  *  @default  null
209                                  */
210                                 "footer": null
211                         },
212                         
213                         /**
214                          * Right column cloned table nodes
215                          * @namespace
216                          */
217                         "right": {
218                                 /**
219                                  * Cloned header table
220                                  *  @type     node
221                                  *  @default  null
222                                  */
223                                 "header": null,
224                         
225                                 /**
226                                  * Cloned body table
227                                  *  @type     node
228                                  *  @default  null
229                                  */
230                                 "body": null,
231                         
232                                 /**
233                                  * Cloned footer table
234                                  *  @type     node
235                                  *  @default  null
236                                  */
237                                 "footer": null
238                         }
239                 }
240         };
241
242         /* Attach the instance to the DataTables instance so it can be accessed easily */
243         this.s.dt.oFixedColumns = this;
244         
245         /* Let's do it */
246         this._fnConstruct( oInit );
247 };
248
249
250
251 FixedColumns.prototype = {
252         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
253          * Public methods
254          * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
255         
256         /**
257          * Update the fixed columns - including headers and footers. Note that FixedColumns will
258          * automatically update the display whenever the host DataTable redraws.
259          *  @returns {void}
260          *  @example
261          *      var oTable = $('#example').dataTable( {
262          *              "sScrollX": "100%"
263          *      } );
264          *      var oFC = new FixedColumns( oTable );
265          *      
266          *      // at some later point when the table has been manipulated....
267          *      oFC.fnUpdate();
268          */
269         "fnUpdate": function ()
270         {
271                 this._fnDraw( true );
272         },
273         
274         
275         /**
276          * Recalculate the resizes of the 3x3 grid that FixedColumns uses for display of the table.
277          * This is useful if you update the width of the table container. Note that FixedColumns will
278          * perform this function automatically when the window.resize event is fired.
279          *  @returns {void}
280          *  @example
281          *      var oTable = $('#example').dataTable( {
282          *              "sScrollX": "100%"
283          *      } );
284          *      var oFC = new FixedColumns( oTable );
285          *      
286          *      // Resize the table container and then have FixedColumns adjust its layout....
287          *      $('#content').width( 1200 );
288          *      oFC.fnRedrawLayout();
289          */
290         "fnRedrawLayout": function ()
291         {
292                 this._fnGridLayout();
293         },
294         
295         
296         /**
297          * Mark a row such that it's height should be recalculated when using 'semiauto' row
298          * height matching. This function will have no effect when 'none' or 'auto' row height
299          * matching is used.
300          *  @param   {Node} nTr TR element that should have it's height recalculated
301          *  @returns {void}
302          *  @example
303          *      var oTable = $('#example').dataTable( {
304          *              "sScrollX": "100%"
305          *      } );
306          *      var oFC = new FixedColumns( oTable );
307          *      
308          *      // manipulate the table - mark the row as needing an update then update the table
309          *      // this allows the redraw performed by DataTables fnUpdate to recalculate the row
310          *      // height
311          *      oFC.fnRecalculateHeight();
312          *      oTable.fnUpdate( $('#example tbody tr:eq(0)')[0], ["insert date", 1, 2, 3 ... ]);
313          */
314         "fnRecalculateHeight": function ( nTr )
315         {
316                 nTr._DTTC_iHeight = null;
317                 nTr.style.height = 'auto';
318         },
319         
320         
321         /**
322          * Set the height of a given row - provides cross browser compatibility
323          *  @param   {Node} nTarget TR element that should have it's height recalculated
324          *  @param   {int} iHeight Height in pixels to set
325          *  @returns {void}
326          *  @example
327          *      var oTable = $('#example').dataTable( {
328          *              "sScrollX": "100%"
329          *      } );
330          *      var oFC = new FixedColumns( oTable );
331          *      
332          *      // You may want to do this after manipulating a row in the fixed column
333          *      oFC.fnSetRowHeight( $('#example tbody tr:eq(0)')[0], 50 );
334          */
335         "fnSetRowHeight": function ( nTarget, iHeight )
336         {
337                 var jqBoxHack = $(nTarget).children(':first');
338                 var iBoxHack = jqBoxHack.outerHeight() - jqBoxHack.height();
339
340                 /* Can we use some kind of object detection here?! This is very nasty - damn browsers */
341                 if ( $.browser.mozilla || $.browser.opera )
342                 {
343                         nTarget.style.height = iHeight+"px";
344                 }
345                 else
346                 {
347                         $(nTarget).children().height( iHeight-iBoxHack );
348                 }
349         },
350         
351         
352         
353         /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
354          * Private methods (they are of course public in JS, but recommended as private)
355          * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
356         
357         /**
358          * Initialisation for FixedColumns
359          *  @param   {Object} oInit User settings for initialisation
360          *  @returns {void}
361          *  @private
362          */
363         "_fnConstruct": function ( oInit )
364         {
365                 var i, iLen, iWidth,
366                         that = this;
367                 
368                 /* Sanity checking */
369                 if ( typeof this.s.dt.oInstance.fnVersionCheck != 'function' ||
370                      this.s.dt.oInstance.fnVersionCheck( '1.8.0' ) !== true )
371                 {
372                         alert( "FixedColumns "+FixedColumns.VERSION+" required DataTables 1.8.0 or later. "+
373                                 "Please upgrade your DataTables installation" );
374                         return;
375                 }
376                 
377                 if ( this.s.dt.oScroll.sX === "" )
378                 {
379                         this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "FixedColumns is not needed (no "+
380                                 "x-scrolling in DataTables enabled), so no action will be taken. Use 'FixedHeader' for "+
381                                 "column fixing when scrolling is not enabled" );
382                         return;
383                 }
384                 
385                 /* Apply the settings from the user / defaults */
386                 this.s = $.extend( true, this.s, FixedColumns.defaults, oInit );
387
388                 /* Set up the DOM as we need it and cache nodes */
389                 this.dom.grid.dt = $(this.s.dt.nTable).parents('div.dataTables_scroll')[0];
390                 this.dom.scroller = $('div.dataTables_scrollBody', this.dom.grid.dt )[0];
391
392                 var iScrollWidth = $(this.dom.grid.dt).width();
393                 var iLeftWidth = 0;
394                 var iRightWidth = 0;
395
396                 $('tbody>tr:eq(0)>td', this.s.dt.nTable).each( function (i) {
397                         iWidth = $(this).outerWidth();
398                         that.s.aiWidths.push( iWidth );
399                         if ( i < that.s.iLeftColumns )
400                         {
401                                 iLeftWidth += iWidth;
402                         }
403                         if ( that.s.iTableColumns-that.s.iRightColumns <= i )
404                         {
405                                 iRightWidth += iWidth;
406                         }
407                 } );
408
409                 if ( this.s.iLeftWidth === null )
410                 {
411                         this.s.iLeftWidth = this.s.sLeftWidth == 'fixed' ?
412                                 iLeftWidth : (iLeftWidth/iScrollWidth) * 100; 
413                 }
414                 
415                 if ( this.s.iRightWidth === null )
416                 {
417                         this.s.iRightWidth = this.s.sRightWidth == 'fixed' ?
418                                 iRightWidth : (iRightWidth/iScrollWidth) * 100;
419                 }
420                 
421                 /* Set up the DOM that we want for the fixed column layout grid */
422                 this._fnGridSetup();
423
424                 /* Use the DataTables API method fnSetColumnVis to hide the columns we are going to fix */
425                 for ( i=0 ; i<this.s.iLeftColumns ; i++ )
426                 {
427                         this.s.dt.oInstance.fnSetColumnVis( i, false );
428                 }
429                 for ( i=this.s.iTableColumns - this.s.iRightColumns ; i<this.s.iTableColumns ; i++ )
430                 {
431                         this.s.dt.oInstance.fnSetColumnVis( i, false );
432                 }
433
434                 /* Event handlers */
435                 $(this.dom.scroller).scroll( function () {
436                         that.dom.grid.left.body.scrollTop = that.dom.scroller.scrollTop;
437                         if ( that.s.iRightColumns > 0 )
438                         {
439                                 that.dom.grid.right.body.scrollTop = that.dom.scroller.scrollTop;
440                         }
441                 } );
442
443                 $(window).resize( function () {
444                         that._fnGridLayout.call( that );
445                 } );
446                 
447                 var bFirstDraw = true;
448                 this.s.dt.aoDrawCallback = [ {
449                         "fn": function () {
450                                 that._fnDraw.call( that, bFirstDraw );
451                                 that._fnGridHeight( that );
452                                 bFirstDraw = false;
453                         },
454                         "sName": "FixedColumns"
455                 } ].concat( this.s.dt.aoDrawCallback );
456                 
457                 /* Get things right to start with - note that due to adjusting the columns, there must be
458                  * another redraw of the main table. It doesn't need to be a full redraw however.
459                  */
460                 this._fnGridLayout();
461                 this._fnGridHeight();
462                 this.s.dt.oInstance.fnDraw(false);
463         },
464         
465         
466         /**
467          * Set up the DOM for the fixed column. The way the layout works is to create a 1x3 grid
468          * for the left column, the DataTable (for which we just reuse the scrolling element DataTable
469          * puts into the DOM) and the right column. In each of he two fixed column elements there is a
470          * grouping wrapper element and then a head, body and footer wrapper. In each of these we then
471          * place the cloned header, body or footer tables. This effectively gives as 3x3 grid structure.
472          *  @returns {void}
473          *  @private
474          */
475         "_fnGridSetup": function ()
476         {
477                 var that = this;
478
479                 this.dom.body = this.s.dt.nTable;
480                 this.dom.header = this.s.dt.nTHead.parentNode;
481                 this.dom.header.parentNode.parentNode.style.position = "relative";
482                 
483                 var nSWrapper = 
484                         $('<div class="DTFC_ScrollWrapper" style="position:relative; clear:both;">'+
485                                 '<div class="DTFC_LeftWrapper" style="position:absolute; top:0; left:0;">'+
486                                         '<div class="DTFC_LeftHeadWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
487                                         '<div class="DTFC_LeftBodyWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
488                                         '<div class="DTFC_LeftFootWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
489                                 '</div>'+
490                                 '<div class="DTFC_RightWrapper" style="position:absolute; top:0; left:0;">'+
491                                         '<div class="DTFC_RightHeadWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
492                                         '<div class="DTFC_RightBodyWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
493                                         '<div class="DTFC_RightFootWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
494                                 '</div>'+
495                           '</div>')[0];
496                 nLeft = nSWrapper.childNodes[0];
497                 nRight = nSWrapper.childNodes[1];
498
499                 this.dom.grid.wrapper = nSWrapper;
500                 this.dom.grid.left.wrapper = nLeft;
501                 this.dom.grid.left.head = nLeft.childNodes[0];
502                 this.dom.grid.left.body = nLeft.childNodes[1];
503
504                 if ( this.s.iRightColumns > 0 )
505                 {
506                         this.dom.grid.right.wrapper = nRight;
507                         this.dom.grid.right.head = nRight.childNodes[0];
508                         this.dom.grid.right.body = nRight.childNodes[1];
509                 }
510                 
511                 if ( this.s.dt.nTFoot )
512                 {
513                         this.dom.footer = this.s.dt.nTFoot.parentNode;
514                         this.dom.grid.left.foot = nLeft.childNodes[2];
515                         if ( this.s.iRightColumns > 0 )
516                         {
517                                 this.dom.grid.right.foot = nRight.childNodes[2];
518                         }
519                 }
520
521                 nSWrapper.appendChild( nLeft );
522                 this.dom.grid.dt.parentNode.insertBefore( nSWrapper, this.dom.grid.dt );
523                 nSWrapper.appendChild( this.dom.grid.dt );
524
525                 this.dom.grid.dt.style.position = "absolute";
526                 this.dom.grid.dt.style.top = "0px";
527                 this.dom.grid.dt.style.left = this.s.iLeftWidth+"px";
528                 this.dom.grid.dt.style.width = ($(this.dom.grid.dt).width()-this.s.iLeftWidth-this.s.iRightWidth)+"px";
529         },
530         
531         
532         /**
533          * Style and position the grid used for the FixedColumns layout based on the instance settings.
534          * Specifically sLeftWidth ('fixed' or 'absolute'), iLeftWidth (px if fixed, % if absolute) and
535          * there 'right' counterparts.
536          *  @returns {void}
537          *  @private
538          */
539         "_fnGridLayout": function ()
540         {
541                 var oGrid = this.dom.grid;
542                 var iTotal = $(oGrid.wrapper).width();
543                 var iLeft = 0, iRight = 0, iRemainder = 0;
544
545                 if ( this.s.sLeftWidth == 'fixed' )
546                 {
547                         iLeft = this.s.iLeftWidth;
548                 }
549                 else
550                 {
551                         iLeft = ( this.s.iLeftWidth / 100 ) * iTotal;
552                 }
553
554                 if ( this.s.sRightWidth == 'fixed' )
555                 {
556                         iRight = this.s.iRightWidth;
557                 }
558                 else
559                 {
560                         iRight = ( this.s.iRightWidth / 100 ) * iTotal;
561                 }
562
563                 iRemainder = iTotal - iLeft - iRight;
564
565                 oGrid.left.wrapper.style.width = iLeft+"px";
566                 oGrid.dt.style.width = iRemainder+"px";
567                 oGrid.dt.style.left = iLeft+"px";
568
569                 if ( this.s.iRightColumns > 0 )
570                 {
571                         oGrid.right.wrapper.style.width = iRight+"px";
572                         oGrid.right.wrapper.style.left = (iTotal-iRight)+"px";
573                 }
574         },
575         
576         
577         /**
578          * Recalculate and set the height of the grid components used for positioning of the 
579          * FixedColumn display grid.
580          *  @returns {void}
581          *  @private
582          */
583         "_fnGridHeight": function ()
584         {
585                 var oGrid = this.dom.grid;
586                 var iHeight = $(this.dom.grid.dt).height();
587
588                 oGrid.wrapper.style.height = iHeight+"px";
589                 oGrid.left.body.style.height = $(this.dom.scroller).height()+"px";
590                 oGrid.left.wrapper.style.height = iHeight+"px";
591                 
592                 if ( this.s.iRightColumns > 0 )
593                 {
594                         oGrid.right.wrapper.style.height = iHeight+"px";
595                         oGrid.right.body.style.height = $(this.dom.scroller).height()+"px";
596                 }
597         },
598         
599         
600         /**
601          * Clone and position the fixed columns
602          *  @returns {void}
603          *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
604          *  @private
605          */
606         "_fnDraw": function ( bAll )
607         {
608                 this._fnCloneLeft( bAll );
609                 this._fnCloneRight( bAll );
610
611                 /* Draw callback function */
612                 if ( this.s.fnDrawCallback !== null )
613                 {
614                         this.s.fnDrawCallback.call( this, this.dom.clone.left, this.dom.clone.right );
615                 }
616
617                 /* Event triggering */
618                 $(this).trigger( 'draw', { 
619                         "leftClone": this.dom.clone.left,
620                         "rightClone": this.dom.clone.right
621                 } );
622         },
623         
624         
625         /**
626          * Clone the right columns
627          *  @returns {void}
628          *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
629          *  @private
630          */
631         "_fnCloneRight": function ( bAll )
632         {
633                 if ( this.s.iRightColumns <= 0 )
634                 {
635                         return;
636                 }
637                 
638                 var that = this,
639                         i, jq,
640                         aiColumns = [];
641
642                 for ( i=this.s.iTableColumns-this.s.iRightColumns ; i<this.s.iTableColumns ; i++ )
643                 {
644                         aiColumns.push( i );
645                 }
646
647                 this._fnClone( this.dom.clone.right, this.dom.grid.right, aiColumns, bAll );
648         },
649         
650         
651         /**
652          * Clone the left columns
653          *  @returns {void}
654          *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
655          *  @private
656          */
657         "_fnCloneLeft": function ( bAll )
658         {
659                 if ( this.s.iLeftColumns <= 0 )
660                 {
661                         return;
662                 }
663                 
664                 var that = this,
665                         i, jq,
666                         aiColumns = [];
667                 
668                 for ( i=0 ; i<this.s.iLeftColumns ; i++ )
669                 {
670                         aiColumns.push( i );
671                 }
672
673                 this._fnClone( this.dom.clone.left, this.dom.grid.left, aiColumns, bAll );
674         },
675         
676         
677         /**
678          * Make a copy of the layout object for a header or footer element from DataTables. Note that
679          * this method will clone the nodes in the layout object.
680          *  @returns {Array} Copy of the layout array
681          *  @param   {Object} aoOriginal Layout array from DataTables (aoHeader or aoFooter)
682          *  @param   {Object} aiColumns Columns to copy
683          *  @private
684          */
685         "_fnCopyLayout": function ( aoOriginal, aiColumns )
686         {
687                 var aReturn = [];
688                 var aClones = [];
689                 var aCloned = [];
690
691                 for ( var i=0, iLen=aoOriginal.length ; i<iLen ; i++ )
692                 {
693                         var aRow = [];
694                         aRow.nTr = $(aoOriginal[i].nTr).clone(true)[0];
695
696                         for ( var j=0, jLen=this.s.iTableColumns ; j<jLen ; j++ )
697                         {
698                                 if ( $.inArray( j, aiColumns ) === -1 )
699                                 {
700                                         continue;
701                                 }
702
703                                 var iCloned = $.inArray( aoOriginal[i][j].cell, aCloned );
704                                 if ( iCloned === -1 )
705                                 {
706                                         var nClone = $(aoOriginal[i][j].cell).clone(true)[0];
707                                         aClones.push( nClone );
708                                         aCloned.push( aoOriginal[i][j].cell );
709
710                                         aRow.push( {
711                                                 "cell": nClone,
712                                                 "unique": aoOriginal[i][j].unique
713                                         } );
714                                 }
715                                 else
716                                 {
717                                         aRow.push( {
718                                                 "cell": aClones[ iCloned ],
719                                                 "unique": aoOriginal[i][j].unique
720                                         } );
721                                 }
722                         }
723                         
724                         aReturn.push( aRow );
725                 }
726
727                 return aReturn;
728         },
729         
730         
731         /**
732          * Clone the DataTable nodes and place them in the DOM (sized correctly)
733          *  @returns {void}
734          *  @param   {Object} oClone Object containing the header, footer and body cloned DOM elements
735          *  @param   {Object} oGrid Grid object containing the display grid elements for the cloned 
736          *                    column (left or right)
737          *  @param   {Array} aiColumns Column indexes which should be operated on from the DataTable
738          *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
739          *  @private
740          */
741         "_fnClone": function ( oClone, oGrid, aiColumns, bAll )
742         {
743                 var that = this,
744                         i, iLen, j, jLen, jq, nTarget, iColumn, nClone, iIndex;
745
746                 /* 
747                  * Header
748                  */
749                 if ( bAll )
750                 {
751                         if ( oClone.header !== null )
752                         {
753                                 oClone.header.parentNode.removeChild( oClone.header );
754                         }
755                         oClone.header = $(this.dom.header).clone(true)[0];
756                         oClone.header.className += " DTFC_Cloned";
757                         oClone.header.style.width = "100%";
758                         oGrid.head.appendChild( oClone.header );
759                         
760                         /* Copy the DataTables layout cache for the header for our floating column */
761                         var aoCloneLayout = this._fnCopyLayout( this.s.dt.aoHeader, aiColumns );
762                         var jqCloneThead = $('>thead', oClone.header);
763                         jqCloneThead.empty();
764
765                         /* Add the created cloned TR elements to the table */
766                         for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
767                         {
768                                 jqCloneThead[0].appendChild( aoCloneLayout[i].nTr );
769                         }
770
771                         /* Use the handy _fnDrawHead function in DataTables to do the rowspan/colspan
772                          * calculations for us
773                          */
774                         this.s.dt.oApi._fnDrawHead( this.s.dt, aoCloneLayout, true );
775                 }
776                 else
777                 {
778                         /* To ensure that we copy cell classes exactly, regardless of colspan, multiple rows
779                          * etc, we make a copy of the header from the DataTable again, but don't insert the 
780                          * cloned cells, just copy the classes across. To get the matching layout for the
781                          * fixed component, we use the DataTables _fnDetectHeader method, allowing 1:1 mapping
782                          */
783                         var aoCloneLayout = this._fnCopyLayout( this.s.dt.aoHeader, aiColumns );
784                         var aoCurrHeader=[];
785
786                         this.s.dt.oApi._fnDetectHeader( aoCurrHeader, $('>thead', oClone.header)[0] );
787
788                         for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
789                         {
790                                 for ( j=0, jLen=aoCloneLayout[i].length ; j<jLen ; j++ )
791                                 {
792                                         aoCurrHeader[i][j].cell.className = aoCloneLayout[i][j].cell.className;
793
794                                         // If jQuery UI theming is used we need to copy those elements as well
795                                         $('span.DataTables_sort_icon', aoCurrHeader[i][j].cell).each( function () {
796                                                 this.className = $('span.DataTables_sort_icon', aoCloneLayout[i][j].cell)[0].className;
797                                         } );
798                                 }
799                         }
800                 }
801                 this._fnEqualiseHeights( 'thead', this.dom.header, oClone.header );
802                 
803                 /* 
804                  * Body
805                  */
806                 if ( this.s.sHeightMatch == 'auto' )
807                 {
808                         /* Remove any heights which have been applied already and let the browser figure it out */
809                         $('>tbody>tr', that.dom.body).css('height', 'auto');
810                 }
811                 
812                 if ( oClone.body !== null )
813                 {
814                         oClone.body.parentNode.removeChild( oClone.body );
815                         oClone.body = null;
816                 }
817                 
818                 oClone.body = $(this.dom.body).clone(true)[0];
819                 oClone.body.className += " DTFC_Cloned";
820                 oClone.body.style.paddingBottom = this.s.dt.oScroll.iBarWidth+"px";
821                 oClone.body.style.marginBottom = (this.s.dt.oScroll.iBarWidth*2)+"px"; /* For IE */
822                 if ( oClone.body.getAttribute('id') !== null )
823                 {
824                         oClone.body.removeAttribute('id');
825                 }
826                 
827                 $('>thead>tr', oClone.body).empty();
828                 $('>tfoot', oClone.body).remove();
829                 
830                 var nBody = $('tbody', oClone.body)[0];
831                 $(nBody).empty();
832                 if ( this.s.dt.aiDisplay.length > 0 )
833                 {
834                         /* Copy the DataTables' header elements to force the column width in exactly the
835                          * same way that DataTables does it - have the header element, apply the width and
836                          * colapse it down
837                          */
838                         var nInnerThead = $('>thead>tr', oClone.body)[0];
839                         for ( iIndex=0 ; iIndex<aiColumns.length ; iIndex++ )
840                         {
841                                 iColumn = aiColumns[iIndex];
842
843                                 nClone = this.s.dt.aoColumns[iColumn].nTh;
844                                 nClone.innerHTML = "";
845
846                                 oStyle = nClone.style;
847                                 oStyle.paddingTop = "0";
848                                 oStyle.paddingBottom = "0";
849                                 oStyle.borderTopWidth = "0";
850                                 oStyle.borderBottomWidth = "0";
851                                 oStyle.height = 0;
852                                 oStyle.width = that.s.aiWidths[iColumn]+"px";
853
854                                 nInnerThead.appendChild( nClone );
855                         }
856
857                         /* Add in the tbody elements, cloning form the master table */
858                         $('>tbody>tr', that.dom.body).each( function (z) {
859                                 var n = this.cloneNode(false);
860                                 var i = that.s.dt.oFeatures.bServerSide===false ?
861                                         that.s.dt.aiDisplay[ that.s.dt._iDisplayStart+z ] : z;
862                                 for ( iIndex=0 ; iIndex<aiColumns.length ; iIndex++ )
863                                 {
864                                         iColumn = aiColumns[iIndex];
865                                         if ( typeof that.s.dt.aoData[i]._anHidden[iColumn] != 'undefined' )
866                                         {
867                                                 nClone = $(that.s.dt.aoData[i]._anHidden[iColumn]).clone(true)[0];
868                                                 n.appendChild( nClone );
869                                         }
870                                 }
871                                 nBody.appendChild( n );
872                         } );
873                 }
874                 else
875                 {
876                         $('>tbody>tr', that.dom.body).each( function (z) {
877                                 nClone = this.cloneNode(true);
878                                 nClone.className += ' DTFC_NoData';
879                                 $('td', nClone).html('');
880                                 nBody.appendChild( nClone );
881                         } );
882                 }
883                 
884                 oClone.body.style.width = "100%";
885                 oGrid.body.appendChild( oClone.body );
886
887                 this._fnEqualiseHeights( 'tbody', that.dom.body, oClone.body );
888                 
889                 /*
890                  * Footer
891                  */
892                 if ( this.s.dt.nTFoot !== null )
893                 {
894                         if ( bAll )
895                         {
896                                 if ( oClone.footer !== null )
897                                 {
898                                         oClone.footer.parentNode.removeChild( oClone.footer );
899                                 }
900                                 oClone.footer = $(this.dom.footer).clone(true)[0];
901                                 oClone.footer.className += " DTFC_Cloned";
902                                 oClone.footer.style.width = "100%";
903                                 oGrid.foot.appendChild( oClone.footer );
904
905                                 /* Copy the footer just like we do for the header */
906                                 var aoCloneLayout = this._fnCopyLayout( this.s.dt.aoFooter, aiColumns );
907                                 var jqCloneTfoot = $('>tfoot', oClone.footer);
908                                 jqCloneTfoot.empty();
909         
910                                 for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
911                                 {
912                                         jqCloneTfoot[0].appendChild( aoCloneLayout[i].nTr );
913                                 }
914                                 this.s.dt.oApi._fnDrawHead( this.s.dt, aoCloneLayout, true );
915                         }
916                         else
917                         {
918                                 var aoCloneLayout = this._fnCopyLayout( this.s.dt.aoFooter, aiColumns );
919                                 var aoCurrFooter=[];
920
921                                 this.s.dt.oApi._fnDetectHeader( aoCurrFooter, $('>tfoot', oClone.footer)[0] );
922
923                                 for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
924                                 {
925                                         for ( j=0, jLen=aoCloneLayout[i].length ; j<jLen ; j++ )
926                                         {
927                                                 aoCurrFooter[i][j].cell.className = aoCloneLayout[i][j].cell.className;
928                                         }
929                                 }
930                         }
931                         this._fnEqualiseHeights( 'tfoot', this.dom.footer, oClone.footer );
932                 }
933
934                 /* Equalise the column widths between the header footer and body - body get's priority */
935                 var anUnique = this.s.dt.oApi._fnGetUniqueThs( this.s.dt, $('>thead', oClone.header)[0] );
936                 $(anUnique).each( function (i) {
937                         iColumn = aiColumns[i];
938                         this.style.width = that.s.aiWidths[iColumn]+"px";
939                 } );
940
941                 if ( that.s.dt.nTFoot !== null )
942                 {
943                         anUnique = this.s.dt.oApi._fnGetUniqueThs( this.s.dt, $('>tfoot', oClone.footer)[0] );
944                         $(anUnique).each( function (i) {
945                                 iColumn = aiColumns[i];
946                                 this.style.width = that.s.aiWidths[iColumn]+"px";
947                         } );
948                 }
949         },
950         
951         
952         /**
953          * From a given table node (THEAD etc), get a list of TR direct child elements
954          *  @param   {Node} nIn Table element to search for TR elements (THEAD, TBODY or TFOOT element)
955          *  @returns {Array} List of TR elements found
956          *  @private
957          */
958         "_fnGetTrNodes": function ( nIn )
959         {
960                 var aOut = [];
961                 for ( var i=0, iLen=nIn.childNodes.length ; i<iLen ; i++ )
962                 {
963                         if ( nIn.childNodes[i].nodeName.toUpperCase() == "TR" )
964                         {
965                                 aOut.push( nIn.childNodes[i] );
966                         }
967                 }
968                 return aOut;
969         },
970
971         
972         /**
973          * Equalise the heights of the rows in a given table node in a cross browser way
974          *  @returns {void}
975          *  @param   {String} nodeName Node type - thead, tbody or tfoot
976          *  @param   {Node} original Original node to take the heights from
977          *  @param   {Node} clone Copy the heights to
978          *  @private
979          */
980         "_fnEqualiseHeights": function ( nodeName, original, clone )
981         {
982                 if ( this.s.sHeightMatch == 'none' && nodeName !== 'thead' && nodeName !== 'tfoot' )
983                 {
984                         return;
985                 }
986                 
987                 var that = this,
988                         i, iLen, iHeight, iHeight2, iHeightOriginal, iHeightClone,
989                         rootOriginal = original.getElementsByTagName(nodeName)[0],
990                         rootClone    = clone.getElementsByTagName(nodeName)[0],
991                         jqBoxHack    = $('>'+nodeName+'>tr:eq(0)', original).children(':first'),
992                         iBoxHack     = jqBoxHack.outerHeight() - jqBoxHack.height(),
993                         anOriginal   = this._fnGetTrNodes( rootOriginal ),
994                         anClone      = this._fnGetTrNodes( rootClone );
995                 
996                 for ( i=0, iLen=anClone.length ; i<iLen ; i++ )
997                 {
998                         if ( this.s.sHeightMatch == 'semiauto' && typeof anOriginal[i]._DTTC_iHeight != 'undefined' && 
999                                 anOriginal[i]._DTTC_iHeight !== null )
1000                         {
1001                                 /* Oddly enough, IE / Chrome seem not to copy the style height - Mozilla and Opera keep it */
1002                                 if ( $.browser.msie )
1003                                 {
1004                                         $(anClone[i]).children().height( anOriginal[i]._DTTC_iHeight-iBoxHack );
1005                                 }
1006                                 continue;
1007                         }
1008                         
1009                         iHeightOriginal = anOriginal[i].offsetHeight;
1010                         iHeightClone = anClone[i].offsetHeight;
1011                         iHeight = iHeightClone > iHeightOriginal ? iHeightClone : iHeightOriginal;
1012                         
1013                         if ( this.s.sHeightMatch == 'semiauto' )
1014                         {
1015                                 anOriginal[i]._DTTC_iHeight = iHeight;
1016                         }
1017                         
1018                         /* Can we use some kind of object detection here?! This is very nasty - damn browsers */
1019                         if ( $.browser.msie && $.browser.version < 8 )
1020                         {
1021                                 $(anClone[i]).children().height( iHeight-iBoxHack );
1022                                 $(anOriginal[i]).children().height( iHeight-iBoxHack ); 
1023                         }
1024                         else
1025                         {
1026                                 anClone[i].style.height = iHeight+"px";
1027                                 anOriginal[i].style.height = iHeight+"px";
1028                         }
1029                 }
1030         }
1031 };
1032
1033
1034
1035 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1036  * Statics
1037  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1038
1039
1040 /**
1041  * FixedColumns default settings for initialisation
1042  *  @namespace
1043  *  @static
1044  */
1045 FixedColumns.defaults = {
1046         /** 
1047          * Number of left hand columns to fix in position
1048          *  @type     int
1049          *  @default  1
1050          *  @static
1051          *  @example
1052          *      var oTable = $('#example').dataTable( {
1053          *              "sScrollX": "100%"
1054          *      } );
1055          *      new FixedColumns( oTable, {
1056          *              "iLeftColumns": 2
1057          *      } );
1058          */
1059         "iLeftColumns": 1,
1060         
1061         /** 
1062          * Number of right hand columns to fix in position
1063          *  @type     int
1064          *  @default  0
1065          *  @static
1066          *  @example
1067          *      var oTable = $('#example').dataTable( {
1068          *              "sScrollX": "100%"
1069          *      } );
1070          *      new FixedColumns( oTable, {
1071          *              "iRightColumns": 1
1072          *      } );
1073          */
1074         "iRightColumns": 0,
1075         
1076         /** 
1077          * Draw callback function which is called when FixedColumns has redrawn the fixed assets
1078          *  @type     function(object, object):void
1079          *  @default  null
1080          *  @static
1081          *  @example
1082          *      var oTable = $('#example').dataTable( {
1083          *              "sScrollX": "100%"
1084          *      } );
1085          *      new FixedColumns( oTable, {
1086          *              "fnDrawCallback": function () {
1087          *                              alert( "FixedColumns redraw" );
1088          *                      }
1089          *      } );
1090          */
1091         "fnDrawCallback": null,
1092         
1093         /** 
1094          * Type of left column size calculation. Can take the values of "fixed", whereby the iLeftWidth
1095          * value will be treated as a pixel value, or "relative" for which case iLeftWidth will be
1096          * treated as a percentage value.
1097          *  @type     string
1098          *  @default  fixed
1099          *  @static
1100          *  @example
1101          *      var oTable = $('#example').dataTable( {
1102          *              "sScrollX": "100%"
1103          *      } );
1104          *      new FixedColumns( oTable, {
1105          *              "sLeftWidth": "relative",
1106          *              "iLeftWidth": 10 // percentage
1107          *      } );
1108          */
1109         "sLeftWidth": "fixed",
1110         
1111         /** 
1112          * Width to set for the width of the left fixed column(s) - note that the behaviour of this
1113          * property is directly effected by the sLeftWidth property. If not defined then this property
1114          * is calculated automatically from what has been assigned by DataTables.
1115          *  @type     int
1116          *  @default  null
1117          *  @static
1118          *  @example
1119          *      var oTable = $('#example').dataTable( {
1120          *              "sScrollX": "100%"
1121          *      } );
1122          *      new FixedColumns( oTable, {
1123          *              "iLeftWidth": 100 // pixels
1124          *      } );
1125          */
1126         "iLeftWidth": null,
1127         
1128         /** 
1129          * Type of right column size calculation. Can take the values of "fixed", whereby the 
1130          * iRightWidth value will be treated as a pixel value, or "relative" for which case 
1131          * iRightWidth will be treated as a percentage value.
1132          *  @type     string
1133          *  @default  fixed
1134          *  @static
1135          *  @example
1136          *      var oTable = $('#example').dataTable( {
1137          *              "sScrollX": "100%"
1138          *      } );
1139          *      new FixedColumns( oTable, {
1140          *              "sRightWidth": "relative",
1141          *              "iRightWidth": 10 // percentage
1142          *      } );
1143          */
1144         "sRightWidth": "fixed",
1145         
1146         /**
1147          * Width to set for the width of the right fixed column(s) - note that the behaviour of this
1148          * property is directly effected by the sRightWidth property. If not defined then this property
1149          * is calculated automatically from what has been assigned by DataTables.
1150          *  @type     int
1151          *  @default  null
1152          *  @static
1153          *  @example
1154          *      var oTable = $('#example').dataTable( {
1155          *              "sScrollX": "100%"
1156          *      } );
1157          *      new FixedColumns( oTable, {
1158          *              "iRightWidth": 200 // pixels
1159          *      } );
1160          */
1161         "iRightWidth": null,
1162         
1163         /** 
1164          * Height matching algorthim to use. This can be "none" which will result in no height
1165          * matching being applied by FixedColumns (height matching could be forced by CSS in this
1166          * case), "semiauto" whereby the height calculation will be performed once, and the result
1167          * cached to be used again (fnRecalculateHeight can be used to force recalculation), or
1168          * "auto" when height matching is performed on every draw (slowest but must accurate)
1169          *  @type     string
1170          *  @default  semiauto
1171          *  @static
1172          *  @example
1173          *      var oTable = $('#example').dataTable( {
1174          *              "sScrollX": "100%"
1175          *      } );
1176          *      new FixedColumns( oTable, {
1177          *              "sHeightMatch": "auto"
1178          *      } );
1179          */
1180         "sHeightMatch": "semiauto"
1181 };
1182
1183
1184
1185
1186 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1187  * Constants
1188  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1189
1190
1191 /**
1192  * Name of this class
1193  *  @constant CLASS
1194  *  @type     String
1195  *  @default  FixedColumns
1196  */
1197 FixedColumns.prototype.CLASS = "FixedColumns";
1198
1199
1200 /**
1201  * FixedColumns version
1202  *  @constant  FixedColumns.VERSION
1203  *  @type      String
1204  *  @default   See code
1205  *  @static
1206  */
1207 FixedColumns.VERSION = "2.0.3";
1208
1209
1210
1211 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1212  * Fired events (for documentation)
1213  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1214
1215
1216 /**
1217  * 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.
1218  * @name FixedColumns#draw
1219  * @event
1220  * @param {event} e jQuery event object
1221  * @param {object} o Event parameters from FixedColumns
1222  * @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
1223  * @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
1224  */
1225
1226 })(jQuery, window, document);