Subversion-Projekte lars-tiefland.cienc

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
9 lars 1
/*! RowReorder 1.0.0
2
 * 2015 SpryMedia Ltd - datatables.net/license
3
 */
4
 
5
/**
6
 * @summary     RowReorder
7
 * @description Row reordering extension for DataTables
8
 * @version     1.0.0
9
 * @file        dataTables.rowReorder.js
10
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
11
 * @contact     www.sprymedia.co.uk/contact
12
 * @copyright   Copyright 2015 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
var factory = function( $, DataTable ) {
28
"use strict";
29
 
30
/**
31
 * RowReorder provides the ability in DataTables to click and drag rows to
32
 * reorder them. When a row is dropped the data for the rows effected will be
33
 * updated to reflect the change. Normally this data point should also be the
34
 * column being sorted upon in the DataTable but this does not need to be the
35
 * case. RowReorder implements a "data swap" method - so the rows being
36
 * reordered take the value of the data point from the row that used to occupy
37
 * the row's new position.
38
 *
39
 * Initialisation is done by either:
40
 *
41
 * * `rowReorder` parameter in the DataTable initialisation object
42
 * * `new $.fn.dataTable.RowReorder( table, opts )` after DataTables
43
 *   initialisation.
44
 *
45
 *  @class
46
 *  @param {object} settings DataTables settings object for the host table
47
 *  @param {object} [opts] Configuration options
48
 *  @requires jQuery 1.7+
49
 *  @requires DataTables 1.10.7+
50
 */
51
var RowReorder = function ( dt, opts ) {
52
	// Sanity check that we are using DataTables 1.10 or newer
53
	if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
54
		throw 'DataTables RowReorder requires DataTables 1.10.8 or newer';
55
	}
56
 
57
	// User and defaults configuration object
58
	this.c = $.extend( true, {},
59
		DataTable.defaults.rowReorder,
60
		RowReorder.defaults,
61
		opts
62
	);
63
 
64
	// Internal settings
65
	this.s = {
66
		/** @type {integer} Scroll body top cache */
67
		bodyTop: null,
68
 
69
		/** @type {DataTable.Api} DataTables' API instance */
70
		dt: new DataTable.Api( dt ),
71
 
72
		/** @type {function} Data fetch function */
73
		getDataFn: DataTable.ext.oApi._fnGetObjectDataFn( this.c.dataSrc ),
74
 
75
		/** @type {array} Pixel positions for row insertion calculation */
76
		middles: null,
77
 
78
		/** @type {function} Data set function */
79
		setDataFn: DataTable.ext.oApi._fnSetObjectDataFn( this.c.dataSrc ),
80
 
81
		/** @type {Object} Mouse down information */
82
		start: {
83
			top: 0,
84
			left: 0,
85
			offsetTop: 0,
86
			offsetLeft: 0,
87
			nodes: []
88
		},
89
 
90
		/** @type {integer} Window height cached value */
91
		windowHeight: 0
92
	};
93
 
94
	// DOM items
95
	this.dom = {
96
		/** @type {jQuery} Cloned row being moved around */
97
		clone: null
98
	};
99
 
100
	// Check if row reorder has already been initialised on this table
101
	var settings = this.s.dt.settings()[0];
102
	var exisiting = settings.rowreorder;
103
	if ( exisiting ) {
104
		return exisiting;
105
	}
106
 
107
	settings.rowreorder = this;
108
	this._constructor();
109
};
110
 
111
 
