Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
875 lars 1
/*! ColReorder 1.2.0
2
 * ©2010-2014 SpryMedia Ltd - datatables.net/license
3
 */
4
 
5
/**
6
 * @summary     ColReorder
7
 * @description Provide the ability to reorder columns in a DataTable
8
 * @version     1.2.0
9
 * @file        dataTables.colReorder.js
10
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
11
 * @contact     www.sprymedia.co.uk/contact
12
 * @copyright   Copyright 2010-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
 
24
(function(window, document, undefined) {
25
 
26
 
27
/**
28
 * Switch the key value pairing of an index array to be value key (i.e. the old value is now the
29
 * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ].
30
 *  @method  fnInvertKeyValues
31
 *  @param   array aIn Array to switch around
32
 *  @returns array
33
 */
34
function fnInvertKeyValues( aIn )
35
{
36
	var aRet=[];
37
	for ( var i=0, iLen=aIn.length ; i<iLen ; i++ )
38
	{
39
		aRet[ aIn[i] ] = i;
40
	}
41
	return aRet;
42
}
43
 
44
 
45
/**
46
 * Modify an array by switching the position of two elements
47
 *  @method  fnArraySwitch
48
 *  @param   array aArray Array to consider, will be modified by reference (i.e. no return)
49
 *  @param   int iFrom From point
50
 *  @param   int iTo Insert point
51
 *  @returns void
52
 */
53
function fnArraySwitch( aArray, iFrom, iTo )
54
{
55
	var mStore = aArray.splice( iFrom, 1 )[0];
56
	aArray.splice( iTo, 0, mStore );
57
}
58
 
59
 
60
/**
61
 * Switch the positions of nodes in a parent node (note this is specifically designed for
62
 * table rows). Note this function considers all element nodes under the parent!
63
 *  @method  fnDomSwitch
64
 *  @param   string sTag Tag to consider
65
 *  @param   int iFrom Element to move
66
 *  @param   int Point to element the element to (before this point), can be null for append
67
 *  @returns void
68
 */
69
function fnDomSwitch( nParent, iFrom, iTo )
70
{
71
	var anTags = [];
72
	for ( var i=0, iLen=nParent.childNodes.length ; i<iLen ; i++ )
73
	{
74
		if ( nParent.childNodes[i].nodeType == 1 )
75
		{
76
			anTags.push( nParent.childNodes[i] );
77
		}
78
	}
79
	var nStore = anTags[ iFrom ];
80
 
81
	if ( iTo !== null )
82
	{
83
		nParent.insertBefore( nStore, anTags[iTo] );
84
	}
85
	else
86
	{
87
		nParent.appendChild( nStore );
88
	}
89
}
90
 
91
 
92
 
93
var factory = function( $, DataTable ) {
94
"use strict";
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, oCol;
108
	var attrMap = function ( obj, prop, mapping ) {
109
		if ( ! obj[ prop ] || typeof obj[ prop ] === 'function' ) {
110
			return;
111
		}
112
 
113
		var a = obj[ prop ].split('.');
114
		var num = a.shift();
115
 
116
		if ( isNaN( num*1 ) ) {
117
			return;
118
		}
119
 
120
		obj[ prop ] = mapping[ num*1 ]+'.'+a.join('.');
121
	};
122
 
123
	/* Sanity check in the input */
124
	if ( iFrom == iTo )
125
	{
126
		/* Pointless reorder */
127
		return;
128
	}
129
 
130
	if ( iFrom < 0 || iFrom >= iCols )
131
	{
132
		this.oApi._fnLog( oSettings, 1, "ColReorder 'from' index is out of bounds: "+iFrom );
133
		return;
134
	}
135
 
136
	if ( iTo < 0 || iTo >= iCols )
137
	{
138
		this.oApi._fnLog( oSettings, 1, "ColReorder 'to' index is out of bounds: "+iTo );
139
		return;
140
	}
141
 
142
	/*
143
	 * Calculate the new column array index, so we have a mapping between the old and new
144
	 */
145
	var aiMapping = [];
146
	for ( i=0, iLen=iCols ; i<iLen ; i++ )
147
	{
148
		aiMapping[i] = i;
149
	}
150
	fnArraySwitch( aiMapping, iFrom, iTo );
151
	var aiInvertMapping = fnInvertKeyValues( aiMapping );
152
 
153
 
154
	/*
155
	 * Convert all internal indexing to the new column order indexes
156
	 */
157
	/* Sorting */
158
	for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ )
159
	{
160
		oSettings.aaSorting[i][0] = aiInvertMapping[ oSettings.aaSorting[i][0] ];
161
	}
162
 
163
	/* Fixed sorting */
164
	if ( oSettings.aaSortingFixed !== null )
165
	{
166
		for ( i=0, iLen=oSettings.aaSortingFixed.length ; i<iLen ; i++ )
167
		{
168
			oSettings.aaSortingFixed[i][0] = aiInvertMapping[ oSettings.aaSortingFixed[i][0] ];
169
		}
170
	}
171
 
172
	/* Data column sorting (the column which the sort for a given column should take place on) */
173
	for ( i=0, iLen=iCols ; i<iLen ; i++ )
