Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
875 lars 1
/*! FixedHeader 3.0.0
2
 * ©2009-2015 SpryMedia Ltd - datatables.net/license
3
 */
4
 
5
/**
6
 * @summary     FixedHeader
7
 * @description Fix a table's header or footer, so it is always visible while
8
 *              scrolling
9
 * @version     3.0.0
10
 * @file        dataTables.fixedHeader.js
11
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
12
 * @contact     www.sprymedia.co.uk/contact
13
 * @copyright   Copyright 2009-2015 SpryMedia Ltd.
14
 *
15
 * This source file is free software, available under the following license:
16
 *   MIT license - http://datatables.net/license/mit
17
 *
18
 * This source file is distributed in the hope that it will be useful, but
19
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
21
 *
22
 * For details please refer to: http://www.datatables.net
23
 */
24
 
25
 
26
(function(window, document, undefined) {
27
 
28
 
29
var factory = function( $, DataTable ) {
30
"use strict";
31
 
32
var _instCounter = 0;
33
 
34
var FixedHeader = function ( dt, config ) {
35
	// Sanity check - you just know it will happen
36
	if ( ! (this instanceof FixedHeader) ) {
37
		throw "FixedHeader must be initialised with the 'new' keyword.";
38
	}
39
 
40
	// Allow a boolean true for defaults
41
	if ( config === true ) {
42
		config = {};
43
	}
44
 
45
	dt = new DataTable.Api( dt );
46
 
47
	this.c = $.extend( true, {}, FixedHeader.defaults, config );
48
 
49
	this.s = {
50
		dt: dt,
51
		position: {
52
			theadTop: 0,
53
			tbodyTop: 0,
54
			tfootTop: 0,
55
			tfootBottom: 0,
56
			width: 0,
57
			left: 0,
58
			tfootHeight: 0,
59
			theadHeight: 0,
60
			windowHeight: $(window).height(),
61
			visible: true
62
		},
63
		headerMode: null,
64
		footerMode: null,
65
		namespace: '.dtfc'+(_instCounter++)
66
	};
67
 
68
	this.dom = {
69
		floatingHeader: null,
70
		thead: $(dt.table().header()),
71
		tbody: $(dt.table().body()),
72
		tfoot: $(dt.table().footer()),
73
		header: {
74
			host: null,
75
			floating: null,
76
			placeholder: null
77
		},
78
		footer: {
79
			host: null,
80
			floating: null,
81
			placeholder: null
82
		}
83
	};
84
 
85
	this.dom.header.host = this.dom.thead.parent();
86
	this.dom.footer.host = this.dom.tfoot.parent();
87
 
88
	var dtSettings = dt.settings()[0];
89
	if ( dtSettings._fixedHeader ) {
90
		throw "FixedHeader already initialised on table "+dtSettings.nTable.id;
91
	}
92
 
93
	dtSettings._fixedHeader = this;
94
 
95
	this._constructor();
96
};
97
 
98
 
99
/*
100
 * Variable: FixedHeader
101
 * Purpose:  Prototype for FixedHeader
102
 * Scope:    global
103
 */
104
FixedHeader.prototype = {
105
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
106
	 * API methods
107
	 */
108
 
109
	/**
110
	 * Recalculate the position of the fixed elements and force them into place
111
	 */
112
	update: function () {
113
		this._positions();
114
		this._scroll( true );
115
	},
116
 
117
 
118
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
119
	 * Constructor
120
	 */
121
 
122
	/**
123
	 * FixedHeader constructor - adding the required event listeners and
124
	 * simple initialisation
125
	 *
126
	 * @private
127
	 */
128
	_constructor: function ()
129
	{
130
		var that = this;
131
		var dt = this.s.dt;
132
 
133
		$(window)
134
			.on( 'scroll'+this.s.namespace, function () {
135
				that._scroll();
136
			} )
137
			.on( 'resize'+this.s.namespace, function () {
138
				that.s.position.windowHeight = $(window).height();
139
				that._positions();
140
				that._scroll( true );
141
			} );
142
 
143
		dt
144
			.on( 'column-reorder.dt.dtfc column-visibility.dt.dtfc', function () {
145
				that._positions();
146
				that._scroll( true );
147
			} )
148
			.on( 'draw.dtfc', function () {
149
				that._positions();
150
				that._scroll();
151
			} );
152
 
153
		dt.on( 'destroy.dtfc', function () {
154
			dt.off( '.dtfc' );
155
			$(window).off( this.s.namespace );
156
		} );
157
 
158
		this._positions();
159
		this._scroll();
160
	},
161
 
162
 
163
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
164
	 * Private methods
165
	 */
166
 
167
	/**
168
	 * Clone a fixed item to act as a place holder for the original element
169
	 * which is moved into a clone of the table element, and moved around the
170
	 * document to give the fixed effect.
171
	 *
172
	 * @param  {string}  item  'header' or 'footer'
173
	 * @param  {boolean} force Force the clone to happen, or allow automatic
174
	 *   decision (reuse existing if available)
175
	 * @private
176
	 */
177
	_clone: function ( item, force )
178
	{
179
		var dt = this.s.dt;
180
		var itemDom = this.dom[ item ];
181
		var itemElement = item === 'header' ?
182
			this.dom.thead :
183
			this.dom.tfoot;
184
 
185
		if ( ! force && itemDom.floating ) {
186
			// existing floating element - reuse it
187
			itemDom.floating.removeClass( 'fixedHeader-floating fixedHeader-locked' );
188
		}
189
		else {
190
			if ( itemDom.floating ) {
191
				itemDom.placeholder.remove();
192
				itemDom.floating.children().detach();
193
				itemDom.floating.remove();
194
			}
195
 
196
			itemDom.floating = $( dt.table().node().cloneNode( false ) )
197
				.removeAttr( 'id' )
198
				.append( itemElement )
199
				.appendTo( 'body' );
200
 
201
			// Insert a fake thead/tfoot into the DataTable to stop it jumping around
202
			itemDom.placeholder = itemElement.clone( false );
203
			itemDom.host.append( itemDom.placeholder );
204
 
205
			// Footer needs sizes cloned across
206
			if ( item === 'footer' ) {
207
				this._footerMatch( itemDom.placeholder, itemDom.floating );
208
			}
209
		}
210
	},
211
 
212
	/**
213
	 * Copy widths from the cells in one element to another. This is required
214
	 * for the footer as the footer in the main table takes its sizes from the
215
	 * header columns. That isn't present in the footer so to have it still
216
	 * align correctly, the sizes need to be copied over.
217
	 *
218
	 * @param  {jQuery} from Copy widths from
219
	 * @param  {jQuery} to   Copy widths to
220
	 * @private
221
	 */
222
	_footerMatch: function ( from, to ) {
223
		var type = function ( name ) {
224
			var toWidths = $(name, from)
225
				.map( function () {
226
					return $(this).width();
227
				} ).toArray();
228
 
229
			$(name, to).each( function ( i ) {
230
				$(this).width( toWidths[i] );
231
			} );
232
		};
233
 
234
		type( 'th' );
235
		type( 'td' );
236
	},
237
 
238
	/**
239
	 * Remove assigned widths from the cells in an element. This is required
240
	 * when inserting the footer back into the main table so the size is defined
241
	 * by the header columns.
242
	 *
243
	 * @private
244
	 */
245
	_footerUnsize: function () {
246
		var footer = this.dom.footer.floating;
247
 
248
		if ( footer ) {
249
			$('th, td', footer).css( 'width', '' );
250
		}
251
	},
252
 
253
	/**
254
	 * Change from one display mode to another. Each fixed item can be in one
255
	 * of:
256
	 *
257
	 * * `in-place` - In the main DataTable
258
	 * * `in` - Floating over the DataTable
259
	 * * `below` - (Header only) Fixed to the bottom of the table body
260
	 * * `above` - (Footer only) Fixed to the top of the table body
261
	 *
262
	 * @param  {string}  mode        Mode that the item should be shown in
263
	 * @param  {string}  item        'header' or 'footer'
264
	 * @param  {boolean} forceChange Force a redraw of the mode, even if already
265
	 *     in that mode.
266
	 * @private
267
	 */
268
	_modeChange: function ( mode, item, forceChange )
269
	{
270
		var dt = this.s.dt;
271
		var itemDom = this.dom[ item ];
272
		var position = this.s.position;
273
 
274
		if ( mode === 'in-place' ) {
275
			// Insert the header back into the table's real header
276
			if ( itemDom.placeholder ) {
277
				itemDom.placeholder.remove();
278
				itemDom.placeholder = null;
279
			}
280
 
281
			itemDom.host.append( item === 'header' ?
282
				this.dom.thead :
283
				this.dom.tfoot
284
			);
285
 
286
			if ( itemDom.floating ) {
287
				itemDom.floating.remove();
288
				itemDom.floating = null;
289
			}
290
 
291
			if ( item === 'footer' ) {
292
				this._footerUnsize();
293
			}
294
		}
295
		else if ( mode === 'in' ) {
296
			// Remove the header from the read header and insert into a fixed
297
			// positioned floating table clone
298
			this._clone( item, forceChange );
299
 
300
			itemDom.floating
301
				.addClass( 'fixedHeader-floating' )
302
				.css( item === 'header' ? 'top' : 'bottom', this.c[item+'Offset'] )
303
				.css( 'left', position.left+'px' )
304
				.css( 'width', position.width+'px' );
305
 
306
			if ( item === 'footer' ) {
307
				itemDom.floating.css( 'top', '' );
308
			}
309
		}
310
		else if ( mode === 'below' ) { // only used for the header
311
			// Fix the position of the floating header at base of the table body
312
			this._clone( item, forceChange );
313
 
314
			itemDom.floating
315
				.addClass( 'fixedHeader-locked' )
316
				.css( 'top', position.tfootTop - position.theadHeight )
317
				.css( 'left', position.left+'px' )
318
				.css( 'width', position.width+'px' );
319
		}
320
		else if ( mode === 'above' ) { // only used for the footer
321
			// Fix the position of the floating footer at top of the table body
322
			this._clone( item, forceChange );
323
 
324
			itemDom.floating
325
				.addClass( 'fixedHeader-locked' )
326
				.css( 'top', position.tbodyTop )
327
				.css( 'left', position.left+'px' )
328
				.css( 'width', position.width+'px' );
329
		}
330
 
331
		this.s[item+'Mode'] = mode;
332
	},
333
 
334
	/**
335
	 * Cache the positional information that is required for the mode
336
	 * calculations that FixedHeader performs.
337
	 *
338
	 * @private
339
	 */
340
	_positions: function ()
341
	{
342
		var dt = this.s.dt;
343
		var table = dt.table();
344
		var position = this.s.position;
345
		var dom = this.dom;
346
		var tableNode = $(table.node());
347
 
348
		// Need to use the header and footer that are in the main table,
349
		// regardless of if they are clones, since they hold the positions we
350
		// want to measure from
351
		var thead = tableNode.children('thead');
352
		var tfoot = tableNode.children('tfoot');
353
		var tbody = dom.tbody;
354
 
355
		position.visible = tableNode.is(':visible');
356
		position.width = tableNode.outerWidth();
357
		position.left = tableNode.offset().left;
358
		position.theadTop = thead.offset().top;
359
		position.tbodyTop = tbody.offset().top;
360
		position.theadHeight = position.tbodyTop - position.theadTop;
361
 
362
		if ( tfoot.length ) {
363
			position.tfootTop = tfoot.offset().top;
364
			position.tfootBottom = position.tfootTop + tfoot.outerHeight();
365
			position.tfootHeight = position.tfootBottom - position.tfootTop;
366
		}
367
		else {
368
			position.tfootTop = position.tbodyTop + tbody.outerHeight();
369
			position.tfootBottom = position.tfootTop;
370
			position.tfootHeight = position.tfootTop;
371
		}
372
	},
373
 
374
 
375
	/**
376
	 * Mode calculation - determine what mode the fixed items should be placed
377
	 * into.
378
	 *
379
	 * @param  {boolean} forceChange Force a redraw of the mode, even if already
380
	 *     in that mode.
381
	 * @private
382
	 */
383
	_scroll: function ( forceChange )
384
	{
385
		var windowTop = $(document).scrollTop();
386
		var position = this.s.position;
387
		var headerMode, footerMode;
388
 
389
		if ( this.c.header ) {
390
			if ( ! position.visible || windowTop <= position.theadTop - this.c.headerOffset ) {
391
				headerMode = 'in-place';
392
			}
393
			else if ( windowTop <= position.tfootTop - position.theadHeight - this.c.headerOffset ) {
394
				headerMode = 'in';
395
			}
396
			else {
397
				headerMode = 'below';
398
			}
399
 
400
			if ( forceChange || headerMode !== this.s.headerMode ) {
401
				this._modeChange( headerMode, 'header', forceChange );
402
			}
403
		}
404
 
405
		if ( this.c.footer && this.dom.tfoot.length ) {
406
			if ( ! position.visible || windowTop + position.windowHeight >= position.tfootBottom + this.c.footerOffset ) {
407
				footerMode = 'in-place';
408
			}
409
			else if ( position.windowHeight + windowTop > position.tbodyTop + position.tfootHeight + this.c.footerOffset ) {
410
				footerMode = 'in';
411
			}
412
			else {
413
				footerMode = 'above';
414
			}
415
 
416
			if ( forceChange || footerMode !== this.s.footerMode ) {
417
				this._modeChange( footerMode, 'footer', forceChange );
418
			}
419
		}
420
	}
421
};
422
 
423
 
424
/**
425
 * Version
426
 * @type {String}
427
 * @static
428
 */
429
FixedHeader.version = "3.0.0";
430
 
431
/**
432
 * Defaults
433
 * @type {Object}
434
 * @static
435
 */
436
FixedHeader.defaults = {
437
	header: true,
438
	footer: false,
439
	headerOffset: 0,
440
	footerOffset: 0
441
};
442
 
443
 
444
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
445
 * DataTables interfaces
446
 */
447
 
448
// Attach for constructor access
449
$.fn.dataTable.FixedHeader = FixedHeader;
450
$.fn.DataTable.FixedHeader = FixedHeader;
451
 
452
 
453
// DataTables creation - check if the FixedHeader option has been defined on the
454
// table and if so, initialise
455
$(document).on( 'init.dt.dtb', function (e, settings, json) {
456
	if ( e.namespace !== 'dt' ) {
457
		return;
458
	}
459
 
460
	var opts = settings.oInit.fixedHeader || DataTable.defaults.fixedHeader;
461
 
462
	if ( opts && ! settings._buttons ) {
463
		new FixedHeader( settings, opts );
464
	}
465
} );
466
 
467
// DataTables API methods
468
DataTable.Api.register( 'fixedHeader()', function () {} );
469
 
470
DataTable.Api.register( 'fixedHeader.adjust()', function () {
471
	return this.iterator( 'table', function ( ctx ) {
472
		var fh = ctx._fixedHeader;
473
 
474
		if ( fh ) {
475
			fh.update();
476
		}
477
	} );
478
} );
479
 
480
 
481
return FixedHeader;
482
}; // /factory
483
 
484
 
485
// Define as an AMD module if possible
486
if ( typeof define === 'function' && define.amd ) {
487
	define( ['jquery', 'datatables'], factory );
488
}
489
else if ( typeof exports === 'object' ) {
490
    // Node/CommonJS
491
    factory( require('jquery'), require('datatables') );
492
}
493
else if ( jQuery && !jQuery.fn.dataTable.FixedHeader ) {
494
	// Otherwise simply initialise as normal, stopping multiple evaluation
495
	factory( jQuery, jQuery.fn.dataTable );
496
}
497
 
498
 
499
})(window, document);