GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Branch dev (b24bdb)
by Liuta
05:14
created

admin/js/dataTables.responsive.js   F

Complexity

Total Complexity 197
Complexity/F 2.81

Size

Lines of Code 1210
Function Count 70

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
eloc 510
c 0
b 0
f 0
nc 0
dl 0
loc 1210
rs 2
wmc 197
mnd 6
bc 160
fnc 70
bpm 2.2857
cpm 2.8142
noi 24

3 Functions

Rating   Name   Duplication   Size   Complexity  
B dataTables.responsive.js ➔ ?!? 0 1185 1
A dataTables.responsive.js ➔ define 0 3 1
A module.exports 0 11 4

How to fix   Complexity   

Complexity

Complex classes like admin/js/dataTables.responsive.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*! Responsive 2.1.0
2
 * 2014-2016 SpryMedia Ltd - datatables.net/license
3
 */
4
5
/**
6
 * @summary     Responsive
7
 * @description Responsive tables plug-in for DataTables
8
 * @version     2.1.0
9
 * @file        dataTables.responsive.js
10
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
11
 * @contact     www.sprymedia.co.uk/contact
12
 * @copyright   Copyright 2014-2016 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
 /** global: define */
24
 
25
(function( factory ){
26
	if ( typeof define === 'function' && define.amd ) {
27
		// AMD
28
		define( ['jquery', 'datatables.net'], function ( $ ) {
29
			return factory( $, window, document );
30
		} );
31
	}
32
	else if ( typeof exports === 'object' ) {
33
		// CommonJS
34
		module.exports = function (root, $) {
35
			if ( ! root ) {
36
				root = window;
37
			}
38
39
			if ( ! $ || ! $.fn.dataTable ) {
40
				$ = require('datatables.net')(root, $).$;
41
			}
42
43
			return factory( $, root, root.document );
44
		};
45
	}
46
	else {
47
		// Browser
48
		factory( jQuery, window, document );
49
	}
50
}(function( $, window, document, undefined ) {
51
'use strict';
52
var DataTable = $.fn.dataTable;
53
54
55
/**
56
 * Responsive is a plug-in for the DataTables library that makes use of
57
 * DataTables' ability to change the visibility of columns, changing the
58
 * visibility of columns so the displayed columns fit into the table container.
59
 * The end result is that complex tables will be dynamically adjusted to fit
60
 * into the viewport, be it on a desktop, tablet or mobile browser.
61
 *
62
 * Responsive for DataTables has two modes of operation, which can used
63
 * individually or combined:
64
 *
65
 * * Class name based control - columns assigned class names that match the
66
 *   breakpoint logic can be shown / hidden as required for each breakpoint.
67
 * * Automatic control - columns are automatically hidden when there is no
68
 *   room left to display them. Columns removed from the right.
69
 *
70
 * In additional to column visibility control, Responsive also has built into
71
 * options to use DataTables' child row display to show / hide the information
72
 * from the table that has been hidden. There are also two modes of operation
73
 * for this child row display:
74
 *
75
 * * Inline - when the control element that the user can use to show / hide
76
 *   child rows is displayed inside the first column of the table.
77
 * * Column - where a whole column is dedicated to be the show / hide control.
78
 *
79
 * Initialisation of Responsive is performed by:
80
 *
81
 * * Adding the class `responsive` or `dt-responsive` to the table. In this case
82
 *   Responsive will automatically be initialised with the default configuration
83
 *   options when the DataTable is created.
84
 * * Using the `responsive` option in the DataTables configuration options. This
85
 *   can also be used to specify the configuration options, or simply set to
86
 *   `true` to use the defaults.
87
 *
88
 *  @class
89
 *  @param {object} settings DataTables settings object for the host table
90
 *  @param {object} [opts] Configuration options
91
 *  @requires jQuery 1.7+
92
 *  @requires DataTables 1.10.3+
93
 *
94
 *  @example
95
 *      $('#example').DataTable( {
96
 *        responsive: true
97
 *      } );
98
 *    } );
99
 */
100
var Responsive = function ( settings, opts ) {
101
	// Sanity check that we are using DataTables 1.10 or newer
102
	if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.3' ) ) {
103
		throw 'DataTables Responsive requires DataTables 1.10.3 or newer';
104
	}
105
106
	this.s = {
107
		dt: new DataTable.Api( settings ),
108
		columns: [],
109
		current: []
110
	};
111
112
	// Check if responsive has already been initialised on this table
113
	if ( this.s.dt.settings()[0].responsive ) {
114
		return;
115
	}
116
117
	// details is an object, but for simplicity the user can give it as a string
118
	// or a boolean
119
	if ( opts && typeof opts.details === 'string' ) {
120
		opts.details = { type: opts.details };
121
	}
122
	else if ( opts && opts.details === false ) {
123
		opts.details = { type: false };
124
	}
125
	else if ( opts && opts.details === true ) {
126
		opts.details = { type: 'inline' };
127
	}
128
129
	this.c = $.extend( true, {}, Responsive.defaults, DataTable.defaults.responsive, opts );
130
	settings.responsive = this;
131
	this._constructor();
132
};
133
134
$.extend( Responsive.prototype, {
135
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
136
	 * Constructor
137
	 */
138
139
	/**
140
	 * Initialise the Responsive instance
141
	 *
142
	 * @private
143
	 */
144
	_constructor: function ()
145
	{
146
		var that = this;
147
		var dt = this.s.dt;
148
		var dtPrivateSettings = dt.settings()[0];
149
		var oldWindowWidth = $(window).width();
150
151
		dt.settings()[0]._responsive = this;
152
153
		// Use DataTables' throttle function to avoid processor thrashing on
154
		// resize
155
		$(window).on( 'resize.dtr orientationchange.dtr', DataTable.util.throttle( function () {
156
			// iOS has a bug whereby resize can fire when only scrolling
157
			// See: http://stackoverflow.com/questions/8898412
158
			var width = $(window).width();
159
160
			if ( width !== oldWindowWidth ) {
161
				that._resize();
162
				oldWindowWidth = width;
163
			}
164
		} ) );
165
166
		// DataTables doesn't currently trigger an event when a row is added, so
167
		// we need to hook into its private API to enforce the hidden rows when
168
		// new data is added
169
		dtPrivateSettings.oApi._fnCallbackReg( dtPrivateSettings, 'aoRowCreatedCallback', function (tr, data, idx) {
0 ignored issues
show
Unused Code introduced by
The parameter idx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
170
			if ( $.inArray( false, that.s.current ) !== -1 ) {
171
				$('td, th', tr).each( function ( i ) {
172
					var idx = dt.column.index( 'toData', i );
173
174
					if ( that.s.current[idx] === false ) {
175
						$(this).css('display', 'none');
176
					}
177
				} );
178
			}
179
		} );
180
181
		// Destroy event handler
182
		dt.on( 'destroy.dtr', function () {
183
			dt.off( '.dtr' );
184
			$( dt.table().body() ).off( '.dtr' );
185
			$(window).off( 'resize.dtr orientationchange.dtr' );
186
187
			// Restore the columns that we've hidden
188
			$.each( that.s.current, function ( i, val ) {
189
				if ( val === false ) {
190
					that._setColumnVis( i, true );
191
				}
192
			} );
193
		} );
194
195
		// Reorder the breakpoints array here in case they have been added out
196
		// of order
197
		this.c.breakpoints.sort( function (a, b) {
198
			return a.width < b.width ? 1 :
199
				a.width > b.width ? -1 : 0;
200
		} );
201
202
		this._classLogic();
203
		this._resizeAuto();
204
205
		// Details handler
206
		var details = this.c.details;
207
208
		if ( details.type !== false ) {
209
			that._detailsInit();
210
211
			// DataTables will trigger this event on every column it shows and
212
			// hides individually
213
			dt.on( 'column-visibility.dtr', function (e, ctx, col, vis) {
0 ignored issues
show
Unused Code introduced by
The parameter col is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter vis is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter ctx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
214
				that._classLogic();
215
				that._resizeAuto();
216
				that._resize();
217
			} );
218
219
			// Redraw the details box on each draw which will happen if the data
220
			// has changed. This is used until DataTables implements a native
221
			// `updated` event for rows
222
			dt.on( 'draw.dtr', function () {
223
				that._redrawChildren();
224
			} );
225
226
			$(dt.table().node()).addClass( 'dtr-'+details.type );
227
		}
228
229
		dt.on( 'column-reorder.dtr', function (e, settings, details) {
0 ignored issues
show
Unused Code introduced by
The parameter settings is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter details is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
230
			that._classLogic();
231
			that._resizeAuto();
232
			that._resize();
233
		} );
234
235
		// Change in column sizes means we need to calc
236
		dt.on( 'column-sizing.dtr', function () {
237
			that._resizeAuto();
238
			that._resize();
239
		});
240
241
		dt.on( 'init.dtr', function (e, settings, details) {
0 ignored issues
show
Unused Code introduced by
The parameter settings is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter details is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
242
			that._resizeAuto();
243
			that._resize();
244
245
			// If columns were hidden, then DataTables needs to adjust the
246
			// column sizing
247
			if ( $.inArray( false, that.s.current ) ) {
248
				dt.columns.adjust();
249
			}
250
		} );
251
252
		// First pass - draw the table for the current viewport size
253
		this._resize();
254
	},
255
256
257
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
258
	 * Private methods
259
	 */
260
261
	/**
262
	 * Calculate the visibility for the columns in a table for a given
263
	 * breakpoint. The result is pre-determined based on the class logic if
264
	 * class names are used to control all columns, but the width of the table
265
	 * is also used if there are columns which are to be automatically shown
266
	 * and hidden.
267
	 *
268
	 * @param  {string} breakpoint Breakpoint name to use for the calculation
269
	 * @return {array} Array of boolean values initiating the visibility of each
270
	 *   column.
271
	 *  @private
272
	 */
273
	_columnsVisiblity: function ( breakpoint )
274
	{
275
		var dt = this.s.dt;
276
		var columns = this.s.columns;
277
		var i, ien;
278
279
		// Create an array that defines the column ordering based first on the
280
		// column's priority, and secondly the column index. This allows the
281
		// columns to be removed from the right if the priority matches
282
		var order = columns
283
			.map( function ( col, idx ) {
284
				return {
285
					columnIdx: idx,
286
					priority: col.priority
287
				};
288
			} )
289
			.sort( function ( a, b ) {
290
				if ( a.priority !== b.priority ) {
291
					return a.priority - b.priority;
292
				}
293
				return a.columnIdx - b.columnIdx;
294
			} );
295
296
		// Class logic - determine which columns are in this breakpoint based
297
		// on the classes. If no class control (i.e. `auto`) then `-` is used
298
		// to indicate this to the rest of the function
299
		var display = $.map( columns, function ( col ) {
300
			return col.auto && col.minWidth === null ?
301
				false :
302
				col.auto === true ?
303
					'-' :
304
					$.inArray( breakpoint, col.includeIn ) !== -1;
305
		} );
306
307
		// Auto column control - first pass: how much width is taken by the
308
		// ones that must be included from the non-auto columns
309
		var requiredWidth = 0;
310
		for ( i=0, ien=display.length ; i<ien ; i++ ) {
311
			if ( display[i] === true ) {
312
				requiredWidth += columns[i].minWidth;
313
			}
314
		}
315
316
		// Second pass, use up any remaining width for other columns. For
317
		// scrolling tables we need to subtract the width of the scrollbar. It
318
		// may not be requires which makes this sub-optimal, but it would
319
		// require another full redraw to make complete use of those extra few
320
		// pixels
321
		var scrolling = dt.settings()[0].oScroll;
322
		var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0;
323
		var widthAvailable = dt.table().container().offsetWidth - bar;
324
		var usedWidth = widthAvailable - requiredWidth;
325
326
		// Control column needs to always be included. This makes it sub-
327
		// optimal in terms of using the available with, but to stop layout
328
		// thrashing or overflow. Also we need to account for the control column
329
		// width first so we know how much width is available for the other
330
		// columns, since the control column might not be the first one shown
331
		for ( i=0, ien=display.length ; i<ien ; i++ ) {
332
			if ( columns[i].control ) {
333
				usedWidth -= columns[i].minWidth;
334
			}
335
		}
336
337
		// Allow columns to be shown (counting by priority and then right to
338
		// left) until we run out of room
339
		var empty = false;
340
		for ( i=0, ien=order.length ; i<ien ; i++ ) {
341
			var colIdx = order[i].columnIdx;
342
343
			if ( display[colIdx] === '-' && ! columns[colIdx].control && columns[colIdx].minWidth ) {
344
				// Once we've found a column that won't fit we don't let any
345
				// others display either, or columns might disappear in the
346
				// middle of the table
347
				if ( empty || usedWidth - columns[colIdx].minWidth < 0 ) {
348
					empty = true;
349
					display[colIdx] = false;
350
				}
351
				else {
352
					display[colIdx] = true;
353
				}
354
355
				usedWidth -= columns[colIdx].minWidth;
356
			}
357
		}
358
359
		// Determine if the 'control' column should be shown (if there is one).
360
		// This is the case when there is a hidden column (that is not the
361
		// control column). The two loops look inefficient here, but they are
362
		// trivial and will fly through. We need to know the outcome from the
363
		// first , before the action in the second can be taken
364
		var showControl = false;
365
366
		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
367
			if ( ! columns[i].control && ! columns[i].never && ! display[i] ) {
368
				showControl = true;
369
				break;
370
			}
371
		}
372
373
		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
374
			if ( columns[i].control ) {
375
				display[i] = showControl;
376
			}
377
		}
378
379
		// Finally we need to make sure that there is at least one column that
380
		// is visible
381
		if ( $.inArray( true, display ) === -1 ) {
382
			display[0] = true;
383
		}
384
385
		return display;
386
	},
387
388
389
	/**
390
	 * Create the internal `columns` array with information about the columns
391
	 * for the table. This includes determining which breakpoints the column
392
	 * will appear in, based upon class names in the column, which makes up the
393
	 * vast majority of this method.
394
	 *
395
	 * @private
396
	 */
397
	_classLogic: function ()
398
	{
399
		var that = this;
400
		var calc = {};
0 ignored issues
show
Unused Code introduced by
The variable calc seems to be never used. Consider removing it.
Loading history...
401
		var breakpoints = this.c.breakpoints;
402
		var dt = this.s.dt;
403
		var columns = dt.columns().eq(0).map( function (i) {
404
			var column = this.column(i);
405
			var className = column.header().className;
406
			var priority = dt.settings()[0].aoColumns[i].responsivePriority;
407
408
			if ( priority === undefined ) {
409
				var dataPriority = $(column.header()).data('priority');
410
411
				priority = dataPriority !== undefined ?
412
					dataPriority * 1 :
413
					10000;
414
			}
415
416
			return {
417
				className: className,
418
				includeIn: [],
419
				auto:      false,
420
				control:   false,
421
				never:     className.match(/\bnever\b/) ? true : false,
422
				priority:  priority
423
			};
424
		} );
425
426
		// Simply add a breakpoint to `includeIn` array, ensuring that there are
427
		// no duplicates
428
		var add = function ( colIdx, name ) {
429
			var includeIn = columns[ colIdx ].includeIn;
430
431
			if ( $.inArray( name, includeIn ) === -1 ) {
432
				includeIn.push( name );
433
			}
434
		};
435
436
		var column = function ( colIdx, name, operator, matched ) {
437
			var size, i, ien;
438
439
			if ( ! operator ) {
440
				columns[ colIdx ].includeIn.push( name );
441
			}
442
			else if ( operator === 'max-' ) {
443
				// Add this breakpoint and all smaller
444
				size = that._find( name ).width;
445
446
				for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
447
					if ( breakpoints[i].width <= size ) {
448
						add( colIdx, breakpoints[i].name );
449
					}
450
				}
451
			}
452
			else if ( operator === 'min-' ) {
453
				// Add this breakpoint and all larger
454
				size = that._find( name ).width;
455
456
				for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
457
					if ( breakpoints[i].width >= size ) {
458
						add( colIdx, breakpoints[i].name );
459
					}
460
				}
461
			}
462
			else if ( operator === 'not-' ) {
463
				// Add all but this breakpoint
464
				for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
465
					if ( breakpoints[i].name.indexOf( matched ) === -1 ) {
466
						add( colIdx, breakpoints[i].name );
467
					}
468
				}
469
			}
470
		};