174
	{
175
		oCol = oSettings.aoColumns[i];
176
		for ( j=0, jLen=oCol.aDataSort.length ; j<jLen ; j++ )
177
		{
178
			oCol.aDataSort[j] = aiInvertMapping[ oCol.aDataSort[j] ];
179
		}
180
 
181
		// Update the column indexes
182
		oCol.idx = aiInvertMapping[ oCol.idx ];
183
	}
184
 
185
	// Update 1.10 optimised sort class removal variable
186
	$.each( oSettings.aLastSort, function (i, val) {
187
		oSettings.aLastSort[i].src = aiInvertMapping[ val.src ];
188
	} );
189
 
190
	/* Update the Get and Set functions for each column */
191
	for ( i=0, iLen=iCols ; i<iLen ; i++ )
192
	{
193
		oCol = oSettings.aoColumns[i];
194
 
195
		if ( typeof oCol.mData == 'number' ) {
196
			oCol.mData = aiInvertMapping[ oCol.mData ];
197
 
198
			// regenerate the get / set functions
199
			oSettings.oApi._fnColumnOptions( oSettings, i, {} );
200
		}
201
		else if ( $.isPlainObject( oCol.mData ) ) {
202
			// HTML5 data sourced
203
			attrMap( oCol.mData, '_',      aiInvertMapping );
204
			attrMap( oCol.mData, 'filter', aiInvertMapping );
205
			attrMap( oCol.mData, 'sort',   aiInvertMapping );
206
			attrMap( oCol.mData, 'type',   aiInvertMapping );
207
 
208
			// regenerate the get / set functions
209
			oSettings.oApi._fnColumnOptions( oSettings, i, {} );
210
		}
211
	}
212
 
213
 
214
	/*
215
	 * Move the DOM elements
216
	 */
217
	if ( oSettings.aoColumns[iFrom].bVisible )
218
	{
219
		/* Calculate the current visible index and the point to insert the node before. The insert
220
		 * before needs to take into account that there might not be an element to insert before,
221
		 * in which case it will be null, and an appendChild should be used
222
		 */
223
		var iVisibleIndex = this.oApi._fnColumnIndexToVisible( oSettings, iFrom );
224
		var iInsertBeforeIndex = null;
225
 
226
		i = iTo < iFrom ? iTo : iTo + 1;
227
		while ( iInsertBeforeIndex === null && i < iCols )
228
		{
229
			iInsertBeforeIndex = this.oApi._fnColumnIndexToVisible( oSettings, i );
230
			i++;
231
		}
232
 
233
		/* Header */
234
		nTrs = oSettings.nTHead.getElementsByTagName('tr');
235
		for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
236
		{
237
			fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex );
238
		}
239
 
240
		/* Footer */
241
		if ( oSettings.nTFoot !== null )
242
		{
243
			nTrs = oSettings.nTFoot.getElementsByTagName('tr');
244
			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
245
			{
246
				fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex );
247
			}
248
		}
249
 
250
		/* Body */
251
		for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
252
		{
253
			if ( oSettings.aoData[i].nTr !== null )
254
			{
255
				fnDomSwitch( oSettings.aoData[i].nTr, iVisibleIndex, iInsertBeforeIndex );
256
			}
257
		}
258
	}
259
 
260
	/*
261
	 * Move the internal array elements
262
	 */
263
	/* Columns */
264
	fnArraySwitch( oSettings.aoColumns, iFrom, iTo );
265
 
266
	/* Search columns */
267
	fnArraySwitch( oSettings.aoPreSearchCols, iFrom, iTo );
268
 
269
	/* Array array - internal data anodes cache */
270
	for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
271
	{
272
		var data = oSettings.aoData[i];
273
 
274
		if ( data.anCells ) {
275
			fnArraySwitch( data.anCells, iFrom, iTo );
276
		}
277
 
278
		// For DOM sourced data, the invalidate will reread the cell into
279
		// the data array, but for data sources as an array, they need to
280
		// be flipped
281
		if ( data.src !== 'dom' && $.isArray( data._aData ) ) {
282
			fnArraySwitch( data._aData, iFrom, iTo );
283
		}
284
	}
285
 
286
	/* Reposition the header elements in the header layout array */
287
	for ( i=0, iLen=oSettings.aoHeader.length ; i<iLen ; i++ )
288
	{
289
		fnArraySwitch( oSettings.aoHeader[i], iFrom, iTo );
290
	}
291
 
292
	if ( oSettings.aoFooter !== null )
293
	{
294
		for ( i=0, iLen=oSettings.aoFooter.length ; i<iLen ; i++ )
295
		{
296
			fnArraySwitch( oSettings.aoFooter[i], iFrom, iTo );
297
		}
298
	}
299
 
300
	// Invalidate row cached data for sorting, filtering etc
301
	var api = new $.fn.dataTable.Api( oSettings );
302
	api.rows().invalidate();
303
 
304
	/*
305
	 * Update DataTables' event handlers
306
	 */
307
 
308
	/* Sort listener */
309
	for ( i=0, iLen=iCols ; i<iLen ; i++ )
310
	{
311
		$(oSettings.aoColumns[i].nTh).off('click.DT');
312
		this.oApi._fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i );
313
	}
314
 
315
 
316
	/* Fire an event so other plug-ins can update */
