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