471
472
		// Loop over each column and determine if it has a responsive control
473
		// class
474
		columns.each( function ( col, i ) {
475
			var classNames = col.className.split(' ');
476
			var hasClass = false;
477
478
			// Split the class name up so multiple rules can be applied if needed
479
			for ( var k=0, ken=classNames.length ; k<ken ; k++ ) {
480
				var className = $.trim( classNames[k] );
481
482
				if ( className === 'all' ) {
483
					// Include in all
484
					hasClass = true;
0 ignored issues
show
Unused Code introduced by
The assignment to variable hasClass seems to be never used. Consider removing it.
Loading history...
485
					col.includeIn = $.map( breakpoints, function (a) {
486
						return a.name;
487
					} );
488
					return;
489
				}
490
				else if ( className === 'none' || col.never ) {
491
					// Include in none (default) and no auto
492
					hasClass = true;
493
					return;
494
				}
495
				else if ( className === 'control' ) {
496
					// Special column that is only visible, when one of the other
497
					// columns is hidden. This is used for the details control
498
					hasClass = true;
499
					col.control = true;
500
					return;
501
				}
502
503
				$.each( breakpoints, function ( j, breakpoint ) {
504
					// Does this column have a class that matches this breakpoint?
505
					var brokenPoint = breakpoint.name.split('-');
506
					var re = new RegExp( '(min\\-|max\\-|not\\-)?('+brokenPoint[0]+')(\\-[_a-zA-Z0-9])?' );
507
					var match = className.match( re );
0 ignored issues
show
Bug introduced by
The variable className is changed as part of the for loop for example by $.trim(classNames.k) on line 480. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
508
509
					if ( match ) {
510
						hasClass = true;
511
512
						if ( match[2] === brokenPoint[0] && match[3] === '-'+brokenPoint[1] ) {
513
							// Class name matches breakpoint name fully
514
							column( i, breakpoint.name, match[1], match[2]+match[3] );
515
						}
516
						else if ( match[2] === brokenPoint[0] && ! match[3] ) {
517
							// Class name matched primary breakpoint name with no qualifier
518
							column( i, breakpoint.name, match[1], match[2] );
519
						}
520
					}
521
				} );
522
			}
523
524
			// If there was no control class, then automatic sizing is used
525
			if ( ! hasClass ) {
526
				col.auto = true;
527
			}
528
		} );