317
	$(oSettings.oInstance).trigger( 'column-reorder.dt', [ oSettings, {
318
		from: iFrom,
319
		to: iTo,
320
		mapping: aiInvertMapping,
321
 
322
		// Old style parameters for compatibility
323
		iFrom: iFrom,
324
		iTo: iTo,
325
		aiInvertMapping: aiInvertMapping
326
	} ] );
327
};
328
 
329
 
330
/**
331
 * ColReorder provides column visibility control for DataTables
332
 * @class ColReorder
333
 * @constructor
334
 * @param {object} dt DataTables settings object
335
 * @param {object} opts ColReorder options
336
 */
337
var ColReorder = function( dt, opts )
338
{
339
	var settings = new $.fn.dataTable.Api( dt ).settings()[0];
340
 
341
	// Ensure that we can't initialise on the same table twice
342
	if ( settings._colReorder ) {
343
		return settings._colReorder;
344
	}
345
 
346
	// Allow the options to be a boolean for defaults
347
	if ( opts === true ) {
348
		opts = {};
349
	}
350
 
351
	// Convert from camelCase to Hungarian, just as DataTables does
352
	var camelToHungarian = $.fn.dataTable.camelToHungarian;
353
	if ( camelToHungarian ) {
354
		camelToHungarian( ColReorder.defaults, ColReorder.defaults, true );
355
		camelToHungarian( ColReorder.defaults, opts || {} );
356
	}
357
 
358
 
359
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
360
	 * Public class variables
361
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
362
 
363
	/**
364
	 * @namespace Settings object which contains customisable information for ColReorder instance
365
	 */
366
	this.s = {
367
		/**
368
		 * DataTables settings object
369
		 *  @property dt
370
		 *  @type     Object
371
		 *  @default  null
372
		 */
373
		"dt": null,
374
 
375
		/**
376
		 * Initialisation object used for this instance
377
		 *  @property init
378
		 *  @type     object
379
		 *  @default  {}
380
		 */
381
		"init": $.extend( true, {}, ColReorder.defaults, opts ),
382
 
383
		/**
384
		 * Number of columns to fix (not allow to be reordered)
385
		 *  @property fixed
386
		 *  @type     int
387
		 *  @default  0
388
		 */
389
		"fixed": 0,
390
 
391
		/**
392
		 * Number of columns to fix counting from right (not allow to be reordered)
393
		 *  @property fixedRight
394
		 *  @type     int
395
		 *  @default  0
396
		 */
397
		"fixedRight": 0,
398
 
399
		/**
400
		 * Callback function for once the reorder has been done
401
		 *  @property reorderCallback
402
		 *  @type     function
403
		 *  @default  null
404
		 */
405
		"reorderCallback": null,
406
 
407
		/**
408
		 * @namespace Information used for the mouse drag
409
		 */
410
		"mouse": {
411
			"startX": -1,
412
			"startY": -1,
413
			"offsetX": -1,
414
			"offsetY": -1,
415
			"target": -1,
416
			"targetIndex": -1,
417
			"fromIndex": -1
418
		},
419
 
420
		/**
421
		 * Information which is used for positioning the insert cusor and knowing where to do the
422
		 * insert. Array of objects with the properties:
423
		 *   x: x-axis position
424
		 *   to: insert point
425
		 *  @property aoTargets
426
		 *  @type     array
427
		 *  @default  []
428
		 */
429
		"aoTargets": []
430
	};
431
 
432
 
433
	/**
434
	 * @namespace Common and useful DOM elements for the class instance
435
	 */
436
	this.dom = {
437
		/**
438
		 * Dragging element (the one the mouse is moving)
439
		 *  @property drag
440
		 *  @type     element
441
		 *  @default  null
442
		 */
443
		"drag": null,
444
 
445
		/**
446
		 * The insert cursor
447
		 *  @property pointer
448
		 *  @type     element
449
		 *  @default  null
450
		 */
451
		"pointer": null
452
	};
453
 
454
 
455
	/* Constructor logic */
456
	this.s.dt = settings;
457
	this.s.dt._colReorder = this;
458
	this._fnConstruct();
459
 
460
	return this;
461
};
462
 
463
 
464
 