112
RowReorder.prototype = {
113
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
114
	 * Constructor
115
	 */
116
 
117
	/**
118
	 * Initialise the RowReorder instance
119
	 *
120
	 * @private
121
	 */
122
	_constructor: function ()
123
	{
124
		var that = this;
125
		var dt = this.s.dt;
126
		var table = $( dt.table().node() );
127
 
128
		// Need to be able to calculate the row positions relative to the table
129
		if ( table.css('position') === 'static' ) {
130
			table.css( 'position', 'relative' );
131
		}
132
 
133
		// listen for mouse down on the target column - we have to implement
134
		// this rather than using HTML5 drag and drop as drag and drop doesn't
135
		// appear to work on table rows at this time. Also mobile browsers are
136
		// not supported
137
		$( table ).on( 'mousedown.rowReorder touchstart.rowReorder', this.c.selector, function (e) {
138
			var tr = $(this).closest('tr');
139
 
140
			// Double check that it is a DataTable row
141
			if ( dt.row( tr ).any() ) {
142
				that._mouseDown( e, tr );
143
				return false;
144
			}
145
		} );
146
 
147
		dt.on( 'destroy', function () {
148
			table.off( 'mousedown.rowReorder' );
149
		} );
150
	},
151
 
152
 
153
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
154
	 * Private methods
155
	 */
156
 
157
	/**
158
	 * Cache the measurements that RowReorder needs in the mouse move handler
159
	 * to attempt to speed things up, rather than reading from the DOM.
160
	 *
161
	 * @private
162
	 */
163
	_cachePositions: function ()
164
	{
165
		var dt = this.s.dt;
166
 
167
		// Frustratingly, if we add `position:relative` to the tbody, the
168
		// position is still relatively to the parent. So we need to adjust
169
		// for that
170
		var headerHeight = $( dt.table().node() ).find('thead').outerHeight();
171
 
172
		// Need to pass the nodes through jQuery to get them in document order,
173
		// not what DataTables thinks it is, since we have been altering the
174
		// order
175
		var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
176
		var tops = $.map( nodes, function ( node, i ) {
177
			return $(node).position().top - headerHeight;
178
		} );
179
 
180
		var middles = $.map( tops, function ( top, i ) {
181
			return tops.length < i-1 ?
182
				(top + tops[i+1]) / 2 :
183
				(top + top + $( dt.row( ':last-child' ).node() ).outerHeight() ) / 2;
184
		} );
185
 
186
		this.s.middles = middles;
187
		this.s.bodyTop = $( dt.table().body() ).offset().top;
188
		this.s.windowHeight = $(window).height();
189
	},
190
 
191
 
192
	/**
193
	 * Clone a row so it can be floated around the screen
194
	 *
195
	 * @param  {jQuery} target Node to be cloned
196
	 * @private
197
	 */
198
	_clone: function ( target )
199
	{
200
		var dt = this.s.dt;
201
		var clone = $( dt.table().node().cloneNode(false) )
202
			.addClass( 'dt-rowReorder-float' )
203
			.append('<tbody/>')
204
			.append( target.clone( false ) );
205
 
206
		// Match the table and column widths - read all sizes before setting
207
		// to reduce reflows
208
		var tableWidth = target.outerWidth();
209
		var tableHeight = target.outerHeight();
210
		var sizes = target.children().map( function () {
211
			return $(this).width();
212
		} );
213
 
214
		clone
215
			.width( tableWidth )
216
			.height( tableHeight )
217
			.find('tr').children().each( function (i) {
218
				this.style.width = sizes[i]+'px';
219
			} );
220
 
221
		// Insert into the document to have it floating around
222
		clone.appendTo( 'body' );
223
 
224
		this.dom.clone = clone;
225
	},
226
 
227
 
228
	/**
229
	 * Update the cloned item's position in the document
230
	 *
231
	 * @param  {object} e Event giving the mouse's position
232
	 * @private
233
	 */
234
	_clonePosition: function ( e )
235
	{
236
		var start = this.s.start;
237
		var topDiff = this._eventToPage( e, 'Y' ) - start.top;
238
		var leftDiff = this._eventToPage( e, 'X' ) - start.left;
239
		var snap = this.c.snapX;
240
		var left;
241
 
242
		if ( snap === true ) {
243
			left = start.offsetLeft;
244
		}
245
		else if ( typeof snap === 'number' ) {
246
			left = start.offsetLeft + snap;
247
		}
248
		else {
249
			left = leftDiff + start.offsetLeft;
250
		}
251
 
252
		this.dom.clone.css( {
253
			top: topDiff + start.offsetTop,
254
			left: left
255
		} );
256
	},
257
 
258
 
259
	/**
260
	 * Emit an event on the DataTable for listeners
261
	 *
262
	 * @param  {string} name Event name
263
	 * @param  {array} args Event arguments
264
	 * @private
265
	 */
266
	_emitEvent: function ( name, args )
267
	{
268
		this.s.dt.iterator( 'table', function ( ctx, i ) {
269
			$(ctx.nTable).triggerHandler( name+'.dt', args );
270
		} );
271
	},
272
 
273
 
274
	/**
275
	 * Get pageX/Y position from an event, regardless of if it is a mouse or
276
	 * touch event.
277
	 *
278
	 * @param  {object} e Event
279
	 * @param  {string} pos X or Y (must be a capital)
280
	 * @private
281
	 */
282
	_eventToPage: function ( e, pos )
283
	{
284
		if ( e.type.indexOf( 'touch' ) !== -1 ) {
285
			return e.originalEvent.touches[0][ 'page'+pos ];
286
		}
287
 
288
		return e[ 'page'+pos ];
289
	},
290
 
291
 
292
	/**
293
	 * Mouse down event handler. Read initial positions and add event handlers
294
	 * for the move.
295
	 *
296
	 * @param  {object} e      Mouse event
297
	 * @param  {jQuery} target TR element that is to be moved
298
	 * @private
299
	 */
300
	_mouseDown: function ( e, target )
301
	{
302
		var that = this;
303
		var dt = this.s.dt;
304
		var start = this.s.start;
305
 
306
		var offset = target.offset();
307
		start.top = this._eventToPage( e, 'Y' );
308
		start.left = this._eventToPage( e, 'X' );
309
		start.offsetTop = offset.top;
310
		start.offsetLeft = offset.left;
311
		start.nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
312
 
313
		this._cachePositions();
314
		this._clone( target );
315
		this._clonePosition( e );
316
 
317
		this.dom.target = target;
318
		target.addClass( 'dt-rowReorder-moving' );
319
 
320
		$( document )
321
			.on( 'mouseup.rowReorder touchend.rowReorder', function (e) {
322
				that._mouseUp(e);
323
			} )
324
			.on( 'mousemove.rowReorder touchmove.rowReorder', function (e) {
325
				that._mouseMove(e);
326
			} );
327
 
328
		// Check if window is x-scrolling - if not, disable it for the duration
329
		// of the drag
330
		if ( $(window).width() === $(document).width() ) {
331
			$(document.body).addClass( 'dt-rowReorder-noOverflow' );
332
		}
333
	},
334
 
335
 
336
	/**
337
	 * Mouse move event handler - move the cloned row and shuffle the table's
338
	 * rows if required.
339
	 *
340
	 * @param  {object} e Mouse event
341
	 * @private
342
	 */
343
	_mouseMove: function ( e )
344
	{
345
		this._clonePosition( e );
346
 
347
		// Transform the mouse position into a position in the table's body
348
		var bodyY = this._eventToPage( e, 'Y' ) - this.s.bodyTop;
349
		var middles = this.s.middles;
350
		var insertPoint = null;
351
		var dt = this.s.dt;
352
		var body = dt.table().body();
353
 
354
		// Determine where the row should be inserted based on the mouse
355
		// position
356
		for ( var i=0, ien=middles.length ; i<ien ; i++ ) {
357
			if ( bodyY < middles[i] ) {
358
				insertPoint = i;
359
				break;
360
			}
361
		}
362
 
363
		if ( insertPoint === null ) {
364
			insertPoint = middles.length;
365
		}
366
 
367
		// Perform the DOM shuffle if it has changed from last time
368
		if ( this.s.lastInsert === null || this.s.lastInsert !== insertPoint ) {
369
			if ( insertPoint === 0 ) {
370
				this.dom.target.prependTo( body );
371
			}
372
			else {
373
				var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
374
 
375
				if ( insertPoint > this.s.lastInsert ) {
376
					this.dom.target.before( nodes[ insertPoint-1 ] );
377
				}
378
				else {
379
					this.dom.target.after( nodes[ insertPoint ] );
380
				}
381
			}
382
 
383
			this._cachePositions();
384
 
385
			this.s.lastInsert = insertPoint;
386
		}
387
 
388
		// scroll window up and down when reaching the edges
389
		var windowY = this._eventToPage( e, 'Y' ) - document.body.scrollTop;
390
		var scrollInterval = this.s.scrollInterval;
391
 
392
		if ( windowY < 65 ) {
393
			if ( ! scrollInterval ) {
394
				this.s.scrollInterval = setInterval( function () {
395
					document.body.scrollTop -= 5;
396
				}, 15 );
397
			}
398
		}
399
		else if ( this.s.windowHeight - windowY < 65 ) {
400
			if ( ! scrollInterval ) {
401
				this.s.scrollInterval = setInterval( function () {
402
					document.body.scrollTop += 5;
403
				}, 15 );
404
			}
405
		}
406
		else {
407
			clearInterval( scrollInterval );
408
			this.s.scrollInterval = null;
409
		}
410
	},
411
 
412
 
413
	/**
414
	 * Mouse up event handler - release the event handlers and perform the
415
	 * table updates
416
	 *
417
	 * @param  {object} e Mouse event
418
	 * @private
419
	 */
420
	_mouseUp: function ( e )
421
	{
422
		var dt = this.s.dt;
423
		var i, ien;
424
 
425
		this.dom.clone.remove();
426
		this.dom.clone = null;
427
 
428
		this.dom.target.removeClass( 'dt-rowReorder-moving' );
429
		//this.dom.target = null;
430
 
431
		$(document).off( '.rowReorder' );
432
		$(document.body).removeClass( 'dt-rowReorder-noOverflow' );
433
 
434
		// Calculate the difference
435
		var startNodes = this.s.start.nodes;
436
		var endNodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
437
		var idDiff = {};
438
		var fullDiff = [];
439
		var diffNodes = [];
440
		var getDataFn = this.s.getDataFn;
441
		var setDataFn = this.s.setDataFn;
442
 
443
		for ( i=0, ien=startNodes.length ; i<ien ; i++ ) {
444
			if ( startNodes[i] !== endNodes[i] ) {
445
				var id = dt.row( endNodes[i] ).id();
446
				var endRowData = dt.row( endNodes[i] ).data();
447
				var startRowData = dt.row( startNodes[i] ).data();
448
 
449
				if ( id ) {
450
					idDiff[ id ] = getDataFn( startRowData );
451
				}
452
 
453
				fullDiff.push( {
454
					node: endNodes[i],
455
					oldData: getDataFn( endRowData ),
456
					newData: getDataFn( startRowData ),
457
					newPosition: i,
458
					oldPosition: $.inArray( endNodes[i], startNodes )
459
				} );
460
 
461
				diffNodes.push( endNodes[i] );
462
			}
463
		}
464
 
465
		// Emit event
466
		this._emitEvent( 'row-reorder', [ fullDiff, {
467
			dataSrc: this.c.dataSrc,
468
			nodes:   diffNodes,
469
			values:  idDiff
470
		} ] );
471
 
472
		// Editor interface
473
		if ( this.c.editor ) {
474
			this.c.editor
475
				.edit( diffNodes, false, {
476
					submit: 'changed'
477
				} )
478
				.multiSet( this.c.dataSrc, idDiff )
479
				.submit();
480
		}
481
 
482
		// Do update if required
483
		if ( this.c.update ) {
484
			for ( i=0, ien=fullDiff.length ; i<ien ; i++ ) {
485
				var row = dt.row( fullDiff[i].node );
486
				var rowData = row.data();
487
 
488
				setDataFn( rowData, fullDiff[i].newData );
489
 
490
				row.invalidate( 'data' );
491
			}
492
 
493
			dt.draw( false );
494
		}
495
	}
496
};
497
 