529
530
		this.s.columns = columns;
531
	},
532
533
534
	/**
535
	 * Show the details for the child row
536
	 *
537
	 * @param  {DataTables.Api} row    API instance for the row
538
	 * @param  {boolean}        update Update flag
539
	 * @private
540
	 */
541
	_detailsDisplay: function ( row, update )
542
	{
543
		var that = this;
544
		var dt = this.s.dt;
545
		var details = this.c.details;
546
547
		if ( details && details.type !== false ) {
548
			var res = details.display( row, update, function () {
549
				return details.renderer(
550
					dt, row[0], that._detailsObj(row[0])
551
				);
552
			} );
553
554
			if ( res === true || res === false ) {
555
				$(dt.table().node()).triggerHandler( 'responsive-display.dt', [dt, row, res, update] );
556
			}
557
		}
558
	},
559
560
561
	/**
562
	 * Initialisation for the details handler
563
	 *
564
	 * @private
565
	 */
566
	_detailsInit: function ()
567
	{
568
		var that    = this;
569
		var dt      = this.s.dt;
570
		var details = this.c.details;
571
572
		// The inline type always uses the first child as the target
573
		if ( details.type === 'inline' ) {
574
			details.target = 'td:first-child, th:first-child';
575
		}
576
577
		// Keyboard accessibility
578
		dt.on( 'draw.dtr', function () {
579
			that._tabIndexes();
580
		} );
581
		that._tabIndexes(); // Initial draw has already happened
582
583
		$( dt.table().body() ).on( 'keyup.dtr', 'td, th', function (e) {
584
			if ( e.keyCode === 13 && $(this).data('dtr-keyboard') ) {
585
				$(this).click();
586
			}
587
		} );
588
589
		// type.target can be a string jQuery selector or a column index
590
		var target   = details.target;
591
		var selector = typeof target === 'string' ? target : 'td, th';
592
593
		// Click handler to show / hide the details rows when they are available
594
		$( dt.table().body() )
595
			.on( 'click.dtr mousedown.dtr mouseup.dtr', selector, function (e) {
596
				// If the table is not collapsed (i.e. there is no hidden columns)
597
				// then take no action
598
				if ( ! $(dt.table().node()).hasClass('collapsed' ) ) {
599
					return;
600
				}
601
602
				// Check that the row is actually a DataTable's controlled node
603
				if ( ! dt.row( $(this).closest('tr') ).length ) {
604
					return;
605
				}
606
607
				// For column index, we determine if we should act or not in the
608
				// handler - otherwise it is already okay
609
				if ( typeof target === 'number' ) {
610
					var targetIdx = target < 0 ?
611
						dt.columns().eq(0).length + target :
612
						target;
613
614
					if ( dt.cell( this ).index().column !== targetIdx ) {
615
						return;
616
					}
617
				}
618
619
				// $().closest() includes itself in its check
620
				var row = dt.row( $(this).closest('tr') );
621
622
				// Check event type to do an action
623
				if ( e.type === 'click' ) {
624
					// The renderer is given as a function so the caller can execute it
625
					// only when they need (i.e. if hiding there is no point is running
626
					// the renderer)
627
					that._detailsDisplay( row, false );
628
				}
629
				else if ( e.type === 'mousedown' ) {
630
					// For mouse users, prevent the focus ring from showing
631
					$(this).css('outline', 'none');
632
				}
633
				else if ( e.type === 'mouseup' ) {
634
					// And then re-allow at the end of the click
635
					$(this).blur().css('outline', '');
636
				}
637
			} );
638
	},