465
ColReorder.prototype = {
466
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
467
	 * Public methods
468
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
469
 
470
	/**
471
	 * Reset the column ordering to the original ordering that was detected on
472
	 * start up.
473
	 *  @return {this} Returns `this` for chaining.
474
	 *
475
	 *  @example
476
	 *    // DataTables initialisation with ColReorder
477
	 *    var table = $('#example').dataTable( {
478
	 *        "sDom": 'Rlfrtip'
479
	 *    } );
480
	 *
481
	 *    // Add click event to a button to reset the ordering
482
	 *    $('#resetOrdering').click( function (e) {
483
	 *        e.preventDefault();
484
	 *        $.fn.dataTable.ColReorder( table ).fnReset();
485
	 *    } );
486
	 */
487
	"fnReset": function ()
488
	{
489
		var a = [];
490
		for ( var i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
491
		{
492
			a.push( this.s.dt.aoColumns[i]._ColReorder_iOrigCol );
493
		}
494
 
495
		this._fnOrderColumns( a );
496
 
497
		return this;
498
	},
499
 
500
	/**
501
	 * `Deprecated` - Get the current order of the columns, as an array.
502
	 *  @return {array} Array of column identifiers
503
	 *  @deprecated `fnOrder` should be used in preference to this method.
504
	 *      `fnOrder` acts as a getter/setter.
505
	 */
506
	"fnGetCurrentOrder": function ()
507
	{
508
		return this.fnOrder();
509
	},
510
 
511
	/**
512
	 * Get the current order of the columns, as an array. Note that the values
513
	 * given in the array are unique identifiers for each column. Currently
514
	 * these are the original ordering of the columns that was detected on
515
	 * start up, but this could potentially change in future.
516
	 *  @return {array} Array of column identifiers
517
	 *
518
	 *  @example
519
	 *    // Get column ordering for the table
520
	 *    var order = $.fn.dataTable.ColReorder( dataTable ).fnOrder();
521
	 *//**
522
	 * Set the order of the columns, from the positions identified in the
523
	 * ordering array given. Note that ColReorder takes a brute force approach
524
	 * to reordering, so it is possible multiple reordering events will occur
525
	 * before the final order is settled upon.
526
	 *  @param {array} [set] Array of column identifiers in the new order. Note
527
	 *    that every column must be included, uniquely, in this array.
528
	 *  @return {this} Returns `this` for chaining.
529
	 *
530
	 *  @example
531
	 *    // Swap the first and second columns
532
	 *    $.fn.dataTable.ColReorder( dataTable ).fnOrder( [1, 0, 2, 3, 4] );
533
	 *
534
	 *  @example
535
	 *    // Move the first column to the end for the table `#example`
536
	 *    var curr = $.fn.dataTable.ColReorder( '#example' ).fnOrder();
537
	 *    var first = curr.shift();
538
	 *    curr.push( first );
539
	 *    $.fn.dataTable.ColReorder( '#example' ).fnOrder( curr );
540
	 *
541
	 *  @example
542
	 *    // Reverse the table's order
543
	 *    $.fn.dataTable.ColReorder( '#example' ).fnOrder(
544
	 *      $.fn.dataTable.ColReorder( '#example' ).fnOrder().reverse()
545
	 *    );
546
	 */
547
	"fnOrder": function ( set )
548
	{
549
		if ( set === undefined )
550
		{
551
			var a = [];
552
			for ( var i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
553
			{
554
				a.push( this.s.dt.aoColumns[i]._ColReorder_iOrigCol );
555
			}
556
			return a;
557
		}
558
 
559
		this._fnOrderColumns( fnInvertKeyValues( set ) );
560
 
561
		return this;
562
	},
563
 
564
 
565
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
566
	 * Private methods (they are of course public in JS, but recommended as private)
567
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
568
 
569
	/**
570
	 * Constructor logic
571
	 *  @method  _fnConstruct
572
	 *  @returns void
573
	 *  @private
574
	 */
575
	"_fnConstruct": function ()
576
	{
577
		var that = this;
578
		var iLen = this.s.dt.aoColumns.length;
579
		var table = this.s.dt.nTable;
580
		var i;
581
 
582
		/* Columns discounted from reordering - counting left to right */
583
		if ( this.s.init.iFixedColumns )
584
		{
585
			this.s.fixed = this.s.init.iFixedColumns;
586
		}
587
 
588
		if ( this.s.init.iFixedColumnsLeft )
589
		{
590
			this.s.fixed = this.s.init.iFixedColumnsLeft;
591
		}
592
 
593
		/* Columns discounted from reordering - counting right to left */
594
		this.s.fixedRight = this.s.init.iFixedColumnsRight ?
595
			this.s.init.iFixedColumnsRight :
596
			0;
597
 
598
		/* Drop callback initialisation option */
599
		if ( this.s.init.fnReorderCallback )
600
		{
601
			this.s.reorderCallback = this.s.init.fnReorderCallback;
602
		}
603
 
604
		/* Add event handlers for the drag and drop, and also mark the original column order */
605
		for ( i = 0; i < iLen; i++ )
606
		{
607
			if ( i > this.s.fixed-1 && i < iLen - this.s.fixedRight )
608
			{
609
				this._fnMouseListener( i, this.s.dt.aoColumns[i].nTh );
610
			}
611
 
612
			/* Mark the original column order for later reference */
613
			this.s.dt.aoColumns[i]._ColReorder_iOrigCol = i;
614
		}
615
 
616
		/* State saving */
617
		this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) {
618
			that._fnStateSave.call( that, oData );
619
		}, "ColReorder_State" );
620
 
621
		/* An initial column order has been specified */
622
		var aiOrder = null;
623
		if ( this.s.init.aiOrder )
624
		{
625
			aiOrder = this.s.init.aiOrder.slice();
626
		}
627
 
628
		/* State loading, overrides the column order given */
629
		if ( this.s.dt.oLoadedState && typeof this.s.dt.oLoadedState.ColReorder != 'undefined' &&
630
		  this.s.dt.oLoadedState.ColReorder.length == this.s.dt.aoColumns.length )
631
		{
632
			aiOrder = this.s.dt.oLoadedState.ColReorder;
633
		}
634
 
635
		/* If we have an order to apply - do so */
636
		if ( aiOrder )
637
		{
638
			/* We might be called during or after the DataTables initialisation. If before, then we need
639
			 * to wait until the draw is done, if after, then do what we need to do right away
640
			 */
641
			if ( !that.s.dt._bInitComplete )
642
			{
643
				var bDone = false;
644
				$(table).on( 'draw.dt.colReorder', function () {
645
					if ( !that.s.dt._bInitComplete && !bDone )
646
					{
647
						bDone = true;
648
						var resort = fnInvertKeyValues( aiOrder );
649
						that._fnOrderColumns.call( that, resort );
650
					}
651
				} );
652
			}
653
			else
654
			{
655
				var resort = fnInvertKeyValues( aiOrder );
656
				that._fnOrderColumns.call( that, resort );
657
			}
658
		}
659
		else {
660
			this._fnSetColumnIndexes();
661
		}
662
 
663
		// Destroy clean up
664
		$(table).on( 'destroy.dt.colReorder', function () {
665
			$(table).off( 'destroy.dt.colReorder draw.dt.colReorder' );
666
			$(that.s.dt.nTHead).find( '*' ).off( '.ColReorder' );
667
 
668
			$.each( that.s.dt.aoColumns, function (i, column) {
669
				$(column.nTh).removeAttr('data-column-index');
670
			} );
671
 
672
			that.s.dt._colReorder = null;
673
			that.s = null;
674
		} );
675
	},
676
 
677
 
678
	/**
679
	 * Set the column order from an array
680
	 *  @method  _fnOrderColumns
681
	 *  @param   array a An array of integers which dictate the column order that should be applied
682
	 *  @returns void
683
	 *  @private
684
	 */
685
	"_fnOrderColumns": function ( a )
686
	{
687
		if ( a.length != this.s.dt.aoColumns.length )
688
		{
689
			this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "ColReorder - array reorder does not "+
690
				"match known number of columns. Skipping." );
691
			return;
692
		}
693
 
694
		for ( var i=0, iLen=a.length ; i<iLen ; i++ )
695
		{
696
			var currIndex = $.inArray( i, a );
697
			if ( i != currIndex )
698
			{
699
				/* Reorder our switching array */
700
				fnArraySwitch( a, currIndex, i );
701
 
702
				/* Do the column reorder in the table */
703
				this.s.dt.oInstance.fnColReorder( currIndex, i );
704
			}
705
		}
706
 
707
		/* When scrolling we need to recalculate the column sizes to allow for the shift */
708
		if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" )
709
		{
710
			this.s.dt.oInstance.fnAdjustColumnSizing( false );
711
		}
712
 
713
		/* Save the state */
714
		this.s.dt.oInstance.oApi._fnSaveState( this.s.dt );
715
 
716
		this._fnSetColumnIndexes();
717
 
718
		if ( this.s.reorderCallback !== null )
719
		{
720
			this.s.reorderCallback.call( this );
721
		}
722
	},
