dataTables.fixedHeader.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028
  1. /*! FixedHeader 2.1.2
  2. * ©2010-2014 SpryMedia Ltd - datatables.net/license
  3. */
  4. /**
  5. * @summary FixedHeader
  6. * @description Fix a table's header or footer, so it is always visible while
  7. * Scrolling
  8. * @version 2.1.2
  9. * @file dataTables.fixedHeader.js
  10. * @author SpryMedia Ltd (www.sprymedia.co.uk)
  11. * @contact www.sprymedia.co.uk/contact
  12. * @copyright Copyright 2009-2014 SpryMedia Ltd.
  13. *
  14. * This source file is free software, available under the following license:
  15. * MIT license - http://datatables.net/license/mit
  16. *
  17. * This source file is distributed in the hope that it will be useful, but
  18. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  19. * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
  20. *
  21. * For details please refer to: http://www.datatables.net
  22. */
  23. /* Global scope for FixedColumns for backwards compatibility - will be removed
  24. * in future. Not documented in 1.1.x.
  25. */
  26. /* Global scope for FixedColumns */
  27. var FixedHeader;
  28. (function(window, document, undefined) {
  29. var factory = function( $, DataTable ) {
  30. "use strict";
  31. /*
  32. * Function: FixedHeader
  33. * Purpose: Provide 'fixed' header, footer and columns for a DataTable
  34. * Returns: object:FixedHeader - must be called with 'new'
  35. * Inputs: mixed:mTable - target table
  36. * @param {object} dt DataTables instance or HTML table node. With DataTables
  37. * 1.10 this can also be a jQuery collection (with just a single table in its
  38. * result set), a jQuery selector, DataTables API instance or settings
  39. * object.
  40. * @param {object} [oInit] initialisation settings, with the following
  41. * properties (each optional)
  42. * * bool:top - fix the header (default true)
  43. * * bool:bottom - fix the footer (default false)
  44. * * int:left - fix the left column(s) (default 0)
  45. * * int:right - fix the right column(s) (default 0)
  46. * * int:zTop - fixed header zIndex
  47. * * int:zBottom - fixed footer zIndex
  48. * * int:zLeft - fixed left zIndex
  49. * * int:zRight - fixed right zIndex
  50. */
  51. FixedHeader = function ( mTable, oInit ) {
  52. /* Sanity check - you just know it will happen */
  53. if ( ! this instanceof FixedHeader )
  54. {
  55. alert( "FixedHeader warning: FixedHeader must be initialised with the 'new' keyword." );
  56. return;
  57. }
  58. var that = this;
  59. var oSettings = {
  60. "aoCache": [],
  61. "oSides": {
  62. "top": true,
  63. "bottom": false,
  64. "left": 0,
  65. "right": 0
  66. },
  67. "oZIndexes": {
  68. "top": 104,
  69. "bottom": 103,
  70. "left": 102,
  71. "right": 101
  72. },
  73. "oCloneOnDraw": {
  74. "top": false,
  75. "bottom": false,
  76. "left": true,
  77. "right": true
  78. },
  79. "oMes": {
  80. "iTableWidth": 0,
  81. "iTableHeight": 0,
  82. "iTableLeft": 0,
  83. "iTableRight": 0, /* note this is left+width, not actually "right" */
  84. "iTableTop": 0,
  85. "iTableBottom": 0 /* note this is top+height, not actually "bottom" */
  86. },
  87. "oOffset": {
  88. "top": 0
  89. },
  90. "nTable": null,
  91. "bFooter": false,
  92. "bInitComplete": false
  93. };
  94. /*
  95. * Function: fnGetSettings
  96. * Purpose: Get the settings for this object
  97. * Returns: object: - settings object
  98. * Inputs: -
  99. */
  100. this.fnGetSettings = function () {
  101. return oSettings;
  102. };
  103. /*
  104. * Function: fnUpdate
  105. * Purpose: Update the positioning and copies of the fixed elements
  106. * Returns: -
  107. * Inputs: -
  108. */
  109. this.fnUpdate = function () {
  110. this._fnUpdateClones();
  111. this._fnUpdatePositions();
  112. };
  113. /*
  114. * Function: fnPosition
  115. * Purpose: Update the positioning of the fixed elements
  116. * Returns: -
  117. * Inputs: -
  118. */
  119. this.fnPosition = function () {
  120. this._fnUpdatePositions();
  121. };
  122. var dt = $.fn.dataTable.Api ?
  123. new $.fn.dataTable.Api( mTable ).settings()[0] :
  124. mTable.fnSettings();
  125. dt._oPluginFixedHeader = this;
  126. /* Let's do it */
  127. this.fnInit( dt, oInit );
  128. };
  129. /*
  130. * Variable: FixedHeader
  131. * Purpose: Prototype for FixedHeader
  132. * Scope: global
  133. */
  134. FixedHeader.prototype = {
  135. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  136. * Initialisation
  137. */
  138. /*
  139. * Function: fnInit
  140. * Purpose: The "constructor"
  141. * Returns: -
  142. * Inputs: {as FixedHeader function}
  143. */
  144. fnInit: function ( oDtSettings, oInit )
  145. {
  146. var s = this.fnGetSettings();
  147. var that = this;
  148. /* Record the user definable settings */
  149. this.fnInitSettings( s, oInit );
  150. if ( oDtSettings.oScroll.sX !== "" || oDtSettings.oScroll.sY !== "" )
  151. {
  152. alert( "FixedHeader 2 is not supported with DataTables' scrolling mode at this time" );
  153. return;
  154. }
  155. s.nTable = oDtSettings.nTable;
  156. oDtSettings.aoDrawCallback.unshift( {
  157. "fn": function () {
  158. FixedHeader.fnMeasure();
  159. that._fnUpdateClones.call(that);
  160. that._fnUpdatePositions.call(that);
  161. },
  162. "sName": "FixedHeader"
  163. } );
  164. s.bFooter = ($('>tfoot', s.nTable).length > 0) ? true : false;
  165. /* Add the 'sides' that are fixed */
  166. if ( s.oSides.top )
  167. {
  168. s.aoCache.push( that._fnCloneTable( "fixedHeader", "FixedHeader_Header", that._fnCloneThead ) );
  169. }
  170. if ( s.oSides.bottom )
  171. {
  172. s.aoCache.push( that._fnCloneTable( "fixedFooter", "FixedHeader_Footer", that._fnCloneTfoot ) );
  173. }
  174. if ( s.oSides.left )
  175. {
  176. s.aoCache.push( that._fnCloneTable( "fixedLeft", "FixedHeader_Left", that._fnCloneTLeft, s.oSides.left ) );
  177. }
  178. if ( s.oSides.right )
  179. {
  180. s.aoCache.push( that._fnCloneTable( "fixedRight", "FixedHeader_Right", that._fnCloneTRight, s.oSides.right ) );
  181. }
  182. /* Event listeners for window movement */
  183. FixedHeader.afnScroll.push( function () {
  184. that._fnUpdatePositions.call(that);
  185. } );
  186. $(window).resize( function () {
  187. FixedHeader.fnMeasure();
  188. that._fnUpdateClones.call(that);
  189. that._fnUpdatePositions.call(that);
  190. } );
  191. $(s.nTable)
  192. .on('column-reorder.dt', function () {
  193. FixedHeader.fnMeasure();
  194. that._fnUpdateClones( true );
  195. that._fnUpdatePositions();
  196. } )
  197. .on('column-visibility.dt', function () {
  198. FixedHeader.fnMeasure();
  199. that._fnUpdateClones( true );
  200. that._fnUpdatePositions();
  201. } );
  202. /* Get things right to start with */
  203. FixedHeader.fnMeasure();
  204. that._fnUpdateClones();
  205. that._fnUpdatePositions();
  206. s.bInitComplete = true;
  207. },
  208. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  209. * Support functions
  210. */
  211. /*
  212. * Function: fnInitSettings
  213. * Purpose: Take the user's settings and copy them to our local store
  214. * Returns: -
  215. * Inputs: object:s - the local settings object
  216. * object:oInit - the user's settings object
  217. */
  218. fnInitSettings: function ( s, oInit )
  219. {
  220. if ( oInit !== undefined )
  221. {
  222. if ( oInit.top !== undefined ) {
  223. s.oSides.top = oInit.top;
  224. }
  225. if ( oInit.bottom !== undefined ) {
  226. s.oSides.bottom = oInit.bottom;
  227. }
  228. if ( typeof oInit.left == 'boolean' ) {
  229. s.oSides.left = oInit.left ? 1 : 0;
  230. }
  231. else if ( oInit.left !== undefined ) {
  232. s.oSides.left = oInit.left;
  233. }
  234. if ( typeof oInit.right == 'boolean' ) {
  235. s.oSides.right = oInit.right ? 1 : 0;
  236. }
  237. else if ( oInit.right !== undefined ) {
  238. s.oSides.right = oInit.right;
  239. }
  240. if ( oInit.zTop !== undefined ) {
  241. s.oZIndexes.top = oInit.zTop;
  242. }
  243. if ( oInit.zBottom !== undefined ) {
  244. s.oZIndexes.bottom = oInit.zBottom;
  245. }
  246. if ( oInit.zLeft !== undefined ) {
  247. s.oZIndexes.left = oInit.zLeft;
  248. }
  249. if ( oInit.zRight !== undefined ) {
  250. s.oZIndexes.right = oInit.zRight;
  251. }
  252. if ( oInit.offsetTop !== undefined ) {
  253. s.oOffset.top = oInit.offsetTop;
  254. }
  255. if ( oInit.alwaysCloneTop !== undefined ) {
  256. s.oCloneOnDraw.top = oInit.alwaysCloneTop;
  257. }
  258. if ( oInit.alwaysCloneBottom !== undefined ) {
  259. s.oCloneOnDraw.bottom = oInit.alwaysCloneBottom;
  260. }
  261. if ( oInit.alwaysCloneLeft !== undefined ) {
  262. s.oCloneOnDraw.left = oInit.alwaysCloneLeft;
  263. }
  264. if ( oInit.alwaysCloneRight !== undefined ) {
  265. s.oCloneOnDraw.right = oInit.alwaysCloneRight;
  266. }
  267. }
  268. },
  269. /*
  270. * Function: _fnCloneTable
  271. * Purpose: Clone the table node and do basic initialisation
  272. * Returns: -
  273. * Inputs: -
  274. */
  275. _fnCloneTable: function ( sType, sClass, fnClone, iCells )
  276. {
  277. var s = this.fnGetSettings();
  278. var nCTable;
  279. /* We know that the table _MUST_ has a DIV wrapped around it, because this is simply how
  280. * DataTables works. Therefore, we can set this to be relatively position (if it is not
  281. * alreadu absolute, and use this as the base point for the cloned header
  282. */
  283. if ( $(s.nTable.parentNode).css('position') != "absolute" )
  284. {
  285. s.nTable.parentNode.style.position = "relative";
  286. }
  287. /* Just a shallow clone will do - we only want the table node */
  288. nCTable = s.nTable.cloneNode( false );
  289. nCTable.removeAttribute( 'id' );
  290. var nDiv = document.createElement( 'div' );
  291. nDiv.style.position = "absolute";
  292. nDiv.style.top = "0px";
  293. nDiv.style.left = "0px";
  294. nDiv.className += " FixedHeader_Cloned "+sType+" "+sClass;
  295. /* Set the zIndexes */
  296. if ( sType == "fixedHeader" )
  297. {
  298. nDiv.style.zIndex = s.oZIndexes.top;
  299. }
  300. if ( sType == "fixedFooter" )
  301. {
  302. nDiv.style.zIndex = s.oZIndexes.bottom;
  303. }
  304. if ( sType == "fixedLeft" )
  305. {
  306. nDiv.style.zIndex = s.oZIndexes.left;
  307. }
  308. else if ( sType == "fixedRight" )
  309. {
  310. nDiv.style.zIndex = s.oZIndexes.right;
  311. }
  312. /* remove margins since we are going to position it absolutely */
  313. nCTable.style.margin = "0";
  314. /* Insert the newly cloned table into the DOM, on top of the "real" header */
  315. nDiv.appendChild( nCTable );
  316. document.body.appendChild( nDiv );
  317. return {
  318. "nNode": nCTable,
  319. "nWrapper": nDiv,
  320. "sType": sType,
  321. "sPosition": "",
  322. "sTop": "",
  323. "sLeft": "",
  324. "fnClone": fnClone,
  325. "iCells": iCells
  326. };
  327. },
  328. /*
  329. * Function: _fnMeasure
  330. * Purpose: Get the current positioning of the table in the DOM
  331. * Returns: -
  332. * Inputs: -
  333. */
  334. _fnMeasure: function ()
  335. {
  336. var
  337. s = this.fnGetSettings(),
  338. m = s.oMes,
  339. jqTable = $(s.nTable),
  340. oOffset = jqTable.offset(),
  341. iParentScrollTop = this._fnSumScroll( s.nTable.parentNode, 'scrollTop' ),
  342. iParentScrollLeft = this._fnSumScroll( s.nTable.parentNode, 'scrollLeft' );
  343. m.iTableWidth = jqTable.outerWidth();
  344. m.iTableHeight = jqTable.outerHeight();
  345. m.iTableLeft = oOffset.left + s.nTable.parentNode.scrollLeft;
  346. m.iTableTop = oOffset.top + iParentScrollTop;
  347. m.iTableRight = m.iTableLeft + m.iTableWidth;
  348. m.iTableRight = FixedHeader.oDoc.iWidth - m.iTableLeft - m.iTableWidth;
  349. m.iTableBottom = FixedHeader.oDoc.iHeight - m.iTableTop - m.iTableHeight;
  350. },
  351. /*
  352. * Function: _fnSumScroll
  353. * Purpose: Sum node parameters all the way to the top
  354. * Returns: int: sum
  355. * Inputs: node:n - node to consider
  356. * string:side - scrollTop or scrollLeft
  357. */
  358. _fnSumScroll: function ( n, side )
  359. {
  360. var i = n[side];
  361. while ( n = n.parentNode )
  362. {
  363. if ( n.nodeName == 'HTML' || n.nodeName == 'BODY' )
  364. {
  365. break;
  366. }
  367. i = n[side];
  368. }
  369. return i;
  370. },
  371. /*
  372. * Function: _fnUpdatePositions
  373. * Purpose: Loop over the fixed elements for this table and update their positions
  374. * Returns: -
  375. * Inputs: -
  376. */
  377. _fnUpdatePositions: function ()
  378. {
  379. var s = this.fnGetSettings();
  380. this._fnMeasure();
  381. for ( var i=0, iLen=s.aoCache.length ; i<iLen ; i++ )
  382. {
  383. if ( s.aoCache[i].sType == "fixedHeader" )
  384. {
  385. this._fnScrollFixedHeader( s.aoCache[i] );
  386. }
  387. else if ( s.aoCache[i].sType == "fixedFooter" )
  388. {
  389. this._fnScrollFixedFooter( s.aoCache[i] );
  390. }
  391. else if ( s.aoCache[i].sType == "fixedLeft" )
  392. {
  393. this._fnScrollHorizontalLeft( s.aoCache[i] );
  394. }
  395. else
  396. {
  397. this._fnScrollHorizontalRight( s.aoCache[i] );
  398. }
  399. }
  400. },
  401. /*
  402. * Function: _fnUpdateClones
  403. * Purpose: Loop over the fixed elements for this table and call their cloning functions
  404. * Returns: -
  405. * Inputs: -
  406. */
  407. _fnUpdateClones: function ( full )
  408. {
  409. var s = this.fnGetSettings();
  410. if ( full ) {
  411. // This is a little bit of a hack to force a full clone draw. When
  412. // `full` is set to true, we want to reclone the source elements,
  413. // regardless of the clone-on-draw settings
  414. s.bInitComplete = false;
  415. }
  416. for ( var i=0, iLen=s.aoCache.length ; i<iLen ; i++ )
  417. {
  418. s.aoCache[i].fnClone.call( this, s.aoCache[i] );
  419. }
  420. if ( full ) {
  421. s.bInitComplete = true;
  422. }
  423. },
  424. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  425. * Scrolling functions
  426. */
  427. /*
  428. * Function: _fnScrollHorizontalLeft
  429. * Purpose: Update the positioning of the scrolling elements
  430. * Returns: -
  431. * Inputs: object:oCache - the cached values for this fixed element
  432. */
  433. _fnScrollHorizontalRight: function ( oCache )
  434. {
  435. var
  436. s = this.fnGetSettings(),
  437. oMes = s.oMes,
  438. oWin = FixedHeader.oWin,
  439. oDoc = FixedHeader.oDoc,
  440. nTable = oCache.nWrapper,
  441. iFixedWidth = $(nTable).outerWidth();
  442. if ( oWin.iScrollRight < oMes.iTableRight )
  443. {
  444. /* Fully right aligned */
  445. this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
  446. this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
  447. this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft+oMes.iTableWidth-iFixedWidth)+"px", 'left', nTable.style );
  448. }
  449. else if ( oMes.iTableLeft < oDoc.iWidth-oWin.iScrollRight-iFixedWidth )
  450. {
  451. /* Middle */
  452. this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style );
  453. this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop-oWin.iScrollTop)+"px", 'top', nTable.style );
  454. this._fnUpdateCache( oCache, 'sLeft', (oWin.iWidth-iFixedWidth)+"px", 'left', nTable.style );
  455. }
  456. else
  457. {
  458. /* Fully left aligned */
  459. this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
  460. this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
  461. this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );
  462. }
  463. },
  464. /*
  465. * Function: _fnScrollHorizontalLeft
  466. * Purpose: Update the positioning of the scrolling elements
  467. * Returns: -
  468. * Inputs: object:oCache - the cached values for this fixed element
  469. */
  470. _fnScrollHorizontalLeft: function ( oCache )
  471. {
  472. var
  473. s = this.fnGetSettings(),
  474. oMes = s.oMes,
  475. oWin = FixedHeader.oWin,
  476. oDoc = FixedHeader.oDoc,
  477. nTable = oCache.nWrapper,
  478. iCellWidth = $(nTable).outerWidth();
  479. if ( oWin.iScrollLeft < oMes.iTableLeft )
  480. {
  481. /* Fully left align */
  482. this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
  483. this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
  484. this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );
  485. }
  486. else if ( oWin.iScrollLeft < oMes.iTableLeft+oMes.iTableWidth-iCellWidth )
  487. {
  488. this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style );
  489. this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop-oWin.iScrollTop)+"px", 'top', nTable.style );
  490. this._fnUpdateCache( oCache, 'sLeft', "0px", 'left', nTable.style );
  491. }
  492. else
  493. {
  494. /* Fully right align */
  495. this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
  496. this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
  497. this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft+oMes.iTableWidth-iCellWidth)+"px", 'left', nTable.style );
  498. }
  499. },
  500. /*
  501. * Function: _fnScrollFixedFooter
  502. * Purpose: Update the positioning of the scrolling elements
  503. * Returns: -
  504. * Inputs: object:oCache - the cached values for this fixed element
  505. */
  506. _fnScrollFixedFooter: function ( oCache )
  507. {
  508. var
  509. s = this.fnGetSettings(),
  510. oMes = s.oMes,
  511. oWin = FixedHeader.oWin,
  512. oDoc = FixedHeader.oDoc,
  513. nTable = oCache.nWrapper,
  514. iTheadHeight = $("thead", s.nTable).outerHeight(),
  515. iCellHeight = $(nTable).outerHeight();
  516. if ( oWin.iScrollBottom < oMes.iTableBottom )
  517. {
  518. /* Below */
  519. this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
  520. this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop+oMes.iTableHeight-iCellHeight)+"px", 'top', nTable.style );
  521. this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );
  522. }
  523. else if ( oWin.iScrollBottom < oMes.iTableBottom+oMes.iTableHeight-iCellHeight-iTheadHeight )
  524. {
  525. this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style );
  526. this._fnUpdateCache( oCache, 'sTop', (oWin.iHeight-iCellHeight)+"px", 'top', nTable.style );
  527. this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft-oWin.iScrollLeft)+"px", 'left', nTable.style );
  528. }
  529. else
  530. {
  531. /* Above */
  532. this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
  533. this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop+iCellHeight)+"px", 'top', nTable.style );
  534. this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );
  535. }
  536. },
  537. /*
  538. * Function: _fnScrollFixedHeader
  539. * Purpose: Update the positioning of the scrolling elements
  540. * Returns: -
  541. * Inputs: object:oCache - the cached values for this fixed element
  542. */
  543. _fnScrollFixedHeader: function ( oCache )
  544. {
  545. var
  546. s = this.fnGetSettings(),
  547. oMes = s.oMes,
  548. oWin = FixedHeader.oWin,
  549. oDoc = FixedHeader.oDoc,
  550. nTable = oCache.nWrapper,
  551. iTbodyHeight = 0,
  552. anTbodies = s.nTable.getElementsByTagName('tbody');
  553. for (var i = 0; i < anTbodies.length; ++i) {
  554. iTbodyHeight += anTbodies[i].offsetHeight;
  555. }
  556. if ( oMes.iTableTop > oWin.iScrollTop + s.oOffset.top )
  557. {
  558. /* Above the table */
  559. this._fnUpdateCache( oCache, 'sPosition', "absolute", 'position', nTable.style );
  560. this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
  561. this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );
  562. }
  563. else if ( oWin.iScrollTop + s.oOffset.top > oMes.iTableTop+iTbodyHeight )
  564. {
  565. /* At the bottom of the table */
  566. this._fnUpdateCache( oCache, 'sPosition', "absolute", 'position', nTable.style );
  567. this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop+iTbodyHeight)+"px", 'top', nTable.style );
  568. this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );
  569. }
  570. else
  571. {
  572. /* In the middle of the table */
  573. this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style );
  574. this._fnUpdateCache( oCache, 'sTop', s.oOffset.top+"px", 'top', nTable.style );
  575. this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft-oWin.iScrollLeft)+"px", 'left', nTable.style );
  576. }
  577. },
  578. /*
  579. * Function: _fnUpdateCache
  580. * Purpose: Check the cache and update cache and value if needed
  581. * Returns: -
  582. * Inputs: object:oCache - local cache object
  583. * string:sCache - cache property
  584. * string:sSet - value to set
  585. * string:sProperty - object property to set
  586. * object:oObj - object to update
  587. */
  588. _fnUpdateCache: function ( oCache, sCache, sSet, sProperty, oObj )
  589. {
  590. if ( oCache[sCache] != sSet )
  591. {
  592. oObj[sProperty] = sSet;
  593. oCache[sCache] = sSet;
  594. }
  595. },
  596. /**
  597. * Copy the classes of all child nodes from one element to another. This implies
  598. * that the two have identical structure - no error checking is performed to that
  599. * fact.
  600. * @param {element} source Node to copy classes from
  601. * @param {element} dest Node to copy classes too
  602. */
  603. _fnClassUpdate: function ( source, dest )
  604. {
  605. var that = this;
  606. if ( source.nodeName.toUpperCase() === "TR" || source.nodeName.toUpperCase() === "TH" ||
  607. source.nodeName.toUpperCase() === "TD" || source.nodeName.toUpperCase() === "SPAN" )
  608. {
  609. dest.className = source.className;
  610. }
  611. $(source).children().each( function (i) {
  612. that._fnClassUpdate( $(source).children()[i], $(dest).children()[i] );
  613. } );
  614. },
  615. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  616. * Cloning functions
  617. */
  618. /*
  619. * Function: _fnCloneThead
  620. * Purpose: Clone the thead element
  621. * Returns: -
  622. * Inputs: object:oCache - the cached values for this fixed element
  623. */
  624. _fnCloneThead: function ( oCache )
  625. {
  626. var s = this.fnGetSettings();
  627. var nTable = oCache.nNode;
  628. if ( s.bInitComplete && !s.oCloneOnDraw.top )
  629. {
  630. this._fnClassUpdate( $('thead', s.nTable)[0], $('thead', nTable)[0] );
  631. return;
  632. }
  633. /* Set the wrapper width to match that of the cloned table */
  634. var iDtWidth = $(s.nTable).outerWidth();
  635. oCache.nWrapper.style.width = iDtWidth+"px";
  636. nTable.style.width = iDtWidth+"px";
  637. /* Remove any children the cloned table has */
  638. while ( nTable.childNodes.length > 0 )
  639. {
  640. $('thead th', nTable).unbind( 'click' );
  641. nTable.removeChild( nTable.childNodes[0] );
  642. }
  643. /* Clone the DataTables header */
  644. var nThead = $('thead', s.nTable).clone(true)[0];
  645. nTable.appendChild( nThead );
  646. /* Copy the widths across - apparently a clone isn't good enough for this */
  647. var a = [];
  648. var b = [];
  649. $("thead>tr th", s.nTable).each( function (i) {
  650. a.push( $(this).width() );
  651. } );
  652. $("thead>tr td", s.nTable).each( function (i) {
  653. b.push( $(this).width() );
  654. } );
  655. $("thead>tr th", s.nTable).each( function (i) {
  656. $("thead>tr th:eq("+i+")", nTable).width( a[i] );
  657. $(this).width( a[i] );
  658. } );
  659. $("thead>tr td", s.nTable).each( function (i) {
  660. $("thead>tr td:eq("+i+")", nTable).width( b[i] );
  661. $(this).width( b[i] );
  662. } );
  663. // Stop DataTables 1.9 from putting a focus ring on the headers when
  664. // clicked to sort
  665. $('th.sorting, th.sorting_desc, th.sorting_asc', nTable).bind( 'click', function () {
  666. this.blur();
  667. } );
  668. },
  669. /*
  670. * Function: _fnCloneTfoot
  671. * Purpose: Clone the tfoot element
  672. * Returns: -
  673. * Inputs: object:oCache - the cached values for this fixed element
  674. */
  675. _fnCloneTfoot: function ( oCache )
  676. {
  677. var s = this.fnGetSettings();
  678. var nTable = oCache.nNode;
  679. /* Set the wrapper width to match that of the cloned table */
  680. oCache.nWrapper.style.width = $(s.nTable).outerWidth()+"px";
  681. /* Remove any children the cloned table has */
  682. while ( nTable.childNodes.length > 0 )
  683. {
  684. nTable.removeChild( nTable.childNodes[0] );
  685. }
  686. /* Clone the DataTables footer */
  687. var nTfoot = $('tfoot', s.nTable).clone(true)[0];
  688. nTable.appendChild( nTfoot );
  689. /* Copy the widths across - apparently a clone isn't good enough for this */
  690. $("tfoot:eq(0)>tr th", s.nTable).each( function (i) {
  691. $("tfoot:eq(0)>tr th:eq("+i+")", nTable).width( $(this).width() );
  692. } );
  693. $("tfoot:eq(0)>tr td", s.nTable).each( function (i) {
  694. $("tfoot:eq(0)>tr td:eq("+i+")", nTable).width( $(this).width() );
  695. } );
  696. },
  697. /*
  698. * Function: _fnCloneTLeft
  699. * Purpose: Clone the left column(s)
  700. * Returns: -
  701. * Inputs: object:oCache - the cached values for this fixed element
  702. */
  703. _fnCloneTLeft: function ( oCache )
  704. {
  705. var s = this.fnGetSettings();
  706. var nTable = oCache.nNode;
  707. var nBody = $('tbody', s.nTable)[0];
  708. /* Remove any children the cloned table has */
  709. while ( nTable.childNodes.length > 0 )
  710. {
  711. nTable.removeChild( nTable.childNodes[0] );
  712. }
  713. /* Is this the most efficient way to do this - it looks horrible... */
  714. nTable.appendChild( $("thead", s.nTable).clone(true)[0] );
  715. nTable.appendChild( $("tbody", s.nTable).clone(true)[0] );
  716. if ( s.bFooter )
  717. {
  718. nTable.appendChild( $("tfoot", s.nTable).clone(true)[0] );
  719. }
  720. /* Remove unneeded cells */
  721. var sSelector = 'gt(' + (oCache.iCells - 1) + ')';
  722. $('thead tr', nTable).each( function (k) {
  723. $('th:' + sSelector, this).remove();
  724. } );
  725. $('tfoot tr', nTable).each( function (k) {
  726. $('th:' + sSelector, this).remove();
  727. } );
  728. $('tbody tr', nTable).each( function (k) {
  729. $('td:' + sSelector, this).remove();
  730. } );
  731. this.fnEqualiseHeights( 'thead', nBody.parentNode, nTable );
  732. this.fnEqualiseHeights( 'tbody', nBody.parentNode, nTable );
  733. this.fnEqualiseHeights( 'tfoot', nBody.parentNode, nTable );
  734. var iWidth = 0;
  735. for (var i = 0; i < oCache.iCells; i++) {
  736. iWidth += $('thead tr th:eq(' + i + ')', s.nTable).outerWidth();
  737. }
  738. nTable.style.width = iWidth+"px";
  739. oCache.nWrapper.style.width = iWidth+"px";
  740. },
  741. /*
  742. * Function: _fnCloneTRight
  743. * Purpose: Clone the right most column(s)
  744. * Returns: -
  745. * Inputs: object:oCache - the cached values for this fixed element
  746. */
  747. _fnCloneTRight: function ( oCache )
  748. {
  749. var s = this.fnGetSettings();
  750. var nBody = $('tbody', s.nTable)[0];
  751. var nTable = oCache.nNode;
  752. var iCols = $('tbody tr:eq(0) td', s.nTable).length;
  753. /* Remove any children the cloned table has */
  754. while ( nTable.childNodes.length > 0 )
  755. {
  756. nTable.removeChild( nTable.childNodes[0] );
  757. }
  758. /* Is this the most efficient way to do this - it looks horrible... */
  759. nTable.appendChild( $("thead", s.nTable).clone(true)[0] );
  760. nTable.appendChild( $("tbody", s.nTable).clone(true)[0] );
  761. if ( s.bFooter )
  762. {
  763. nTable.appendChild( $("tfoot", s.nTable).clone(true)[0] );
  764. }
  765. $('thead tr th:lt('+(iCols-oCache.iCells)+')', nTable).remove();
  766. $('tfoot tr th:lt('+(iCols-oCache.iCells)+')', nTable).remove();
  767. /* Remove unneeded cells */
  768. $('tbody tr', nTable).each( function (k) {
  769. $('td:lt('+(iCols-oCache.iCells)+')', this).remove();
  770. } );
  771. this.fnEqualiseHeights( 'thead', nBody.parentNode, nTable );
  772. this.fnEqualiseHeights( 'tbody', nBody.parentNode, nTable );
  773. this.fnEqualiseHeights( 'tfoot', nBody.parentNode, nTable );
  774. var iWidth = 0;
  775. for (var i = 0; i < oCache.iCells; i++) {
  776. iWidth += $('thead tr th:eq('+(iCols-1-i)+')', s.nTable).outerWidth();
  777. }
  778. nTable.style.width = iWidth+"px";
  779. oCache.nWrapper.style.width = iWidth+"px";
  780. },
  781. /**
  782. * Equalise the heights of the rows in a given table node in a cross browser way. Note that this
  783. * is more or less lifted as is from FixedColumns
  784. * @method fnEqualiseHeights
  785. * @returns void
  786. * @param {string} parent Node type - thead, tbody or tfoot
  787. * @param {element} original Original node to take the heights from
  788. * @param {element} clone Copy the heights to
  789. * @private
  790. */
  791. "fnEqualiseHeights": function ( parent, original, clone )
  792. {
  793. var that = this;
  794. var originals = $(parent +' tr', original);
  795. var height;
  796. $(parent+' tr', clone).each( function (k) {
  797. height = originals.eq( k ).css('height');
  798. // This is nasty :-(. IE has a sub-pixel error even when setting
  799. // the height below (the Firefox fix) which causes the fixed column
  800. // to go out of alignment. Need to add a pixel before the assignment
  801. // Can this be feature detected? Not sure how...
  802. if ( navigator.appName == 'Microsoft Internet Explorer' ) {
  803. height = parseInt( height, 10 ) + 1;
  804. }
  805. $(this).css( 'height', height );
  806. // For Firefox to work, we need to also set the height of the
  807. // original row, to the value that we read from it! Otherwise there
  808. // is a sub-pixel rounding error
  809. originals.eq( k ).css( 'height', height );
  810. } );
  811. }
  812. };
  813. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  814. * Static properties and methods
  815. * We use these for speed! This information is common to all instances of FixedHeader, so no
  816. * point if having them calculated and stored for each different instance.
  817. */
  818. /*
  819. * Variable: oWin
  820. * Purpose: Store information about the window positioning
  821. * Scope: FixedHeader
  822. */
  823. FixedHeader.oWin = {
  824. "iScrollTop": 0,
  825. "iScrollRight": 0,
  826. "iScrollBottom": 0,
  827. "iScrollLeft": 0,
  828. "iHeight": 0,
  829. "iWidth": 0
  830. };
  831. /*
  832. * Variable: oDoc
  833. * Purpose: Store information about the document size
  834. * Scope: FixedHeader
  835. */
  836. FixedHeader.oDoc = {
  837. "iHeight": 0,
  838. "iWidth": 0
  839. };
  840. /*
  841. * Variable: afnScroll
  842. * Purpose: Array of functions that are to be used for the scrolling components
  843. * Scope: FixedHeader
  844. */
  845. FixedHeader.afnScroll = [];
  846. /*
  847. * Function: fnMeasure
  848. * Purpose: Update the measurements for the window and document
  849. * Returns: -
  850. * Inputs: -
  851. */
  852. FixedHeader.fnMeasure = function ()
  853. {
  854. var
  855. jqWin = $(window),
  856. jqDoc = $(document),
  857. oWin = FixedHeader.oWin,
  858. oDoc = FixedHeader.oDoc;
  859. oDoc.iHeight = jqDoc.height();
  860. oDoc.iWidth = jqDoc.width();
  861. oWin.iHeight = jqWin.height();
  862. oWin.iWidth = jqWin.width();
  863. oWin.iScrollTop = jqWin.scrollTop();
  864. oWin.iScrollLeft = jqWin.scrollLeft();
  865. oWin.iScrollRight = oDoc.iWidth - oWin.iScrollLeft - oWin.iWidth;
  866. oWin.iScrollBottom = oDoc.iHeight - oWin.iScrollTop - oWin.iHeight;
  867. };
  868. FixedHeader.version = "2.1.2";
  869. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  870. * Global processing
  871. */
  872. /*
  873. * Just one 'scroll' event handler in FixedHeader, which calls the required components. This is
  874. * done as an optimisation, to reduce calculation and proagation time
  875. */
  876. $(window).scroll( function () {
  877. FixedHeader.fnMeasure();
  878. for ( var i=0, iLen=FixedHeader.afnScroll.length ; i<iLen ; i++ ) {
  879. FixedHeader.afnScroll[i]();
  880. }
  881. } );
  882. $.fn.dataTable.FixedHeader = FixedHeader;
  883. $.fn.DataTable.FixedHeader = FixedHeader;
  884. return FixedHeader;
  885. }; // /factory
  886. // Define as an AMD module if possible
  887. if ( typeof define === 'function' && define.amd ) {
  888. define( ['jquery', 'datatables'], factory );
  889. }
  890. else if ( typeof exports === 'object' ) {
  891. // Node/CommonJS
  892. factory( require('jquery'), require('datatables') );
  893. }
  894. else if ( jQuery && !jQuery.fn.dataTable.FixedHeader ) {
  895. // Otherwise simply initialise as normal, stopping multiple evaluation
  896. factory( jQuery, jQuery.fn.dataTable );
  897. }
  898. })(window, document);