dataTables.FixedColumns.js 32 KB

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