723
 
724
 
725
	/**
726
	 * Because we change the indexes of columns in the table, relative to their starting point
727
	 * we need to reorder the state columns to what they are at the starting point so we can
728
	 * then rearrange them again on state load!
729
	 *  @method  _fnStateSave
730
	 *  @param   object oState DataTables state
731
	 *  @returns string JSON encoded cookie string for DataTables
732
	 *  @private
733
	 */
734
	"_fnStateSave": function ( oState )
735
	{
736
		var i, iLen, aCopy, iOrigColumn;
737
		var oSettings = this.s.dt;
738
		var columns = oSettings.aoColumns;
739
 
740
		oState.ColReorder = [];
741
 
742
		/* Sorting */
743
		if ( oState.aaSorting ) {
744
			// 1.10.0-
745
			for ( i=0 ; i<oState.aaSorting.length ; i++ ) {
746
				oState.aaSorting[i][0] = columns[ oState.aaSorting[i][0] ]._ColReorder_iOrigCol;
747
			}
748
 
749
			var aSearchCopy = $.extend( true, [], oState.aoSearchCols );
750
 
751
			for ( i=0, iLen=columns.length ; i<iLen ; i++ )
752
			{
753
				iOrigColumn = columns[i]._ColReorder_iOrigCol;
754
 
755
				/* Column filter */
756
				oState.aoSearchCols[ iOrigColumn ] = aSearchCopy[i];
757
 
758
				/* Visibility */
759
				oState.abVisCols[ iOrigColumn ] = columns[i].bVisible;
760
 
761
				/* Column reordering */
762
				oState.ColReorder.push( iOrigColumn );
763
			}
764
		}
765
		else if ( oState.order ) {
766
			// 1.10.1+
767
			for ( i=0 ; i<oState.order.length ; i++ ) {
768
				oState.order[i][0] = columns[ oState.order[i][0] ]._ColReorder_iOrigCol;
769
			}
770
 
771
			var stateColumnsCopy = $.extend( true, [], oState.columns );
772
 
773
			for ( i=0, iLen=columns.length ; i<iLen ; i++ )
774
			{
775
				iOrigColumn = columns[i]._ColReorder_iOrigCol;
776
 
777
				/* Columns */
778
				oState.columns[ iOrigColumn ] = stateColumnsCopy[i];
779
 
780
				/* Column reordering */
781
				oState.ColReorder.push( iOrigColumn );
782
			}
783
		}
784
	},
785
 
786
 
787
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
788
	 * Mouse drop and drag
789
	 */
790
 
791
	/**
792
	 * Add a mouse down listener to a particluar TH element
793
	 *  @method  _fnMouseListener
794
	 *  @param   int i Column index
795
	 *  @param   element nTh TH element clicked on
796
	 *  @returns void
797
	 *  @private
798
	 */