639
640
641
	/**
642
	 * Get the details to pass to a renderer for a row
643
	 * @param  {int} rowIdx Row index
644
	 * @private
645
	 */
646
	_detailsObj: function ( rowIdx )
647
	{
648
		var that = this;
649
		var dt = this.s.dt;
650
651
		return $.map( this.s.columns, function( col, i ) {
652
			// Never and control columns should not be passed to the renderer
653
			if ( col.never || col.control ) {
654
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
655
			}
656
657
			return {
658
				title:       dt.settings()[0].aoColumns[ i ].sTitle,
659
				data:        dt.cell( rowIdx, i ).render( that.c.orthogonal ),
660
				hidden:      dt.column( i ).visible() && !that.s.current[ i ],
661
				columnIndex: i,
662
				rowIndex:    rowIdx
663
			};
664
		} );
665
	},
666
667
668
	/**
669
	 * Find a breakpoint object from a name
670
	 *
671
	 * @param  {string} name Breakpoint name to find
672
	 * @return {object}      Breakpoint description object
673
	 * @private
674
	 */
675
	_find: function ( name )
676
	{
677
		var breakpoints = this.c.breakpoints;
678
679
		for ( var i=0, ien=breakpoints.length ; i<ien ; i++ ) {
680
			if ( breakpoints[i].name === name ) {
681
				return breakpoints[i];
682
			}
683
		}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
684
	},
