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