799
	"_fnMouseListener": function ( i, nTh )
800
	{
801
		var that = this;
802
		$(nTh).on( 'mousedown.ColReorder', function (e) {
803
			e.preventDefault();
804
			that._fnMouseDown.call( that, e, nTh );
805
		} );
806
	},
807
 
808
 
809
	/**
810
	 * Mouse down on a TH element in the table header
811
	 *  @method  _fnMouseDown
812
	 *  @param   event e Mouse event
813
	 *  @param   element nTh TH element to be dragged
814
	 *  @returns void
815
	 *  @private
816
	 */
817
	"_fnMouseDown": function ( e, nTh )
818
	{
819
		var that = this;
820
 
821
		/* Store information about the mouse position */
822
		var target = $(e.target).closest('th, td');
823
		var offset = target.offset();
824
		var idx = parseInt( $(nTh).attr('data-column-index'), 10 );
825
 
826
		if ( idx === undefined ) {
827
			return;
828
		}
829
 
830
		this.s.mouse.startX = e.pageX;
831
		this.s.mouse.startY = e.pageY;
832
		this.s.mouse.offsetX = e.pageX - offset.left;
833
		this.s.mouse.offsetY = e.pageY - offset.top;
834
		this.s.mouse.target = this.s.dt.aoColumns[ idx ].nTh;//target[0];
835
		this.s.mouse.targetIndex = idx;
836
		this.s.mouse.fromIndex = idx;
837
 
838
		this._fnRegions();
839
 
840
		/* Add event handlers to the document */
841
		$(document)
842
			.on( 'mousemove.ColReorder', function (e) {
843
				that._fnMouseMove.call( that, e );
844
			} )
845
			.on( 'mouseup.ColReorder', function (e) {
846
				that._fnMouseUp.call( that, e );
847
			} );
848
	},
849
 
850
 
851
	/**
852
	 * Deal with a mouse move event while dragging a node
853
	 *  @method  _fnMouseMove
854
	 *  @param   event e Mouse event
855
	 *  @returns void
856
	 *  @private
857
	 */
858
	"_fnMouseMove": function ( e )
859
	{
860
		var that = this;
861
 
862
		if ( this.dom.drag === null )
863
		{
864
			/* Only create the drag element if the mouse has moved a specific distance from the start
865
			 * point - this allows the user to make small mouse movements when sorting and not have a
866
			 * possibly confusing drag element showing up
867
			 */
868
			if ( Math.pow(
869
				Math.pow(e.pageX - this.s.mouse.startX, 2) +
870
				Math.pow(e.pageY - this.s.mouse.startY, 2), 0.5 ) < 5 )
871
			{
872
				return;
873
			}
874
			this._fnCreateDragNode();
875
		}
876
 
877
		/* Position the element - we respect where in the element the click occured */
878
		this.dom.drag.css( {
879
			left: e.pageX - this.s.mouse.offsetX,
880
			top: e.pageY - this.s.mouse.offsetY
881
		} );
882
 
883
		/* Based on the current mouse position, calculate where the insert should go */
884
		var bSet = false;
885
		var lastToIndex = this.s.mouse.toIndex;
886
 
887
		for ( var i=1, iLen=this.s.aoTargets.length ; i<iLen ; i++ )
888
		{
889
			if ( e.pageX < this.s.aoTargets[i-1].x + ((this.s.aoTargets[i].x-this.s.aoTargets[i-1].x)/2) )
890
			{
891
				this.dom.pointer.css( 'left', this.s.aoTargets[i-1].x );
892
				this.s.mouse.toIndex = this.s.aoTargets[i-1].to;
893
				bSet = true;
894
				break;
895
			}
896
		}
897
 
898
		// The insert element wasn't positioned in the array (less than
899
		// operator), so we put it at the end
900
		if ( !bSet )
901
		{
902
			this.dom.pointer.css( 'left', this.s.aoTargets[this.s.aoTargets.length-1].x );
903
			this.s.mouse.toIndex = this.s.aoTargets[this.s.aoTargets.length-1].to;
904
		}
905
 
906
		// Perform reordering if realtime updating is on and the column has moved
907
		if ( this.s.init.bRealtime && lastToIndex !== this.s.mouse.toIndex ) {
908
			this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex );
909
			this.s.mouse.fromIndex = this.s.mouse.toIndex;
910
			this._fnRegions();
911
		}
912
	},
913
 
914
 
915
	/**
916
	 * Finish off the mouse drag and insert the column where needed
917
	 *  @method  _fnMouseUp
918
	 *  @param   event e Mouse event
919
	 *  @returns void
920
	 *  @private
921
	 */
922
	"_fnMouseUp": function ( e )
923
	{
924
		var that = this;
925
 
926
		$(document).off( 'mousemove.ColReorder mouseup.ColReorder' );
927
 
928
		if ( this.dom.drag !== null )
929
		{
930
			/* Remove the guide elements */
931
			this.dom.drag.remove();
932
			this.dom.pointer.remove();
933
			this.dom.drag = null;
934
			this.dom.pointer = null;
935
 
936
			/* Actually do the reorder */
937
			this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex );
938
			this._fnSetColumnIndexes();
939
 
940
			/* When scrolling we need to recalculate the column sizes to allow for the shift */
941
			if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" )
942
			{
943
				this.s.dt.oInstance.fnAdjustColumnSizing( false );
944
			}
945
 
946
			/* Save the state */
947
			this.s.dt.oInstance.oApi._fnSaveState( this.s.dt );
948
 
949
			if ( this.s.reorderCallback !== null )
950
			{
951
				this.s.reorderCallback.call( this );
952
			}
953
		}