685
686
687
	/**
688
	 * Re-create the contents of the child rows as the display has changed in
689
	 * some way.
690
	 *
691
	 * @private
692
	 */
693
	_redrawChildren: function ()
694
	{
695
		var that = this;
696
		var dt = this.s.dt;
697
698
		dt.rows( {page: 'current'} ).iterator( 'row', function ( settings, idx ) {
699
			var row = dt.row( idx );
700
701
			that._detailsDisplay( dt.row( idx ), true );
702
		} );
703
	},
704
705
706
	/**
707
	 * Alter the table display for a resized viewport. This involves first
708
	 * determining what breakpoint the window currently is in, getting the
709
	 * column visibilities to apply and then setting them.
710
	 *
711
	 * @private
712
	 */
713
	_resize: function ()
714
	{
715
		var that = this;
716
		var dt = this.s.dt;
717
		var width = $(window).width();
718
		var breakpoints = this.c.breakpoints;
719
		var breakpoint = breakpoints[0].name;
720
		var columns = this.s.columns;
721
		var i, ien;
722
		var oldVis = this.s.current.slice();
723
724
		// Determine what breakpoint we are currently at
725
		for ( i=breakpoints.length-1 ; i>=0 ; i-- ) {
726
			if ( width <= breakpoints[i].width ) {
727
				breakpoint = breakpoints[i].name;
728
				break;
729
			}
730
		}
731
		
732
		// Show the columns for that break point
733
		var columnsVis = this._columnsVisiblity( breakpoint );
734
		this.s.current = columnsVis;
735
736
		// Set the class before the column visibility is changed so event
737
		// listeners know what the state is. Need to determine if there are
738
		// any columns that are not visible but can be shown
739
		var collapsedClass = false;
740
		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
741
			if ( columnsVis[i] === false && ! columns[i].never && ! columns[i].control ) {
742
				collapsedClass = true;
743
				break;
744
			}
745
		}
746
747
		$( dt.table().node() ).toggleClass( 'collapsed', collapsedClass );
748
749
		var changed = false;
750
751
		dt.columns().eq(0).each( function ( colIdx, i ) {
752
			if ( columnsVis[i] !== oldVis[i] ) {
753
				changed = true;
754
				that._setColumnVis( colIdx, columnsVis[i] );
755
			}
756
		} );
757
758
		if ( changed ) {
759
			this._redrawChildren();
760
761
			// Inform listeners of the change
762
			$(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] );
763
		}
764
	},