498
 
499
 
500
/**
501
 * RowReorder default settings for initialisation
502
 *
503
 * @namespace
504
 * @name RowReorder.defaults
505
 * @static
506
 */
507
RowReorder.defaults = {
508
	/**
509
	 * Data point in the host row's data source object for where to get and set
510
	 * the data to reorder. This will normally also be the sorting column.
511
	 *
512
	 * @type {Number}
513
	 */
514
	dataSrc: 0,
515
 
516
	/**
517
	 * Editor instance that will be used to perform the update
518
	 *
519
	 * @type {DataTable.Editor}
520
	 */
521
	editor: null,
522
 
523
	/**
524
	 * Drag handle selector. This defines the element that when dragged will
525
	 * reorder a row.
526
	 *
527
	 * @type {String}
528
	 */
529
	selector: 'td:first-child',
530
 
531
	/**
532
	 * Optionally lock the dragged row's x-position. This can be `true` to
533
	 * fix the position match the host table's, `false` to allow free movement
534
	 * of the row, or a number to define an offset from the host table.
535
	 *
536
	 * @type {Boolean|number}
537
	 */
538
	snapX: false,
539
 
540
	/**
541
	 * Update the table's data on drop
542
	 *
543
	 * @type {Boolean}
544
	 */
545
	update: true
546
};
547
 