954
	},
955
 
956
 
957
	/**
958
	 * Calculate a cached array with the points of the column inserts, and the
959
	 * 'to' points
960
	 *  @method  _fnRegions
961
	 *  @returns void
962
	 *  @private
963
	 */
964
	"_fnRegions": function ()
965
	{
966
		var aoColumns = this.s.dt.aoColumns;
967
 
968
		this.s.aoTargets.splice( 0, this.s.aoTargets.length );
969
 
970
		this.s.aoTargets.push( {
971
			"x":  $(this.s.dt.nTable).offset().left,
972
			"to": 0
973
		} );
974
 
975
		var iToPoint = 0;
976
		for ( var i=0, iLen=aoColumns.length ; i<iLen ; i++ )
977
		{
978
			/* For the column / header in question, we want it's position to remain the same if the
979
			 * position is just to it's immediate left or right, so we only incremement the counter for
980
			 * other columns
981
			 */
982
			if ( i != this.s.mouse.fromIndex )
983
			{
984
				iToPoint++;
985
			}
986
 
987
			if ( aoColumns[i].bVisible )
988
			{
989
				this.s.aoTargets.push( {
990
					"x":  $(aoColumns[i].nTh).offset().left + $(aoColumns[i].nTh).outerWidth(),
991
					"to": iToPoint
992
				} );
993
			}
994
		}
995
 
996
		/* Disallow columns for being reordered by drag and drop, counting right to left */
997
		if ( this.s.fixedRight !== 0 )
998
		{
999
			this.s.aoTargets.splice( this.s.aoTargets.length - this.s.fixedRight );
1000
		}
1001
 
1002
		/* Disallow columns for being reordered by drag and drop, counting left to right */
1003
		if ( this.s.fixed !== 0 )
1004
		{
1005
			this.s.aoTargets.splice( 0, this.s.fixed );
1006
		}
1007
	},
1008
 
1009
 
1010
	/**
1011
	 * Copy the TH element that is being drags so the user has the idea that they are actually
1012
	 * moving it around the page.
1013
	 *  @method  _fnCreateDragNode
1014
	 *  @returns void
1015
	 *  @private
1016
	 */
1017
	"_fnCreateDragNode": function ()
1018
	{
1019
		var scrolling = this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "";
1020
 
1021
		var origCell = this.s.dt.aoColumns[ this.s.mouse.targetIndex ].nTh;
1022
		var origTr = origCell.parentNode;
1023
		var origThead = origTr.parentNode;
1024
		var origTable = origThead.parentNode;
1025
		var cloneCell = $(origCell).clone();
1026
 
1027
		// This is a slightly odd combination of jQuery and DOM, but it is the
1028
		// fastest and least resource intensive way I could think of cloning
1029
		// the table with just a single header cell in it.
1030
		this.dom.drag = $(origTable.cloneNode(false))
1031
			.addClass( 'DTCR_clonedTable' )
1032
			.append(
1033
				$(origThead.cloneNode(false)).append(
1034
					$(origTr.cloneNode(false)).append(
1035
						cloneCell[0]
1036
					)
1037
				)
1038
			)
1039
			.css( {
1040
				position: 'absolute',
1041
				top: 0,
1042
				left: 0,
1043
				width: $(origCell).outerWidth(),
1044
				height: $(origCell).outerHeight()
1045
			} )
1046
			.appendTo( 'body' );
1047
 
1048
		this.dom.pointer = $('<div></div>')
1049
			.addClass( 'DTCR_pointer' )
1050
			.css( {
1051
				position: 'absolute',
1052
				top: scrolling ?
1053
					$('div.dataTables_scroll', this.s.dt.nTableWrapper).offset().top :
1054
					$(this.s.dt.nTable).offset().top,
1055
				height : scrolling ?
1056
					$('div.dataTables_scroll', this.s.dt.nTableWrapper).height() :
1057
					$(this.s.dt.nTable).height()
1058
			} )
1059
			.appendTo( 'body' );
1060
	},
1061
 
1062
 
1063
	/**
1064
	 * Add a data attribute to the column headers, so we know the index of
1065
	 * the row to be reordered. This allows fast detection of the index, and
1066
	 * for this plug-in to work with FixedHeader which clones the nodes.
1067
	 *  @private
1068
	 */
1069
	"_fnSetColumnIndexes": function ()
1070
	{
1071
		$.each( this.s.dt.aoColumns, function (i, column) {
1072
			$(column.nTh).attr('data-column-index', i);
1073
		} );
1074
	}
1075
};
1076
 
1077
 
1078
 
1079
 
1080
 
1081
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1082
 * Static parameters
1083
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1084
 
1085
 
1086
/**
1087
 * ColReorder default settings for initialisation
1088
 *  @namespace
1089
 *  @static
1090
 */
