1 /*
  2  * File:        ColReorder.js
  3  * Version:     1.0.1
  4  * CVS:         $Id$
  5  * Description: Controls for column visiblity in DataTables
  6  * Author:      Allan Jardine (www.sprymedia.co.uk)
  7  * Created:     Wed Sep 15 18:23:29 BST 2010
  8  * Modified:    $Date$ by $Author$
  9  * Language:    Javascript
 10  * License:     LGPL
 11  * Project:     DataTables
 12  * Contact:     www.sprymedia.co.uk/contact
 13  * 
 14  * Copyright 2010 Allan Jardine, all rights reserved.
 15  *
 16  */
 17 
 18 
 19 (function($, window, document) {
 20 
 21 
 22 /**
 23  * Switch the key value pairing of an index array to be value key (i.e. the old value is now the
 24  * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ].
 25  *  @method  fnInvertKeyValues
 26  *  @param   array aIn Array to switch around
 27  *  @returns array
 28  */
 29 function fnInvertKeyValues( aIn )
 30 {
 31 	var aRet=[];
 32 	for ( var i=0, iLen=aIn.length ; i<iLen ; i++ )
 33 	{
 34 		aRet[ aIn[i] ] = i;
 35 	}
 36 	return aRet;
 37 }
 38 
 39 
 40 /**
 41  * Modify an array by switching the position of two elements
 42  *  @method  fnArraySwitch
 43  *  @param   array aArray Array to consider, will be modified by reference (i.e. no return)
 44  *  @param   int iFrom From point
 45  *  @param   int iTo Insert point
 46  *  @returns void
 47  */
 48 function fnArraySwitch( aArray, iFrom, iTo )
 49 {
 50 	var mStore = aArray.splice( iFrom, 1 )[0];
 51 	aArray.splice( iTo, 0, mStore );
 52 }
 53 
 54 
 55 /**
 56  * Switch the positions of nodes in a parent node (note this is specifically designed for 
 57  * table rows). Note this function considers all element nodes under the parent!
 58  *  @method  fnDomSwitch
 59  *  @param   string sTag Tag to consider
 60  *  @param   int iFrom Element to move
 61  *  @param   int Point to element the element to (before this point), can be null for append
 62  *  @returns void
 63  */
 64 function fnDomSwitch( nParent, iFrom, iTo )
 65 {
 66 	var anTags = [];
 67 	for ( var i=0, iLen=nParent.childNodes.length ; i<iLen ; i++ )
 68 	{
 69 		if ( nParent.childNodes[i].nodeType == 1 )
 70 		{
 71 			anTags.push( nParent.childNodes[i] );
 72 		}
 73 	}
 74 	var nStore = anTags[ iFrom ];
 75 	
 76 	if ( iTo !== null )
 77 	{
 78 		nParent.insertBefore( nStore, anTags[iTo] );
 79 	}
 80 	else
 81 	{
 82 		nParent.appendChild( nStore );
 83 	}
 84 }
 85 
 86 
 87 
 88 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 89  * DataTables plug-in API functions
 90  *
 91  * This are required by ColReorder in order to perform the tasks required, and also keep this
 92  * code portable, to be used for other column reordering projects with DataTables, if needed.
 93  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 94 
 95 
 96 /**
 97  * Plug-in for DataTables which will reorder the internal column structure by taking the column
 98  * from one position (iFrom) and insert it into a given point (iTo).
 99  *  @method  $.fn.dataTableExt.oApi.fnColReorder
100  *  @param   object oSettings DataTables settings object - automatically added by DataTables! 
101  *  @param   int iFrom Take the column to be repositioned from this point
102  *  @param   int iTo and insert it into this point
103  *  @returns void
104  */
105 $.fn.dataTableExt.oApi.fnColReorder = function ( oSettings, iFrom, iTo )
106 {
107 	var i, iLen, j, jLen, iCols=oSettings.aoColumns.length, nTrs;
108 	
109 	/* Sanity check in the input */
110 	if ( iFrom == iTo )
111 	{
112 		/* Pointless reorder */
113 		return;
114 	}
115 	
116 	if ( iFrom < 0 || iFrom >= iCols )
117 	{
118 		this.oApi._fnLog( oSettings, 1, "ColReorder 'from' index is out of bounds: "+iFrom );
119 		return;
120 	}
121 	
122 	if ( iTo < 0 || iTo >= iCols )
123 	{
124 		this.oApi._fnLog( oSettings, 1, "ColReorder 'to' index is out of bounds: "+iTo );
125 		return;
126 	}
127 	
128 	/*
129 	 * Calculate the new column array index, so we have a mapping between the old and new
130 	 */
131 	var aiMapping = [];
132 	for ( i=0, iLen=iCols ; i<iLen ; i++ )
133 	{
134 		aiMapping[i] = i;
135 	}
136 	fnArraySwitch( aiMapping, iFrom, iTo );
137 	var aiInvertMapping = fnInvertKeyValues( aiMapping );
138 	
139 	
140 	/*
141 	 * Convert all internal indexing to the new column order indexes
142 	 */
143 	/* Sorting */
144 	for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ )
145 	{
146 		oSettings.aaSorting[i][0] = aiInvertMapping[ oSettings.aaSorting[i][0] ];
147 	}
148 	
149 	/* Fixed sorting */
150 	if ( oSettings.aaSortingFixed !== null )
151 	{
152 		for ( i=0, iLen=oSettings.aaSortingFixed.length ; i<iLen ; i++ )
153 		{
154 			oSettings.aaSortingFixed[i][0] = aiInvertMapping[ oSettings.aaSortingFixed[i][0] ];
155 		}
156 	}
157 	
158 	/* Data column sorting (the column which the sort for a given column should take place on) */
159 	for ( i=0, iLen=iCols ; i<iLen ; i++ )
160 	{
161 		oSettings.aoColumns[i].iDataSort = aiInvertMapping[ oSettings.aoColumns[i].iDataSort ];
162 	}
163 	
164 	
165 	/*
166 	 * Move the DOM elements
167 	 */
168 	if ( oSettings.aoColumns[iFrom].bVisible )
169 	{
170 		/* Calculate the current visible index and the point to insert the node before. The insert
171 		 * before needs to take into account that there might not be an element to insert before,
172 		 * in which case it will be null, and an appendChild should be used
173 		 */
174 		var iVisibleIndex = this.oApi._fnColumnIndexToVisible( oSettings, iFrom );
175 		var iInsertBeforeIndex = null;
176 		
177 		i = iTo < iFrom ? iTo : iTo + 1;
178 		while ( iInsertBeforeIndex === null && i < iCols )
179 		{
180 			iInsertBeforeIndex = this.oApi._fnColumnIndexToVisible( oSettings, i );
181 			i++;
182 		}
183 		
184 		/* Header */
185 		nTrs = oSettings.nTHead.getElementsByTagName('tr');
186 		for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
187 		{
188 			fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex );
189 		}
190 		
191 		/* Footer */
192 		if ( oSettings.nTFoot !== null )
193 		{
194 			nTrs = oSettings.nTFoot.getElementsByTagName('tr');
195 			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
196 			{
197 				fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex );
198 			}
199 		}
200 		
201 		/* Body */
202 		for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
203 		{
204 			fnDomSwitch( oSettings.aoData[i].nTr, iVisibleIndex, iInsertBeforeIndex );
205 		}
206 	}
207 	
208 	
209 	/* 
210 	 * Move the internal array elements
211 	 */
212 	/* Columns */
213 	fnArraySwitch( oSettings.aoColumns, iFrom, iTo );
214 	
215 	/* Search columns */
216 	fnArraySwitch( oSettings.aoPreSearchCols, iFrom, iTo );
217 	
218 	/* Array array - internal data anodes cache */
219 	for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
220 	{
221 		fnArraySwitch( oSettings.aoData[i]._aData, iFrom, iTo );
222 		fnArraySwitch( oSettings.aoData[i]._anHidden, iFrom, iTo );
223 	}
224 	
225 	
226 	/*
227 	 * Update DataTables' event handlers
228 	 */
229 	
230 	/* Sort listener */
231 	for ( i=0, iLen=iCols ; i<iLen ; i++ )
232 	{
233 		$(oSettings.aoColumns[i].nTh).unbind('click');
234 		this.oApi._fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i );
235 	}
236 	
237 	
238 	/*
239 	 * Any extra operations for the other plug-ins
240 	 */
241 	if ( typeof ColVis != 'undefined' )
242 	{
243 		ColVis.fnRebuild( oSettings.oInstance );
244 	}
245 	
246 	if ( typeof oSettings.oInstance._oPluginFixedHeader != 'undefined' )
247 	{
248 		oSettings.oInstance._oPluginFixedHeader.fnUpdate();
249 	}
250 };
251 
252 
253 
254 
255 /** 
256  * ColReorder provides column visiblity control for DataTables
257  * @class ColReorder
258  * @constructor
259  * @param {object} DataTables object
260  * @param {object} ColReorder options
261  */
262 ColReorder = function( oTable, oOpts )
263 {
264 	/* Santiy check that we are a new instance */
265 	if ( !this.CLASS || this.CLASS != "ColReorder" )
266 	{
267 		alert( "Warning: ColReorder must be initialised with the keyword 'new'" );
268 	}
269 	
270 	if ( typeof oOpts == 'undefined' )
271 	{
272 		oOpts = {};
273 	}
274 	
275 	
276 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
277 	 * Public class variables
278 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
279 	
280 	/**
281 	 * @namespace Settings object which contains customisable information for ColReorder instance
282 	 */
283 	this.s = {
284 		/**
285 		 * DataTables settings object
286 		 *  @property dt
287 		 *  @type     Object
288 		 *  @default  null
289 		 */
290 		dt: null,
291 		
292 		/**
293 		 * Initialisation object used for this instance
294 		 *  @property init
295 		 *  @type     object
296 		 *  @default  {}
297 		 */
298 		init: oOpts,
299 		
300 		/**
301 		 * Number of columns to fix (not allow to be reordered)
302 		 *  @property fixed
303 		 *  @type     int
304 		 *  @default  0
305 		 */
306 		fixed: 0,
307 		
308 		/**
309 		 * @namespace Information used for the mouse drag
310 		 */
311 		mouse: {
312 			startX: -1,
313 			startY: -1,
314 			offsetX: -1,
315 			offsetY: -1,
316 			target: -1,
317 			targetIndex: -1,
318 			fromIndex: -1
319 		},
320 		
321 		/**
322 		 * Information which is used for positioning the insert cusor and knowing where to do the
323 		 * insert. Array of objects with the properties:
324 		 *   x: x-axis position
325 		 *   to: insert point
326 		 *  @property aoTargets
327 		 *  @type     array
328 		 *  @default  []
329 		 */
330 		aoTargets: []
331 	};
332 	
333 	
334 	/**
335 	 * @namespace Common and useful DOM elements for the class instance
336 	 */
337 	this.dom = {
338 		/**
339 		 * Dragging element (the one the mouse is moving)
340 		 *  @property drag
341 		 *  @type     element
342 		 *  @default  null
343 		 */
344 		drag: null,
345 		
346 		/**
347 		 * The insert cursor
348 		 *  @property pointer
349 		 *  @type     element
350 		 *  @default  null
351 		 */
352 		pointer: null
353 	};
354 	
355 	
356 	/* Constructor logic */
357 	this.s.dt = oTable.fnSettings();
358 	this._fnConstruct();
359 	
360 	/* Store the instance for later use */
361 	ColReorder.aoInstances.push( this );
362 	return this;
363 };
364 
365 
366 
367 ColReorder.prototype = {
368 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
369 	 * Public methods
370 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
371 	
372 	fnReset: function ()
373 	{
374 		var a = [];
375 		for ( var i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
376 		{
377 			a.push( this.s.dt.aoColumns[i]._ColReorder_iOrigCol );
378 		}
379 		
380 		this._fnOrderColumns( a );
381 	},
382 	
383 	
384 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
385 	 * Private methods (they are of course public in JS, but recommended as private)
386 	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
387 	
388 	/**
389 	 * Constructor logic
390 	 *  @method  _fnConstruct
391 	 *  @returns void
392 	 *  @private 
393 	 */
394 	_fnConstruct: function ()
395 	{
396 		var that = this;
397 		var i, iLen;
398 		
399 		/* Columns discounted from reordering - counting left to right */
400 		if ( typeof this.s.init.iFixedColumns != 'undefined' )
401 		{
402 			this.s.fixed = this.s.init.iFixedColumns;
403 		}
404 		
405 		/* Add event handlers for the drag and drop, and also mark the original column order */
406 		for ( i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
407 		{
408 			if ( i > this.s.fixed-1 )
409 			{
410 				this._fnMouseListener( i, this.s.dt.aoColumns[i].nTh );
411 			}
412 			
413 			/* Mark the original column order for later reference */
414 			this.s.dt.aoColumns[i]._ColReorder_iOrigCol = i;
415 		}
416 		
417 		/* State saving */
418 		this.s.dt.aoStateSave.push( {
419 			fn: function (oS, sVal) {
420 				return that._fnStateSave.call( that, sVal );
421 			},
422 			sName: "ColReorder_State"
423 		} );
424 		
425 		/* An initial column order has been specified */
426 		var aiOrder = null;
427 		if ( typeof this.s.init.aiOrder != 'undefined' )
428 		{
429 			aiOrder = this.s.init.aiOrder.slice();
430 		}
431 		
432 		/* State loading, overrides the column order given */
433 		if ( this.s.dt.oLoadedState && typeof this.s.dt.oLoadedState.ColReorder != 'undefined' &&
434 		  this.s.dt.oLoadedState.ColReorder.length == this.s.dt.aoColumns.length )
435 		{
436 			aiOrder = this.s.dt.oLoadedState.ColReorder;
437 		}
438 		
439 		/* If we have an order to apply - do so */
440 		if ( aiOrder )
441 		{
442 			/* We might be called during or after the DataTables initialisation. If before, then we need
443 			 * to wait until the draw is done, if after, then do what we need to do right away
444 			 */
445 			if ( !that.s.dt._bInitComplete )
446 			{
447 				var bDone = false;
448 				this.s.dt.aoDrawCallback.push( {
449 					fn: function () {
450 						if ( !that.s.dt._bInitComplete && !bDone )
451 						{
452 							bDone = true;
453 							var resort = fnInvertKeyValues( aiOrder );
454 							that._fnOrderColumns.call( that, resort );
455 						}
456 					},
457 					sName: "ColReorder_Pre"
458 				} );
459 			}
460 			else
461 			{
462 				var resort = fnInvertKeyValues( aiOrder );
463 				that._fnOrderColumns.call( that, resort );
464 			}
465 		}
466 	},
467 	
468 	
469 	/**
470 	 * Set the column order from an array
471 	 *  @method  _fnOrderColumns
472 	 *  @param   array a An array of integers which dictate the column order that should be applied
473 	 *  @returns void
474 	 *  @private 
475 	 */
476 	_fnOrderColumns: function ( a )
477 	{
478 		if ( a.length != this.s.dt.aoColumns.length )
479 		{
480 			this.s.dt.oInstance.oApi._fnLog( oDTSettings, 1, "ColReorder - array reorder does not "+
481 			 	"match known number of columns. Skipping." );
482 			return;
483 		}
484 		
485 		for ( var i=0, iLen=a.length ; i<iLen ; i++ )
486 		{
487 			var currIndex = $.inArray( i, a );
488 			if ( i != currIndex )
489 			{
490 				/* Reorder our switching array */
491 				fnArraySwitch( a, currIndex, i );
492 				
493 				/* Do the column reorder in the table */
494 				this.s.dt.oInstance.fnColReorder( currIndex, i );
495 			}
496 		}
497 		
498 		/* When scrolling we need to recalculate the column sizes to allow for the shift */
499 		if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" )
500 		{
501 			this.s.dt.oInstance.fnAdjustColumnSizing();
502 		}
503 			
504 		/* Save the state */
505 		this.s.dt.oInstance.oApi._fnSaveState( this.s.dt );
506 	},
507 	
508 	
509 	/**
510 	 * This function effectively replaces the state saving function in DataTables (this is needed
511 	 * because otherwise DataTables would state save the columns in their reordered state, not the
512 	 * original which is needed on first draw). This is sensitive to any changes in the DataTables
513 	 * state saving method!
514 	 *  @method  _fnStateSave
515 	 *  @param   string sCurrentVal 
516 	 *  @returns string JSON encoded cookie string for DataTables
517 	 *  @private 
518 	 */
519 	_fnStateSave: function ( sCurrentVal )
520 	{
521 		var i, iLen, sTmp;
522 		var sValue = sCurrentVal.split('"aaSorting"')[0];
523 		var a = [];
524 		var oSettings = this.s.dt;
525 		
526 		/* Sorting */
527 		sValue += '"aaSorting":[ ';
528 		for ( i=0 ; i<oSettings.aaSorting.length ; i++ )
529 		{
530 			sValue += '['+oSettings.aoColumns[ oSettings.aaSorting[i][0] ]._ColReorder_iOrigCol+
531 				',"'+oSettings.aaSorting[i][1]+'"],';
532 		}
533 		sValue = sValue.substring(0, sValue.length-1);
534 		sValue += "],";
535 		
536 		/* Column filter */
537 		for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
538 		{
539 			a[ oSettings.aoColumns[i]._ColReorder_iOrigCol ] = {
540 				sSearch: encodeURIComponent(oSettings.aoPreSearchCols[i].sSearch),
541 				bRegex: !oSettings.aoPreSearchCols[i].bRegex
542 			};
543 		}
544 		
545 		sValue += '"aaSearchCols":[ ';
546 		for ( i=0 ; i<a.length ; i++ )
547 		{
548 			sValue += '["'+a[i].sSearch+'",'+a[i].bRegex+'],';
549 		}
550 		sValue = sValue.substring(0, sValue.length-1);
551 		sValue += "],";
552 		
553 		/* Visibility */
554 		a = [];
555 		for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
556 		{
557 			a[ oSettings.aoColumns[i]._ColReorder_iOrigCol ] = oSettings.aoColumns[i].bVisible;
558 		}
559 		
560 		sValue += '"abVisCols":[ ';
561 		for ( i=0 ; i<a.length ; i++ )
562 		{
563 			sValue += a[i]+",";
564 		}
565 		sValue = sValue.substring(0, sValue.length-1);
566 		sValue += "],";
567 		
568 		/* Column reordering */
569 		a = [];
570 		for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) {
571 			a.push( oSettings.aoColumns[i]._ColReorder_iOrigCol );
572 		}
573 		sValue += '"ColReorder":['+a.join(',')+']';
574 		
575 		return sValue;
576 	},
577 	
578 	
579 	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
580 	 * Mouse drop and drag
581 	 */
582 	
583 	/**
584 	 * Add a mouse down listener to a particluar TH element
585 	 *  @method  _fnMouseListener
586 	 *  @param   int i Column index
587 	 *  @param   element nTh TH element clicked on
588 	 *  @returns void
589 	 *  @private 
590 	 */
591 	_fnMouseListener: function ( i, nTh )
592 	{
593 		var that = this;
594 		$(nTh).bind( 'mousedown.ColReorder', function (e) {
595 			that._fnMouseDown.call( that, e, nTh );
596 			return false;
597 		} );
598 	},
599 	
600 	
601 	/**
602 	 * Mouse down on a TH element in the table header
603 	 *  @method  _fnMouseDown
604 	 *  @param   event e Mouse event
605 	 *  @param   element nTh TH element to be dragged
606 	 *  @returns void
607 	 *  @private 
608 	 */
609 	_fnMouseDown: function ( e, nTh )
610 	{
611 		var
612 			that = this,
613 			aoColumns = this.s.dt.aoColumns;
614 		
615 		/* Store information about the mouse position */
616 		var nThTarget = e.target.nodeName == "TH" ? e.target : $(e.target).parents('TH')[0];
617 		var offset = $(nThTarget).offset();
618 		this.s.mouse.startX = e.pageX;
619 		this.s.mouse.startY = e.pageY;
620 		this.s.mouse.offsetX = e.pageX - offset.left;
621 		this.s.mouse.offsetY = e.pageY - offset.top;
622 		this.s.mouse.target = nTh;
623 		this.s.mouse.targetIndex = $('th', nTh.parentNode).index( nTh );
624 		this.s.mouse.fromIndex = this.s.dt.oInstance.oApi._fnVisibleToColumnIndex( this.s.dt, 
625 			this.s.mouse.targetIndex );
626 		
627 		/* Calculate a cached array with the points of the column inserts, and the 'to' points */
628 		this.s.aoTargets.splice( 0, this.s.aoTargets.length );
629 		
630 		this.s.aoTargets.push( {
631 			x:  $(this.s.dt.nTable).offset().left,
632 			to: 0
633 		} );
634 		
635 		var iToPoint = 0;
636 		for ( var i=0, iLen=aoColumns.length ; i<iLen ; i++ )
637 		{
638 			/* For the column / header in question, we want it's position to remain the same if the 
639 			 * position is just to it's immediate left or right, so we only incremement the counter for
640 			 * other columns
641 			 */
642 			if ( i != this.s.mouse.fromIndex )
643 			{
644 				iToPoint++;
645 			}
646 			
647 			if ( aoColumns[i].bVisible )
648 			{
649 				this.s.aoTargets.push( {
650 					x:  $(aoColumns[i].nTh).offset().left + $(aoColumns[i].nTh).outerWidth(),
651 					to: iToPoint
652 				} );
653 			}
654 		}
655 		
656 		/* Disallow columns for being reordered by drag and drop, counting left to right */
657 		if ( this.s.fixed !== 0 )
658 		{
659 			this.s.aoTargets.splice( 0, this.s.fixed );
660 		}
661 		
662 		/* Add event handlers to the document */
663 		$(document).bind( 'mousemove.ColReorder', function (e) {
664 			that._fnMouseMove.call( that, e );
665 		} );
666 		
667 		$(document).bind( 'mouseup.ColReorder', function (e) {
668 			that._fnMouseUp.call( that, e );
669 		} );
670 	},
671 	
672 	
673 	/**
674 	 * Deal with a mouse move event while dragging a node
675 	 *  @method  _fnMouseMove
676 	 *  @param   event e Mouse event
677 	 *  @returns void
678 	 *  @private 
679 	 */
680 	_fnMouseMove: function ( e )
681 	{
682 		var that = this;
683 		
684 		if ( this.dom.drag === null )
685 		{
686 			/* Only create the drag element if the mouse has moved a specific distance from the start
687 			 * point - this allows the user to make small mouse movements when sorting and not have a
688 			 * possibly confusing drag element showing up
689 			 */
690 			if ( Math.pow(
691 				Math.pow(e.pageX - this.s.mouse.startX, 2) + 
692 				Math.pow(e.pageY - this.s.mouse.startY, 2), 0.5 ) < 5 )
693 			{
694 				return;
695 			}
696 			this._fnCreateDragNode();
697 		}
698 		
699 		/* Position the element - we respect where in the element the click occured */
700 		this.dom.drag.style.left = (e.pageX - this.s.mouse.offsetX) + "px";
701 		this.dom.drag.style.top = (e.pageY - this.s.mouse.offsetY) + "px";
702 		
703 		/* Based on the current mouse position, calculate where the insert should go */
704 		var bSet = false;
705 		for ( var i=1, iLen=this.s.aoTargets.length ; i<iLen ; i++ )
706 		{
707 			if ( e.pageX < this.s.aoTargets[i-1].x + ((this.s.aoTargets[i].x-this.s.aoTargets[i-1].x)/2) )
708 			{
709 				this.dom.pointer.style.left = this.s.aoTargets[i-1].x +"px";
710 				this.s.mouse.toIndex = this.s.aoTargets[i-1].to;
711 				bSet = true;
712 				break;
713 			}
714 		}
715 		
716 		/* The insert element wasn't positioned in the array (less than operator), so we put it at 
717 		 * the end
718 		 */
719 		if ( !bSet )
720 		{
721 			this.dom.pointer.style.left = this.s.aoTargets[this.s.aoTargets.length-1].x +"px";
722 			this.s.mouse.toIndex = this.s.aoTargets[this.s.aoTargets.length-1].to;
723 		}
724 	},
725 	
726 	
727 	/**
728 	 * Finish off the mouse drag and insert the column where needed
729 	 *  @method  _fnMouseUp
730 	 *  @param   event e Mouse event
731 	 *  @returns void
732 	 *  @private 
733 	 */
734 	_fnMouseUp: function ( e )
735 	{
736 		var that = this;
737 		
738 		$(document).unbind( 'mousemove.ColReorder' );
739 		$(document).unbind( 'mouseup.ColReorder' );
740 		
741 		if ( this.dom.drag !== null )
742 		{
743 			/* Remove the guide elements */
744 			document.body.removeChild( this.dom.drag );
745 			document.body.removeChild( this.dom.pointer );
746 			this.dom.drag = null;
747 			this.dom.pointer = null;
748 			
749 			/* Actually do the reorder */
750 			this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex );
751 			
752 			/* When scrolling we need to recalculate the column sizes to allow for the shift */
753 			if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" )
754 			{
755 				this.s.dt.oInstance.fnAdjustColumnSizing();
756 			}
757 			
758 			/* Save the state */
759 			this.s.dt.oInstance.oApi._fnSaveState( this.s.dt );
760 		}
761 	},
762 	
763 	
764 	/**
765 	 * Copy the TH element that is being drags so the user has the idea that they are actually 
766 	 * moving it around the page.
767 	 *  @method  _fnCreateDragNode
768 	 *  @returns void
769 	 *  @private 
770 	 */
771 	_fnCreateDragNode: function ()
772 	{
773 		var that = this;
774 		
775 		this.dom.drag = $(this.s.dt.nTHead.parentNode).clone(true)[0];
776 		this.dom.drag.className += " DTCR_clonedTable";
777 		while ( this.dom.drag.getElementsByTagName('tbody').length > 0 )
778 		{
779 			this.dom.drag.removeChild( this.dom.drag.getElementsByTagName('tbody')[0] );
780 		}
781 		while ( this.dom.drag.getElementsByTagName('tfoot').length > 0 )
782 		{
783 			this.dom.drag.removeChild( this.dom.drag.getElementsByTagName('tfoot')[0] );
784 		}
785 		
786 		$('thead tr:eq(0)', this.dom.drag).each( function () {
787 			$('th:not(:eq('+that.s.mouse.targetIndex+'))', this).remove();
788 		} );
789 		$('tr', this.dom.drag).height( $('tr:eq(0)', that.s.dt.nTHead).height() );
790 		
791 		$('thead tr:gt(0)', this.dom.drag).remove();
792 		
793 		$('thead th:eq(0)', this.dom.drag).each( function (i) {
794 			this.style.width = $('th:eq('+that.s.mouse.targetIndex+')', that.s.dt.nTHead).width()+"px";
795 		} );
796 		
797 		this.dom.drag.style.position = "absolute";
798 		this.dom.drag.style.top = "0px";
799 		this.dom.drag.style.left = "0px";
800 		this.dom.drag.style.width = $('th:eq('+that.s.mouse.targetIndex+')', that.s.dt.nTHead).outerWidth()+"px";
801 		
802 		
803 		this.dom.pointer = document.createElement( 'div' );
804 		this.dom.pointer.className = "DTCR_pointer";
805 		this.dom.pointer.style.position = "absolute";
806 		
807 		if ( this.s.dt.oScroll.sX === "" && this.s.dt.oScroll.sY === "" )
808 		{
809 			this.dom.pointer.style.top = $(this.s.dt.nTable).offset().top+"px";
810 			this.dom.pointer.style.height = $(this.s.dt.nTable).height()+"px";
811 		}
812 		else
813 		{
814 			this.dom.pointer.style.top = $('div.dataTables_scroll', this.s.dt.nTableWrapper).offset().top+"px";
815 			this.dom.pointer.style.height = $('div.dataTables_scroll', this.s.dt.nTableWrapper).height()+"px";
816 		}
817 	
818 		document.body.appendChild( this.dom.pointer );
819 		document.body.appendChild( this.dom.drag );
820 	}
821 };
822 
823 
824 
825 
826 
827 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
828  * Static parameters
829  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
830 
831 /**
832  * Array of all ColReorder instances for later reference
833  *  @property ColReorder.aoInstances
834  *  @type     array
835  *  @default  []
836  *  @static
837  */
838 ColReorder.aoInstances = [];
839 
840 
841 
842 
843 
844 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
845  * Static functions
846  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
847 
848 /**
849  * Reset the column ordering for a DataTables instance
850  *  @method  ColReorder.fnReset
851  *  @param   object oTable DataTables instance to consider
852  *  @returns void
853  *  @static
854  */
855 ColReorder.fnReset = function ( oTable )
856 {
857 	for ( var i=0, iLen=ColReorder.aoInstances.length ; i<iLen ; i++ )
858 	{
859 		if ( ColReorder.aoInstances[i].s.dt.oInstance == oTable )
860 		{
861 			ColReorder.aoInstances[i].fnReset();
862 		}
863 	}
864 };
865 
866 
867 
868 
869 
870 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
871  * Constants
872  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
873 
874 /**
875  * Name of this class
876  *  @constant CLASS
877  *  @type     String
878  *  @default  ColReorder
879  */
880 ColReorder.prototype.CLASS = "ColReorder";
881 
882 
883 /**
884  * ColReorder version
885  *  @constant  VERSION
886  *  @type      String
887  *  @default   1.0.1.dev
888  */
889 ColReorder.VERSION = "1.0.1";
890 ColReorder.prototype.VERSION = ColReorder.VERSION;
891 
892 
893 
894 
895 
896 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
897  * Initialisation
898  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
899 
900 /*
901  * Register a new feature with DataTables
902  */
903 if ( typeof $.fn.dataTable == "function" &&
904      typeof $.fn.dataTableExt.fnVersionCheck == "function" &&
905      $.fn.dataTableExt.fnVersionCheck('1.7.4') )
906 {
907 	$.fn.dataTableExt.aoFeatures.push( {
908 		fnInit: function( oDTSettings ) {
909 			var oTable = oDTSettings.oInstance;
910 			if ( typeof oTable._oPluginColReorder == 'undefined' ) {
911 				var opts = typeof oDTSettings.oInit.oColReorder != 'undefined' ? 
912 					oDTSettings.oInit.oColReorder : {};
913 				oTable._oPluginColReorder = new ColReorder( oDTSettings.oInstance, opts );
914 			} else {
915 				oTable.oApi._fnLog( oDTSettings, 1, "ColReorder attempted to initialise twice. Ignoring second" );
916 			}
917 			
918 			return null; /* No node to insert */
919 		},
920 		cFeature: "R",
921 		sFeature: "ColReorder"
922 	} );
923 }
924 else
925 {
926 	alert( "Warning: ColReorder requires DataTables 1.7.4 or greater - www.datatables.net/download");
927 }
928 
929 })(jQuery, window, document);
930