765
766
767
	/**
768
	 * Determine the width of each column in the table so the auto column hiding
769
	 * has that information to work with. This method is never going to be 100%
770
	 * perfect since column widths can change slightly per page, but without
771
	 * seriously compromising performance this is quite effective.
772
	 *
773
	 * @private
774
	 */
775
	_resizeAuto: function ()
776
	{
777
		var dt = this.s.dt;
778
		var columns = this.s.columns;
779
780
		// Are we allowed to do auto sizing?
781
		if ( ! this.c.auto ) {
782
			return;
783
		}
784
785
		// Are there any columns that actually need auto-sizing, or do they all
786
		// have classes defined
787
		if ( $.inArray( true, $.map( columns, function (c) { return c.auto; } ) ) === -1 ) {
788
			return;
789
		}
790
791
		// Clone the table with the current data in it
792
		var tableWidth   = dt.table().node().offsetWidth;
0 ignored issues
show
Unused Code introduced by
The variable tableWidth seems to be never used. Consider removing it.
Loading history...
793
		var columnWidths = dt.columns;
0 ignored issues
show
Unused Code introduced by
The variable columnWidths seems to be never used. Consider removing it.
Loading history...
794
		var clonedTable  = dt.table().node().cloneNode( false );
795
		var clonedHeader = $( dt.table().header().cloneNode( false ) ).appendTo( clonedTable );
796
		var clonedBody   = $( dt.table().body() ).clone( false, false ).empty().appendTo( clonedTable ); // use jQuery because of IE8
797
798
		// Header
799
		var headerCells = dt.columns()
800
			.header()
801
			.filter( function (idx) {
802
				return dt.column(idx).visible();
803
			} )
804
			.to$()
805
			.clone( false )
806
			.css( 'display', 'table-cell' );
807
808
		// Body rows - we don't need to take account of DataTables' column
809
		// visibility since we implement our own here (hence the `display` set)
810
		$(clonedBody)
811
			.append( $(dt.rows( { page: 'current' } ).nodes()).clone( false ) )
812
			.find( 'th, td' ).css( 'display', '' );
813
814
		// Footer
815
		var footer = dt.table().footer();
816
		if ( footer ) {
817
			var clonedFooter = $( footer.cloneNode( false ) ).appendTo( clonedTable );
818
			var footerCells = dt.columns()
819
				.footer()
820
				.filter( function (idx) {
821
					return dt.column(idx).visible();
822
				} )
823
				.to$()
824
				.clone( false )
825
				.css( 'display', 'table-cell' );
826
827
			$('<tr/>')
828
				.append( footerCells )
829
				.appendTo( clonedFooter );
830
		}
831
832
		$('<tr/>')
833
			.append( headerCells )
834
			.appendTo( clonedHeader );
835
836
		// In the inline case extra padding is applied to the first column to
837
		// give space for the show / hide icon. We need to use this in the
838
		// calculation
839
		if ( this.c.details.type === 'inline' ) {
840
			$(clonedTable).addClass( 'dtr-inline collapsed' );
841
		}
842
		
843
		// It is unsafe to insert elements with the same name into the DOM
844
		// multiple times. For example, cloning and inserting a checked radio
845
		// clears the chcecked state of the original radio.
846
		$( clonedTable ).find( '[name]' ).removeAttr( 'name' );
847
		
848
		var inserted = $('<div/>')
849
			.css( {
850
				width: 1,
851
				height: 1,
852
				overflow: 'hidden'
853
			} )
854
			.append( clonedTable );
855
856
		inserted.insertBefore( dt.table().node() );
857
858
		// The cloned header now contains the smallest that each column can be
859
		headerCells.each( function (i) {
860
			var idx = dt.column.index( 'fromVisible', i );
861
			columns[ idx ].minWidth =  this.offsetWidth || 0;
862
		} );
863
864
		inserted.remove();
865
	},
866
867
	/**
868
	 * Set a column's visibility.
869
	 *
870
	 * We don't use DataTables' column visibility controls in order to ensure
871
	 * that column visibility can Responsive can no-exist. Since only IE8+ is
872
	 * supported (and all evergreen browsers of course) the control of the
873
	 * display attribute works well.
874
	 *
875
	 * @param {integer} col      Column index
876
	 * @param {boolean} showHide Show or hide (true or false)
877
	 * @private
878
	 */
879
	_setColumnVis: function ( col, showHide )
880
	{
881
		var dt = this.s.dt;
882
		var display = showHide ? '' : 'none'; // empty string will remove the attr
883
884
		$( dt.column( col ).header() ).css( 'display', display );
885
		$( dt.column( col ).footer() ).css( 'display', display );
886
		dt.column( col ).nodes().to$().css( 'display', display );
887
	},
888
889
890
	/**
891
	 * Update the cell tab indexes for keyboard accessibility. This is called on
892
	 * every table draw - that is potentially inefficient, but also the least
893
	 * complex option given that column visibility can change on the fly. Its a
894
	 * shame user-focus was removed from CSS 3 UI, as it would have solved this
895
	 * issue with a single CSS statement.
896
	 *
897
	 * @private
898
	 */
899
	_tabIndexes: function ()