1091
ColReorder.defaults = {
1092
	/**
1093
	 * Predefined ordering for the columns that will be applied automatically
1094
	 * on initialisation. If not specified then the order that the columns are
1095
	 * found to be in the HTML is the order used.
1096
	 *  @type array
1097
	 *  @default null
1098
	 *  @static
1099
	 */
1100
	aiOrder: null,
1101
 
1102
	/**
1103
	 * Redraw the table's column ordering as the end user draws the column
1104
	 * (`true`) or wait until the mouse is released (`false` - default). Note
1105
	 * that this will perform a redraw on each reordering, which involves an
1106
	 * Ajax request each time if you are using server-side processing in
1107
	 * DataTables.
1108
	 *  @type boolean
1109
	 *  @default false
1110
	 *  @static
1111
	 */
1112
	bRealtime: true,
1113
 
1114
	/**
1115
	 * Indicate how many columns should be fixed in position (counting from the
1116
	 * left). This will typically be 1 if used, but can be as high as you like.
1117
	 *  @type int
1118
	 *  @default 0
1119
	 *  @static
1120
	 */
1121
	iFixedColumnsLeft: 0,
1122
 
1123
	/**
1124
	 * As `iFixedColumnsRight` but counting from the right.
1125
	 *  @type int
1126
	 *  @default 0
1127
	 *  @static
1128
	 */
1129
	iFixedColumnsRight: 0,
1130
 
1131
	/**
1132
	 * Callback function that is fired when columns are reordered. The `column-
1133
	 * reorder` event is preferred over this callback
1134
	 *  @type function():void
1135
	 *  @default null
1136
	 *  @static
1137
	 */
1138
	fnReorderCallback: null
1139
};
1140
 
1141
 
1142
 
1143
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1144
 * Constants
1145
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1146
 
1147
/**
1148
 * ColReorder version
1149
 *  @constant  version
1150
 *  @type      String
1151
 *  @default   As code
1152
 */
1153
ColReorder.version = "1.2.0";
1154
 
1155
 
1156
 
1157
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1158
 * DataTables interfaces
1159
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1160
 
1161
// Expose
1162
$.fn.dataTable.ColReorder = ColReorder;
1163
$.fn.DataTable.ColReorder = ColReorder;
1164
 
1165
 
1166
// Register a new feature with DataTables
1167
if ( typeof $.fn.dataTable == "function" &&
1168
     typeof $.fn.dataTableExt.fnVersionCheck == "function" &&
1169
     $.fn.dataTableExt.fnVersionCheck('1.10.8') )
1170
{
1171
	$.fn.dataTableExt.aoFeatures.push( {
1172
		"fnInit": function( settings ) {
1173
			var table = settings.oInstance;
1174
 
1175
			if ( ! settings._colReorder ) {
1176
				var dtInit = settings.oInit;
1177
				var opts = dtInit.colReorder || dtInit.oColReorder || {};
1178
 
1179
				new ColReorder( settings, opts );
1180
			}
1181
			else {
1182
				table.oApi._fnLog( settings, 1, "ColReorder attempted to initialise twice. Ignoring second" );
1183
			}
1184
 
1185
			return null; /* No node for DataTables to insert */
1186
		},
1187
		"cFeature": "R",
1188
		"sFeature": "ColReorder"
1189
	} );
1190
}
1191
else {
1192
	alert( "Warning: ColReorder requires DataTables 1.10.8 or greater - www.datatables.net/download");
1193
}
1194
 
1195
 
1196
// Attach a listener to the document which listens for DataTables initialisation
1197
// events so we can automatically initialise
1198
$(document).on( 'preInit.dt.colReorder', function (e, settings) {
1199
	if ( e.namespace !== 'dt' ) {
1200
		return;
1201
	}
1202
 
1203
	var init = settings.oInit.colReorder;
1204
	var defaults = DataTable.defaults.colReorder;
1205
 
1206
	if ( init || defaults ) {
1207
		var opts = $.extend( {}, init, defaults );
1208
 
1209
		if ( init !== false ) {
1210
			new ColReorder( settings, opts  );
1211
		}
1212
	}
1213
} );
1214
 
1215
 
1216
// API augmentation
1217
$.fn.dataTable.Api.register( 'colReorder.reset()', function () {
1218
	return this.iterator( 'table', function ( ctx ) {
1219
		ctx._colReorder.fnReset();
1220
	} );
1221
} );
1222
 
1223
$.fn.dataTable.Api.register( 'colReorder.order()', function ( set ) {
1224
	if ( set ) {
1225
		return this.iterator( 'table', function ( ctx ) {
1226
			ctx._colReorder.fnOrder( set );
1227
		} );
1228
	}
1229
 
1230
	return this.context.length ?
1231
		this.context[0]._colReorder.fnOrder() :
1232
		null;
1233
} );
1234
 
1235
 
1236
return ColReorder;
1237
}; // /factory
1238
 
1239
 
1240
// Define as an AMD module if possible
1241
if ( typeof define === 'function' && define.amd ) {
1242
	define( ['jquery', 'datatables'], factory );
1243
}
1244
else if ( typeof exports === 'object' ) {
1245
    // Node/CommonJS
1246
    factory( require('jquery'), require('datatables') );
1247
}
1248
else if ( jQuery && !jQuery.fn.dataTable.ColReorder ) {
1249
	// Otherwise simply initialise as normal, stopping multiple evaluation
1250
	factory( jQuery, jQuery.fn.dataTable );
1251
}
1252
 
1253
 
1254
})(window, document);