1 /* 2 * File: FixedColumns.js 3 * Version: 1.0.0 4 * Description: "Fix" columns on the left of a scrolling DataTable 5 * Author: Allan Jardine (www.sprymedia.co.uk) 6 * Created: Sat Sep 18 09:28:54 BST 2010 7 * Language: Javascript 8 * License: GPL v2 or BSD 3 point style 9 * Project: Just a little bit of fun - enjoy :-) 10 * Contact: www.sprymedia.co.uk/contact 11 * 12 * Copyright 2010 Allan Jardine, all rights reserved. 13 */ 14 15 var FixedColumns = function ( oDT, oInit ) { 16 /* Sanity check - you just know it will happen */ 17 if ( typeof this._fnConstruct != 'function' ) 18 { 19 alert( "FixedColumns warning: FixedColumns must be initialised with the 'new' keyword." ); 20 return; 21 } 22 23 if ( typeof oInit == 'undefined' ) 24 { 25 oInit = {}; 26 } 27 28 /** 29 * @namespace Settings object which contains customisable information for FixedColumns instance 30 */ 31 this.s = { 32 /** 33 * DataTables settings objects 34 * @property dt 35 * @type object 36 * @default null 37 */ 38 dt: oDT.fnSettings(), 39 40 /** 41 * Number of columns to fix in position 42 * @property columns 43 * @type int 44 * @default 1 45 */ 46 columns: 1 47 }; 48 49 50 /** 51 * @namespace Common and useful DOM elements for the class instance 52 */ 53 this.dom = { 54 /** 55 * DataTables scrolling element 56 * @property scroller 57 * @type node 58 * @default null 59 */ 60 scroller: null, 61 62 /** 63 * DataTables header table 64 * @property header 65 * @type node 66 * @default null 67 */ 68 header: null, 69 70 /** 71 * DataTables body table 72 * @property body 73 * @type node 74 * @default null 75 */ 76 body: null, 77 78 /** 79 * DataTables footer table 80 * @property footer 81 * @type node 82 * @default null 83 */ 84 footer: null, 85 86 /** 87 * @namespace Cloned table nodes 88 */ 89 clone: { 90 /** 91 * Cloned header table 92 * @property header 93 * @type node 94 * @default null 95 */ 96 header: null, 97 98 /** 99 * Cloned body table 100 * @property body 101 * @type node 102 * @default null 103 */ 104 body: null, 105 106 /** 107 * Cloned footer table 108 * @property footer 109 * @type node 110 * @default null 111 */ 112 footer: null 113 } 114 }; 115 116 /* Let's do it */ 117 this._fnConstruct( oInit ); 118 }; 119 120 121 FixedColumns.prototype = { 122 /** 123 * Initialisation for FixedColumns 124 * @method _fnConstruct 125 * @param {Object} oInit User settings for initialisation 126 * @returns void 127 */ 128 _fnConstruct: function ( oInit ) 129 { 130 var that = this; 131 132 /* Sanity checking */ 133 if ( typeof this.s.dt.oInstance.fnVersionCheck != 'function' || 134 this.s.dt.oInstance.fnVersionCheck( '1.7.0' ) !== true ) 135 { 136 alert( "FixedColumns 2 required DataTables 1.7.0 or later. "+ 137 "Please upgrade your DataTables installation" ); 138 return; 139 } 140 141 if ( this.s.dt.oScroll.sX === "" ) 142 { 143 this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "FixedColumns is not needed (no "+ 144 "x-scrolling in DataTables enabled), so no action will be taken. Use 'FixedHeader' for "+ 145 "column fixing when scrolling is not enabled" ); 146 return; 147 } 148 149 if ( typeof oInit.columns != 'undefined' ) 150 { 151 if ( oInit.columns < 1 ) 152 { 153 this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "FixedColumns is not needed (no "+ 154 "columns to be fixed), so no action will be taken" ); 155 return; 156 } 157 this.s.columns = oInit.columns; 158 } 159 160 /* Set up the DOM as we need it and cache nodes */ 161 this.dom.body = this.s.dt.nTable; 162 this.dom.scroller = this.dom.body.parentNode; 163 this.dom.scroller.style.position = "relative"; 164 165 this.dom.header = this.s.dt.nTHead.parentNode; 166 this.dom.header.parentNode.style.position = "relative"; 167 168 if ( this.s.dt.nTFoot ) 169 { 170 this.dom.footer = this.s.dt.nTFoot.parentNode; 171 this.dom.footer.parentNode.style.position = "relative"; 172 } 173 174 /* Event handlers */ 175 $(this.dom.scroller).scroll( function () { 176 that._fnScroll.call( that ); 177 } ); 178 179 this.s.dt.aoDrawCallback.push( { 180 fn: function () { 181 that._fnClone.call( that ); 182 that._fnScroll.call( that ); 183 }, 184 sName: "FixedColumns" 185 } ); 186 187 /* Get things right to start with */ 188 this._fnClone(); 189 this._fnScroll(); 190 }, 191 192 193 /** 194 * Clone the DataTable nodes and place them in the DOM (sized correctly) 195 * @method _fnClone 196 * @returns void 197 * @private 198 */ 199 _fnClone: function () 200 { 201 var 202 that = this, 203 iTableWidth = 0, 204 aiCellWidth = [], 205 i, iLen, jq; 206 207 /* Grab the widths that we are going to need */ 208 for ( i=0, iLen=this.s.columns ; i<iLen ; i++ ) 209 { 210 jq = $('thead th:eq('+i+')', this.dom.header); 211 iTableWidth += jq.outerWidth(); 212 aiCellWidth.push( jq.width() ); 213 } 214 215 216 /* Header */ 217 if ( this.dom.clone.header !== null ) 218 { 219 this.dom.clone.header.parentNode.removeChild( this.dom.clone.header ); 220 } 221 this.dom.clone.header = $(this.dom.header).clone(true)[0]; 222 this.dom.clone.header.className += " FixedColumns_Cloned"; 223 224 $('thead tr', this.dom.clone.header).each( function () { 225 $('th:gt('+(that.s.columns-1)+')', this).remove(); 226 } ); 227 228 $('thead th', this.dom.clone.header).each( function (i) { 229 this.style.width = aiCellWidth[i]+"px"; 230 } ); 231 232 this.dom.clone.header.style.position = "absolute"; 233 this.dom.clone.header.style.top = "0px"; 234 this.dom.clone.header.style.left = "0px"; 235 this.dom.clone.header.style.width = iTableWidth+"px"; 236 this.dom.header.parentNode.appendChild( this.dom.clone.header ); 237 238 239 /* Body */ 240 if ( this.dom.clone.body !== null ) 241 { 242 this.dom.clone.body.parentNode.removeChild( this.dom.clone.body ); 243 } 244 this.dom.clone.body = $(this.dom.body).clone(true)[0]; 245 this.dom.clone.body.className += " FixedColumns_Cloned"; 246 247 $('thead tr', this.dom.clone.body).each( function () { 248 $('th:gt('+(that.s.columns-1)+')', this).remove(); 249 } ); 250 251 $('tbody tr', this.dom.clone.body).each( function () { 252 $('td:gt('+(that.s.columns-1)+')', this).remove(); 253 } ); 254 255 $('tfoot tr', this.dom.clone.body).each( function () { 256 $('th:gt('+(that.s.columns-1)+')', this).remove(); 257 } ); 258 259 $('thead th', this.dom.clone.body).each( function (i) { 260 this.style.width = aiCellWidth[i]+"px"; 261 } ); 262 263 this.dom.clone.body.style.position = "absolute"; 264 this.dom.clone.body.style.top = "0px"; 265 this.dom.clone.body.style.left = "0px"; 266 this.dom.clone.body.style.width = iTableWidth+"px"; 267 this.dom.body.parentNode.appendChild( this.dom.clone.body ); 268 269 270 /* Footer */ 271 if ( this.s.dt.nTFoot !== null ) 272 { 273 if ( this.dom.clone.footer !== null ) 274 { 275 this.dom.clone.footer.parentNode.removeChild( this.dom.clone.footer ); 276 } 277 this.dom.clone.footer = $(this.dom.footer).clone(true)[0]; 278 this.dom.clone.footer.className += " FixedColumns_Cloned"; 279 280 $('tfoot tr', this.dom.clone.footer).each( function () { 281 $('th:gt('+(that.s.columns-1)+')', this).remove(); 282 } ); 283 284 $('tfoot th', this.dom.clone.footer).each( function (i) { 285 this.style.width = aiCellWidth[i]+"px"; 286 } ); 287 288 this.dom.clone.footer.style.position = "absolute"; 289 this.dom.clone.footer.style.top = "0px"; 290 this.dom.clone.footer.style.left = "0px"; 291 this.dom.clone.footer.style.width = iTableWidth+"px"; 292 this.dom.footer.parentNode.appendChild( this.dom.clone.footer ); 293 } 294 }, 295 296 297 /** 298 * Set the absolute position of the fixed column tables when scrolling the DataTable 299 * @method _fnScroll 300 * @returns void 301 * @private 302 */ 303 _fnScroll: function () 304 { 305 var iScrollLeft = $(this.dom.scroller).scrollLeft(); 306 307 this.dom.clone.header.style.left = iScrollLeft+"px"; 308 this.dom.clone.body.style.left = iScrollLeft+"px"; 309 if ( this.dom.footer ) 310 { 311 this.dom.clone.footer.style.left = iScrollLeft+"px"; 312 } 313 } 314 }; 315