900
	{
901
		var dt = this.s.dt;
902
		var cells = dt.cells( { page: 'current' } ).nodes().to$();
903
		var ctx = dt.settings()[0];
904
		var target = this.c.details.target;
905
906
		cells.filter( '[data-dtr-keyboard]' ).removeData( '[data-dtr-keyboard]' );
907
908
		var selector = typeof target === 'number' ?
909
			':eq('+target+')' :
910
			target;
911
912
		$( selector, dt.rows( { page: 'current' } ).nodes() )
913
			.attr( 'tabIndex', ctx.iTabIndex )
914
			.data( 'dtr-keyboard', 1 );
915
	}
916
} );
917
918
919
/**
920
 * List of default breakpoints. Each item in the array is an object with two
921
 * properties:
922
 *
923
 * * `name` - the breakpoint name.
924
 * * `width` - the breakpoint width
925
 *
926
 * @name Responsive.breakpoints
927
 * @static
928
 */
929
Responsive.breakpoints = [
930
	{ name: 'desktop',  width: Infinity },
931
	{ name: 'tablet-l', width: 1024 },
932
	{ name: 'tablet-p', width: 768 },
933
	{ name: 'mobile-l', width: 480 },
934
	{ name: 'mobile-p', width: 320 }
935
];
936
937
938
/**
939
 * Display methods - functions which define how the hidden data should be shown
940
 * in the table.
941
 *
942
 * @namespace
943
 * @name Responsive.defaults
944
 * @static
945
 */
946
Responsive.display = {
947
	childRow: function ( row, update, render ) {
948
		if ( update ) {
949
			if ( $(row.node()).hasClass('parent') ) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if $(row.node()).hasClass("parent") is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
950
				row.child( render(), 'child' ).show();
951
952
				return true;
953
			}
954
		}
955
		else {
956
			if ( ! row.child.isShown()  ) {
957
				row.child( render(), 'child' ).show();
958
				$( row.node() ).addClass( 'parent' );
959
960
				return true;
961
			}
962
			else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
963
				row.child( false );
964
				$( row.node() ).removeClass( 'parent' );
965
966
				return false;
967
			}
968
		}
969
	},
970
971
	childRowImmediate: function ( row, update, render ) {
972
		if ( (! update && row.child.isShown()) || ! row.responsive.hasHidden() ) {
973
			// User interaction and the row is show, or nothing to show
974
			row.child( false );
975
			$( row.node() ).removeClass( 'parent' );
976
977
			return false;
978
		}
979
		else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
980
			// Display
981
			row.child( render(), 'child' ).show();
982
			$( row.node() ).addClass( 'parent' );
983
984
			return true;
985
		}
986
	},
987
988
	// This is a wrapper so the modal options for Bootstrap and jQuery UI can
989
	// have options passed into them. This specific one doesn't need to be a
990
	// function but it is for consistency in the `modal` name
991
	modal: function ( options ) {
992
		return function ( row, update, render ) {
993
			if ( ! update ) {
994
				// Show a modal
995
				var close = function () {
996
					modal.remove(); // will tidy events for us
997
					$(document).off( 'keypress.dtr' );
998
				};
999
1000
				var modal = $('<div class="dtr-modal"/>')
1001
					.append( $('<div class="dtr-modal-display"/>')
1002
						.append( $('<div class="dtr-modal-content"/>')
1003
							.append( render() )
1004
						)
1005
						.append( $('<div class="dtr-modal-close">&times;</div>' )
1006
							.click( function () {
1007
								close();
1008
							} )
1009
						)
1010
					)
1011
					.append( $('<div class="dtr-modal-background"/>')
1012
						.click( function () {
1013
							close();
1014
						} )
1015
					)
1016
					.appendTo( 'body' );
1017
1018
				$(document).on( 'keyup.dtr', function (e) {
1019
					if ( e.keyCode === 27 ) {
1020
						e.stopPropagation();
1021
1022
						close();
1023
					}
1024
				} );
1025
			}
1026
			else {
1027
				$('div.dtr-modal-content')
1028
					.empty()
1029
					.append( render() );
1030
			}
1031
1032
			if ( options && options.header ) {
1033
				$('div.dtr-modal-content').prepend(
1034
					'<h2>'+options.header( row )+'</h2>'
1035
				);
1036
			}
1037
		};
1038
	}
1039
};
1040
1041
1042
/**
1043
 * Display methods - functions which define how the hidden data should be shown
1044
 * in the table.
1045
 *
1046
 * @namespace
1047
 * @name Responsive.defaults
1048
 * @static
1049
 */
1050
Responsive.renderer = {
1051
	listHidden: function () {
1052
		return function ( api, rowIdx, columns ) {
1053
			var data = $.map( columns, function ( col ) {
1054
				return col.hidden ?
1055
					'<li data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
1056
						'<span class="dtr-title">'+
1057
							col.title+
1058
						'</span> '+
1059
						'<span class="dtr-data">'+
1060
							col.data+
1061
						'</span>'+
1062
					'</li>' :
1063
					'';
1064
			} ).join('');
1065
1066
			return data ?
1067
				$('<ul data-dtr-index="'+rowIdx+'"/>').append( data ) :
1068
				false;
1069
		}
1070
	},
1071
1072
	tableAll: function ( options ) {
1073
		options = $.extend( {
1074
			tableClass: ''
1075
		}, options );
1076
1077
		return function ( api, rowIdx, columns ) {
1078
			var data = $.map( columns, function ( col ) {
1079
				return '<tr data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
1080
						'<td>'+col.title+':'+'</td> '+
1081
						'<td>'+col.data+'</td>'+
1082
					'</tr>';
1083
			} ).join('');
1084
1085
			return $('<table class="'+options.tableClass+'" width="100%"/>').append( data );
1086
		}
1087
	}
1088
};
1089
1090
/**
1091
 * Responsive default settings for initialisation
1092
 *
1093
 * @namespace
1094
 * @name Responsive.defaults
1095
 * @static
1096
 */