548
 
549
/**
550
 * Version information
551
 *
552
 * @name RowReorder.version
553
 * @static
554
 */
555
RowReorder.version = '1.0.0';
556
 
557
 
558
$.fn.dataTable.RowReorder = RowReorder;
559
$.fn.DataTable.RowReorder = RowReorder;
560
 
561
// Attach a listener to the document which listens for DataTables initialisation
562
// events so we can automatically initialise
563
$(document).on( 'init.dt.dtr', function (e, settings, json) {
564
	if ( e.namespace !== 'dt' ) {
565
		return;
566
	}
567
 
568
	var init = settings.oInit.rowReorder;
569
	var defaults = DataTable.defaults.rowReorder;
570
 
571
	if ( init || defaults ) {
572
		var opts = $.extend( {}, init, defaults );
573
 
574
		if ( init !== false ) {
575
			new RowReorder( settings, opts  );
576
		}
577
	}
578
} );
579
 
580
return RowReorder;
581
}; // /factory
582
 
583
 
584
// Define as an AMD module if possible
585
if ( typeof define === 'function' && define.amd ) {
586
	define( ['jquery', 'datatables'], factory );
587
}
588
else if ( typeof exports === 'object' ) {
589
    // Node/CommonJS
590
    factory( require('jquery'), require('datatables') );
591
}
592
else if ( jQuery && !jQuery.fn.dataTable.RowReorder ) {
593
	// Otherwise simply initialise as normal, stopping multiple evaluation
594
	factory( jQuery, jQuery.fn.dataTable );
595
}
596
 
597
 
598
})(window, document);