1097
Responsive.defaults = {
1098
	/**
1099
	 * List of breakpoints for the instance. Note that this means that each
1100
	 * instance can have its own breakpoints. Additionally, the breakpoints
1101
	 * cannot be changed once an instance has been creased.
1102
	 *
1103
	 * @type {Array}
1104
	 * @default Takes the value of `Responsive.breakpoints`
1105
	 */
1106
	breakpoints: Responsive.breakpoints,
1107
1108
	/**
1109
	 * Enable / disable auto hiding calculations. It can help to increase
1110
	 * performance slightly if you disable this option, but all columns would
1111
	 * need to have breakpoint classes assigned to them
1112
	 *
1113
	 * @type {Boolean}
1114
	 * @default  `true`
1115
	 */
1116
	auto: true,
1117
1118
	/**
1119
	 * Details control. If given as a string value, the `type` property of the
1120
	 * default object is set to that value, and the defaults used for the rest
1121
	 * of the object - this is for ease of implementation.
1122
	 *
1123
	 * The object consists of the following properties:
1124
	 *
1125
	 * * `display` - A function that is used to show and hide the hidden details
1126
	 * * `renderer` - function that is called for display of the child row data.
1127
	 *   The default function will show the data from the hidden columns
1128
	 * * `target` - Used as the selector for what objects to attach the child
1129
	 *   open / close to
1130
	 * * `type` - `false` to disable the details display, `inline` or `column`
1131
	 *   for the two control types
1132
	 *
1133
	 * @type {Object|string}
1134
	 */
1135
	details: {
1136
		display: Responsive.display.childRow,
1137
1138
		renderer: Responsive.renderer.listHidden(),
1139
1140
		target: 0,
1141
1142
		type: 'inline'
1143
	},
1144
1145
	/**
1146
	 * Orthogonal data request option. This is used to define the data type
1147
	 * requested when Responsive gets the data to show in the child row.
1148
	 *
1149
	 * @type {String}
1150
	 */
1151
	orthogonal: 'display'
1152
};
1153
1154
1155
/*
1156
 * API
1157
 */
1158
var Api = $.fn.dataTable.Api;
1159
1160
// Doesn't do anything - work around for a bug in DT... Not documented
1161
Api.register( 'responsive()', function () {
1162
	return this;
1163
} );
1164
1165
Api.register( 'responsive.index()', function ( li ) {
1166
	li = $(li);
1167
1168
	return {
1169
		column: li.data('dtr-index'),
1170
		row:    li.parent().data('dtr-index')
1171
	};
1172
} );
1173
1174
Api.register( 'responsive.rebuild()', function () {
1175
	return this.iterator( 'table', function ( ctx ) {
1176
		if ( ctx._responsive ) {
1177
			ctx._responsive._classLogic();
1178
		}
1179
	} );
1180
} );
1181
1182
Api.register( 'responsive.recalc()', function () {
1183
	return this.iterator( 'table', function ( ctx ) {
1184
		if ( ctx._responsive ) {
1185
			ctx._responsive._resizeAuto();
1186
			ctx._responsive._resize();
1187
		}
1188
	} );
1189
} );
1190
1191
Api.register( 'responsive.hasHidden()', function () {
1192
	var ctx = this.context[0];
1193
1194
	return ctx._responsive ?
1195
		$.inArray( false, ctx._responsive.s.current ) !== -1 :
1196
		false;
1197
} );
1198
1199
1200
/**
1201
 * Version information
1202
 *
1203
 * @name Responsive.version
1204
 * @static
1205
 */
1206
Responsive.version = '2.1.0';
1207
1208
1209
$.fn.dataTable.Responsive = Responsive;
1210
$.fn.DataTable.Responsive = Responsive;
1211
1212
// Attach a listener to the document which listens for DataTables initialisation
1213
// events so we can automatically initialise
1214
$(document).on( 'preInit.dt.dtr', function (e, settings, json) {
0 ignored issues
show
Unused Code introduced by
The parameter json is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1215
	if ( e.namespace !== 'dt' ) {
1216
		return;
1217
	}
1218
1219
	if ( $(settings.nTable).hasClass( 'responsive' ) ||
1220
		 $(settings.nTable).hasClass( 'dt-responsive' ) ||
1221
		 settings.oInit.responsive ||
1222
		 DataTable.defaults.responsive
1223
	) {
1224
		var init = settings.oInit.responsive;
1225
1226
		if ( init !== false ) {
1227
			new Responsive( settings, $.isPlainObject( init ) ? init : {}  );
0 ignored issues
show
Unused Code Best Practice introduced by
The object created with new Responsive(settings,...bject(init) ? init: {}) is not used but discarded. Consider invoking another function instead of a constructor if you are doing this purely for side effects.
Loading history...
1228
		}
1229
	}
1230
} );
1231
1232
1233
return Responsive;
1234
}));
1235