Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
875 lars 1
/*! Select for DataTables 1.0.1
2
 * 2015 SpryMedia Ltd - datatables.net/license/mit
3
 */
4
 
5
/**
6
 * @summary     Select for DataTables
7
 * @description A collection of API methods, events and buttons for DataTables
8
 *   that provides selection options of the items in a DataTable
9
 * @version     1.0.1
10
 * @file        dataTables.select.js
11
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
12
 * @contact     datatables.net/forums
13
 * @copyright   Copyright 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/extensions/select
23
 */
24
(function(window, document, undefined) {
25
 
26
 
27
var factory = function( $, DataTable ) {
28
"use strict";
29
 
30
// Version information for debugger
31
DataTable.select = {};
32
DataTable.select.version = '1.0.1';
33
 
34
/*
35
 
36
Select is a collection of API methods, event handlers, event emitters and
37
buttons (for the `Buttons` extension) for DataTables. It provides the following
38
features, with an overview of how they are implemented:
39
 
40
## Selection of rows, columns and cells. Whether an item is selected or not is
41
   stored in:
42
 
43
* rows: a `_select_selected` property which contains a boolean value of the
44
  DataTables' `aoData` object for each row
45
* columns: a `_select_selected` property which contains a boolean value of the
46
  DataTables' `aoColumns` object for each column
47
* cells: a `_selected_cells` property which contains an array of boolean values
48
  of the `aoData` object for each row. The array is the same length as the
49
  columns array, with each element of it representing a cell.
50
 
51
This method of using boolean flags allows Select to operate when nodes have not
52
been created for rows / cells (DataTables' defer rendering feature).
53
 
54
## API methods
55
 
56
A range of API methods are available for triggering selection and de-selection
57
of rows. Methods are also available to configure the selection events that can
58
be triggered by an end user (such as which items are to be selected). To a large
59
extent, these of API methods *is* Select. It is basically a collection of helper
60
functions that can be used to select items in a DataTable.
61
 
62
Configuration of select is held in the object `_select` which is attached to the
63
DataTables settings object on initialisation. Select being available on a table
64
is not optional when Select is loaded, but its default is for selection only to
65
be available via the API - so the end user wouldn't be able to select rows
66
without additional configuration.
67
 
68
The `_select` object contains the following properties:
69
 
70
```
71
{
72
	items:string     - Can be `rows`, `columns` or `cells`. Defines what item
73
	                   will be selected if the user is allowed to activate row
74
	                   selection using the mouse.
75
	style:string     - Can be `none`, `single`, `multi` or `os`. Defines the
76
	                   interaction style when selecting items
77
	blurable:boolean - If row selection can be cleared by clicking outside of
78
	                   the table
79
	info:boolean     - If the selection summary should be shown in the table
80
	                   information elements
81
}
82
```
83
 
84
In addition to the API methods, Select also extends the DataTables selector
85
options for rows, columns and cells adding a `selected` option to the selector
86
options object, allowing the developer to select only selected items or
87
unselected items.
88
 
89
## Mouse selection of items
90
 
91
Clicking on items can be used to select items. This is done by a simple event
92
handler that will select the items using the API methods.
93
 
94
 */
95
 
96
 
97
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
98
 * Local functions
99
 */
100
 
101
/**
102
 * Add one or more cells to the selection when shift clicking in OS selection
103
 * style cell selection.
104
 *
105
 * Cell range is more complicated than row and column as we want to select
106
 * in the visible grid rather than by index in sequence. For example, if you
107
 * click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1
108
 * should also be selected (and not 1-3, 1-4. etc)
109
 *
110
 * @param  {DataTable.Api} dt   DataTable
111
 * @param  {object}        idx  Cell index to select to
112
 * @param  {object}        last Cell index to select from
113
 * @private
114
 */
115
function cellRange( dt, idx, last )
116
{
117
	var indexes;
118
	var columnIndexes;
119
	var rowIndexes;
120
	var selectColumns = function ( start, end ) {
121
		if ( start > end ) {
122
			var tmp = end;
123
			end = start;
124
			start = tmp;
125
		}
126
 
127
		var record = false;
128
		return dt.columns( ':visible' ).indexes().filter( function (i) {
129
			if ( i === start ) {
130
				record = true;
131
			}
132
 
133
			if ( i === end ) { // not else if, as start might === end
134
				record = false;
135
				return true;
136
			}
137
 
138
			return record;
139
		} );
140
	};
141
 
142
	var selectRows = function ( start, end ) {
143
		var indexes = dt.rows( { search: 'applied' } ).indexes();
144
 
145
		// Which comes first - might need to swap
146
		if ( indexes.indexOf( start ) > indexes.indexOf( end ) ) {
147
			var tmp = end;
148
			end = start;
149
			start = tmp;
150
		}
151
 
152
		var record = false;
153
		return indexes.filter( function (i) {
154
			if ( i === start ) {
155
				record = true;
156
			}
157
 
158
			if ( i === end ) {
159
				record = false;
160
				return true;
161
			}
162
 
163
			return record;
164
		} );
165
	};
166
 
167
	if ( ! dt.cells( { selected: true } ).any() && ! last ) {
168
		// select from the top left cell to this one
169
		columnIndexes = selectColumns( 0, idx.column );
170
		rowIndexes = selectRows( 0 , idx.row );
171
	}
172
	else {
173
		// Get column indexes between old and new
174
		columnIndexes = selectColumns( last.column, idx.column );
175
		rowIndexes = selectRows( last.row , idx.row );
176
	}
177
 
178
	indexes = dt.cells( rowIndexes, columnIndexes ).flatten();
179
 
180
	if ( ! dt.cells( idx, { selected: true } ).any() ) {
181
		// Select range
182
		dt.cells( indexes ).select();
183
	}
184
	else {
185
		// Deselect range
186
		dt.cells( indexes ).deselect();
187
	}
188
}
189
 
190
/**
191
 * Disable mouse selection by removing the selectors
192
 *
193
 * @param {DataTable.Api} dt DataTable to remove events from
194
 * @private
195
 */
196
function disableMouseSelection( dt )
197
{
198
	var ctx = dt.settings()[0];
199
	var selector = ctx._select.selector;
200
 
201
	$( dt.table().body() )
202
		.off( 'mousedown.dtSelect', selector )
203
		.off( 'mouseup.dtSelect', selector )
204
		.off( 'click.dtSelect', selector );
205
 
206
	$('body').off( 'click.dtSelect' );
207
}
208
 
209
/**
210
 * Attach mouse listeners to the table to allow mouse selection of items
211
 *
212
 * @param {DataTable.Api} dt DataTable to remove events from
213
 * @private
214
 */
215
function enableMouseSelection ( dt )
216
{
217
	var body = $( dt.table().body() );
218
	var ctx = dt.settings()[0];
219
	var selector = ctx._select.selector;
220
 
221
	body
222
		.on( 'mousedown.dtSelect', selector, function(e) {
223
			// Disallow text selection for shift clicking on the table so multi
224
			// element selection doesn't look terrible!
225
			if ( e.shiftKey ) {
226
				body
227
					.css( '-moz-user-select', 'none' )
228
					.one('selectstart.dtSelect', selector, function () {
229
						return false;
230
					} );
231
			}
232
		} )
233
		.on( 'mouseup.dtSelect', selector, function(e) {
234
			// Allow text selection to occur again, Mozilla style (tested in FF
235
			// 35.0.1 - still required)
236
			body.css( '-moz-user-select', '' );
237
		} )
238
		.on( 'click.dtSelect', selector, function ( e ) {
239
			var items = dt.select.items();
240
			var cellIndex = dt.cell( this ).index();
241
			var idx;
242
 
243
			var ctx = dt.settings()[0];
244
 
245
			// Ignore clicks inside a sub-table
246
			if ( $(e.target).closest('tbody')[0] != body[0] ) {
247
				return;
248
			}
249
 
250
			// Check the cell actually belongs to the host DataTable (so child rows,
251
			// etc, are ignored)
252
			if ( ! dt.cell( e.target ).any() ) {
253
				return;
254
			}
255
 
256
			if ( items === 'row' ) {
257
				idx = cellIndex.row;
258
				typeSelect( e, dt, ctx, 'row', idx );
259
			}
260
			else if ( items === 'column' ) {
261
				idx = dt.cell( e.target ).index().column;
262
				typeSelect( e, dt, ctx, 'column', idx );
263
			}
264
			else if ( items === 'cell' ) {
265
				idx = dt.cell( e.target ).index();
266
				typeSelect( e, dt, ctx, 'cell', idx );
267
			}
268
 
269
			ctx._select_lastCell = cellIndex;
270
		} );
271
 
272
	// Blurable
273
	$('body').on( 'click.dtSelect', function ( e ) {
274
		if ( ctx._select.blurable ) {
275
			// If the click was inside the DataTables container, don't blur
276
			if ( $(e.target).parents().filter( dt.table().container() ).length ) {
277
				return;
278
			}
279
 
280
			// Don't blur in Editor form
281
			if ( $(e.target).parents('div.DTE').length ) {
282
				return;
283
			}
284
 
285
			clear( ctx, true );
286
		}
287
	} );
288
}
289
 
290
/**
291
 * Trigger an event on a DataTable
292
 *
293
 * @param {DataTable.Api} api      DataTable to trigger events on
294
 * @param  {boolean}      selected true if selected, false if deselected
295
 * @param  {string}       type     Item type acting on
296
 * @param  {boolean}      any      Require that there are values before
297
 *     triggering
298
 * @private
299
 */
300
function eventTrigger ( api, type, args, any )
301
{
302
	if ( any && ! api.flatten().length ) {
303
		return;
304
	}
305
 
306
	args.unshift( api );
307
 
308
	$(api.table().node()).triggerHandler( type+'.dt', args );
309
}
310
 
311
/**
312
 * Update the information element of the DataTable showing information about the
313
 * items selected. This is done by adding tags to the existing text
314
 *
315
 * @param {DataTable.Api} api DataTable to update
316
 * @private
317
 */
318
function info ( api )
319
{
320
	var ctx = api.settings()[0];
321
 
322
	if ( ! ctx._select.info || ! ctx.aanFeatures.i ) {
323
		return;
324
	}
325
 
326
	var output  = $('<span class="select-info"/>');
327
	var add = function ( name, num ) {
328
		output.append( $('<span class="select-item"/>').append( api.i18n(
329
			'select.'+name+'s',
330
			{ _: '%d '+name+'s selected', 0: '', 1: '1 '+name+' selected' },
331
			num
332
		) ) );
333
	};
334
 
335
	add( 'row',    api.rows( { selected: true } ).flatten().length );
336
	add( 'column', api.columns( { selected: true } ).flatten().length );
337
	add( 'cell',   api.cells( { selected: true } ).flatten().length );
338
 
339
	// Internal knowledge of DataTables to loop over all information elements
340
	$.each( ctx.aanFeatures.i, function ( i, el ) {
341
		el = $(el);
342
 
343
		var exisiting = el.children('span.select-info');
344
		if ( exisiting.length ) {
345
			exisiting.remove();
346
		}
347
 
348
		if ( output.text() !== '' ) {
349
			el.append( output );
350
		}
351
	} );
352
}
353
 
354
/**
355
 * Initialisation of a new table. Attach event handlers and callbacks to allow
356
 * Select to operate correctly.
357
 *
358
 * This will occur _after_ the initial DataTables initialisation, although
359
 * before Ajax data is rendered, if there is ajax data
360
 *
361
 * @param  {DataTable.settings} ctx Settings object to operate on
362
 * @private
363
 */
364
function init ( ctx ) {
365
	var api = new DataTable.Api( ctx );
366
 
367
	// Row callback so that classes can be added to rows and cells if the item
368
	// was selected before the element was created. This will happen with the
369
	// `deferRender` option enabled.
370
	//
371
	// This method of attaching to `aoRowCreatedCallback` is a hack until
372
	// DataTables has proper events for row manipulation If you are reviewing
373
	// this code to create your own plug-ins, please do not do this!
374
	ctx.aoRowCreatedCallback.push( {
375
		fn: function ( row, data, index ) {
376
			var i, ien;
377
			var d = ctx.aoData[ index ];
378
 
379
			// Row
380
			if ( d._select_selected ) {
381
				$( row ).addClass( 'selected' );
382
			}
383
 
384
			// Cells and columns - if separated out, we would need to do two
385
			// loops, so it makes sense to combine them into a single one
386
			for ( i=0, ien=ctx.aoColumns.length ; i<ien ; i++ ) {
387
				if ( ctx.aoColumns[i]._select_selected || (d._selected_cells && d._selected_cells[i]) ) {
388
					$(d.anCells[i]).addClass( 'selected' );
389
				}
390
			}
391
		},
392
		sName: 'select-deferRender'
393
	} );
394
 
395
	// On Ajax reload we want to reselect all rows which are currently selected,
396
	// if there is an rowId (i.e. a unique value to identify each row with)
397
	api.on( 'preXhr.dt.dtSelect', function () {
398
		// note that column selection doesn't need to be cached and then
399
		// reselected, as they are already selected
400
		var rows = api.rows( { selected: true } ).ids( true ).filter( function ( d ) {
401
			return d !== undefined;
402
		} );
403
 
404
		var cells = api.cells( { selected: true } ).eq(0).map( function ( cellIdx ) {
405
			var id = api.row( cellIdx.row ).id( true );
406
			return id ?
407
				{ row: id, column: cellIdx.column } :
408
				undefined;
409
		} ).filter( function ( d ) {
410
			return d !== undefined;
411
		} );
412
 
413
		// On the next draw, reselect the currently selected items
414
		api.one( 'draw.dt.dtSelect', function () {
415
			api.rows( rows ).select();
416
 
417
			// `cells` is not a cell index selector, so it needs a loop
418
			if ( cells.any() ) {
419
				cells.each( function ( id ) {
420
					api.cells( id.row, id.column ).select();
421
				} );
422
			}
423
		} );
424
	} );
425
 
426
	// Update the table information element with selected item summary
427
	api.on( 'draw.dtSelect.dt select.dtSelect.dt deselect.dtSelect.dt', function () {
428
		info( api );
429
	} );
430
 
431
	// Clean up and release
432
	api.on( 'destroy.dtSelect', function () {
433
		disableMouseSelection( api );
434
		api.off( '.dtSelect' );
435
	} );
436
}
437
 
438
/**
439
 * Add one or more items (rows or columns) to the selection when shift clicking
440
 * in OS selection style
441
 *
442
 * @param  {DataTable.Api} dt   DataTable
443
 * @param  {string}        type Row or column range selector
444
 * @param  {object}        idx  Item index to select to
445
 * @param  {object}        last Item index to select from
446
 * @private
447
 */
448
function rowColumnRange( dt, type, idx, last )
449
{
450
	// Add a range of rows from the last selected row to this one
451
	var indexes = dt[type+'s']( { search: 'applied' } ).indexes();
452
	var idx1 = $.inArray( last, indexes );
453
	var idx2 = $.inArray( idx, indexes );
454
 
455
	if ( ! dt[type+'s']( { selected: true } ).any() && idx1 === -1 ) {
456
		// select from top to here - slightly odd, but both Windows and Mac OS
457
		// do this
458
		indexes.splice( $.inArray( idx, indexes )+1, indexes.length );
459
	}
460
	else {
461
		// reverse so we can shift click 'up' as well as down
462
		if ( idx1 > idx2 ) {
463
			var tmp = idx2;
464
			idx2 = idx1;
465
			idx1 = tmp;
466
		}
467
 
468
		indexes.splice( idx2+1, indexes.length );
469
		indexes.splice( 0, idx1 );
470
	}
471
 
472
	if ( ! dt[type]( idx, { selected: true } ).any() ) {
473
		// Select range
474
		dt[type+'s']( indexes ).select();
475
	}
476
	else {
477
		// Deselect range - need to keep the clicked on row selected
478
		indexes.splice( $.inArray( idx, indexes ), 1 );
479
		dt[type+'s']( indexes ).deselect();
480
	}
481
}
482
 
483
/**
484
 * Clear all selected items
485
 *
486
 * @param  {DataTable.settings} ctx Settings object of the host DataTable
487
 * @param  {boolean} [force=false] Force the de-selection to happen, regardless
488
 *     of selection style
489
 * @private
490
 */
491
function clear( ctx, force )
492
{
493
	if ( force || ctx._select.style === 'single' ) {
494
		var api = new DataTable.Api( ctx );
495
 
496
		api.rows( { selected: true } ).deselect();
497
		api.columns( { selected: true } ).deselect();
498
		api.cells( { selected: true } ).deselect();
499
	}
500
}
501
 
502
/**
503
 * Select items based on the current configuration for style and items.
504
 *
505
 * @param  {object}             e    Mouse event object
506
 * @param  {DataTables.Api}     dt   DataTable
507
 * @param  {DataTable.settings} ctx  Settings object of the host DataTable
508
 * @param  {string}             type Items to select
509
 * @param  {int|object}         idx  Index of the item to select
510
 * @private
511
 */
512
function typeSelect ( e, dt, ctx, type, idx )
513
{
514
	var style = dt.select.style();
515
	var isSelected = dt[type]( idx, { selected: true } ).any();
516
 
517
	if ( style === 'os' ) {
518
		if ( e.ctrlKey || e.metaKey ) {
519
			// Add or remove from the selection
520
			dt[type]( idx ).select( ! isSelected );
521
		}
522
		else if ( e.shiftKey ) {
523
			if ( type === 'cell' ) {
524
				cellRange( dt, idx, ctx._select_lastCell || null );
525
			}
526
			else {
527
				rowColumnRange( dt, type, idx, ctx._select_lastCell ?
528
					ctx._select_lastCell[type] :
529
					null
530
				);
531
			}
532
		}
533
		else {
534
			// No cmd or shift click - deselect if selected, or select
535
			// this row only
536
			var selected = dt[type+'s']( { selected: true } );
537
 
538
			if ( isSelected && selected.flatten().length === 1 ) {
539
				dt[type]( idx ).deselect();
540
			}
541
			else {
542
				selected.deselect();
543
				dt[type]( idx ).select();
544
			}
545
		}
546
	}
547
	else {
548
		dt[ type ]( idx ).select( ! isSelected );
549
	}
550
}
551
 
552
 
553
 
554
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
555
 * DataTables selectors
556
 */
557
 
558
// row and column are basically identical just assigned to different properties
559
// and checking a different array, so we can dynamically create the functions to
560
// reduce the code size
561
$.each( [
562
	{ type: 'row', prop: 'aoData' },
563
	{ type: 'column', prop: 'aoColumns' }
564
], function ( i, o ) {
565
	DataTable.ext.selector[ o.type ].push( function ( settings, opts, indexes ) {
566
		var selected = opts.selected;
567
		var data;
568
		var out = [];
569
 
570
		if ( selected === undefined ) {
571
			return indexes;
572
		}
573
 
574
		for ( var i=0, ien=indexes.length ; i<ien ; i++ ) {
575
			data = settings[ o.prop ][ indexes[i] ];
576
 
577
			if ( (selected === true && data._select_selected === true) ||
578
				 (selected === false && ! data._select_selected )
579
			) {
580
				out.push( indexes[i] );
581
			}
582
		}
583
 
584
		return out;
585
	} );
586
} );
587
 
588
DataTable.ext.selector.cell.push( function ( settings, opts, cells ) {
589
	var selected = opts.selected;
590
	var rowData;
591
	var out = [];
592
 
593
	if ( selected === undefined ) {
594
		return cells;
595
	}
596
 
597
	for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
598
		rowData = settings.aoData[ cells[i].row ];
599
 
600
		if ( (selected === true && rowData._selected_cells && rowData._selected_cells[ cells[i].column ] === true) ||
601
			 (selected === false && ( ! rowData._selected_cells || ! rowData._selected_cells[ cells[i].column ] ) )
602
		) {
603
			out.push( cells[i] );
604
		}
605
	}
606
 
607
	return out;
608
} );
609
 
610
 
611
 
612
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
613
 * DataTables API
614
 *
615
 * For complete documentation, please refer to the docs/api directory or the
616
 * DataTables site
617
 */
618
 
619
// Local variables to improve compression
620
var apiRegister = DataTable.Api.register;
621
var apiRegisterPlural = DataTable.Api.registerPlural;
622
 
623
apiRegister( 'select()', function () {} );
624
 
625
apiRegister( 'select.blurable()', function ( flag ) {
626
	if ( flag === undefined ) {
627
		return this.context[0]._select.blurable;
628
	}
629
 
630
	return this.iterator( 'table', function ( ctx ) {
631
		ctx._select.blurable = flag;
632
	} );
633
} );
634
 
635
apiRegister( 'select.info()', function ( flag ) {
636
	if ( info === undefined ) {
637
		return this.context[0]._select.info;
638
	}
639
 
640
	return this.iterator( 'table', function ( ctx ) {
641
		ctx._select.info = flag;
642
	} );
643
} );
644
 
645
apiRegister( 'select.items()', function ( items ) {
646
	if ( items === undefined ) {
647
		return this.context[0]._select.items;
648
	}
649
 
650
	return this.iterator( 'table', function ( ctx ) {
651
		ctx._select.items = items;
652
 
653
		eventTrigger( new DataTable.Api( ctx ), 'selectItems', [ items ] );
654
	} );
655
} );
656
 
657
// Takes effect from the _next_ selection. None disables future selection, but
658
// does not clear the current selection. Use the `deselect` methods for that
659
apiRegister( 'select.style()', function ( style ) {
660
	if ( style === undefined ) {
661
		return this.context[0]._select.style;
662
	}
663
 
664
	return this.iterator( 'table', function ( ctx ) {
665
		ctx._select.style = style;
666
 
667
		if ( ! ctx._select_init ) {
668
			init( ctx );
669
		}
670
 
671
		// Add / remove mouse event handlers. They aren't required when only
672
		// API selection is available
673
		var dt = new DataTable.Api( ctx );
674
		disableMouseSelection( dt );
675
 
676
		if ( style !== 'api' ) {
677
			enableMouseSelection( dt );
678
		}
679
 
680
		eventTrigger( new DataTable.Api( ctx ), 'selectStyle', [ style ] );
681
	} );
682
} );
683
 
684
apiRegister( 'select.selector()', function ( selector ) {
685
	if ( selector === undefined ) {
686
		return this.context[0]._select.selector;
687
	}
688
 
689
	return this.iterator( 'table', function ( ctx ) {
690
		disableMouseSelection( new DataTable.Api( ctx ) );
691
 
692
		ctx._select.selector = selector;
693
 
694
		if ( ctx._select.style !== 'api' ) {
695
			enableMouseSelection( new DataTable.Api( ctx ) );
696
		}
697
	} );
698
} );
699
 
700
 
701
 
702
apiRegisterPlural( 'rows().select()', 'row().select()', function ( select ) {
703
	var api = this;
704
 
705
	if ( select === false ) {
706
		return this.deselect();
707
	}
708
 
709
	this.iterator( 'row', function ( ctx, idx ) {
710
		clear( ctx );
711
 
712
		ctx.aoData[ idx ]._select_selected = true;
713
		$( ctx.aoData[ idx ].nTr ).addClass( 'selected' );
714
	} );
715
 
716
	this.iterator( 'table', function ( ctx, i ) {
717
		eventTrigger( api, 'select', [ 'row', api[i] ], true );
718
	} );
719
 
720
	return this;
721
} );
722
 
723
apiRegisterPlural( 'columns().select()', 'column().select()', function ( select ) {
724
	var api = this;
725
 
726
	if ( select === false ) {
727
		return this.deselect();
728
	}
729
 
730
	this.iterator( 'column', function ( ctx, idx ) {
731
		clear( ctx );
732
 
733
		ctx.aoColumns[ idx ]._select_selected = true;
734
 
735
		var column = new DataTable.Api( ctx ).column( idx );
736
 
737
		$( column.header() ).addClass( 'selected' );
738
		$( column.footer() ).addClass( 'selected' );
739
 
740
		column.nodes().to$().addClass( 'selected' );
741
	} );
742
 
743
	this.iterator( 'table', function ( ctx, i ) {
744
		eventTrigger( api, 'select', [ 'column', api[i] ], true );
745
	} );
746
 
747
	return this;
748
} );
749
 
750
apiRegisterPlural( 'cells().select()', 'cell().select()', function ( select ) {
751
	var api = this;
752
 
753
	if ( select === false ) {
754
		return this.deselect();
755
	}
756
 
757
	this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
758
		clear( ctx );
759
 
760
		var data = ctx.aoData[ rowIdx ];
761
 
762
		if ( data._selected_cells === undefined ) {
763
			data._selected_cells = [];
764
		}
765
 
766
		data._selected_cells[ colIdx ] = true;
767
 
768
		if ( data.anCells ) {
769
			$( data.anCells[ colIdx ] ).addClass( 'selected' );
770
		}
771
	} );
772
 
773
	this.iterator( 'table', function ( ctx, i ) {
774
		eventTrigger( api, 'select', [ 'cell', api[i] ], true );
775
	} );
776
 
777
	return this;
778
} );
779
 
780
 
781
apiRegisterPlural( 'rows().deselect()', 'row().deselect()', function () {
782
	var api = this;
783
 
784
	this.iterator( 'row', function ( ctx, idx ) {
785
		ctx.aoData[ idx ]._select_selected = false;
786
		$( ctx.aoData[ idx ].nTr ).removeClass( 'selected' );
787
	} );
788
 
789
	this.iterator( 'table', function ( ctx, i ) {
790
		eventTrigger( api, 'deselect', [ 'row', api[i] ], true );
791
	} );
792
 
793
	return this;
794
} );
795
 
796
apiRegisterPlural( 'columns().deselect()', 'column().deselect()', function () {
797
	var api = this;
798
 
799
	this.iterator( 'column', function ( ctx, idx ) {
800
		ctx.aoColumns[ idx ]._select_selected = false;
801
 
802
		var api = new DataTable.Api( ctx );
803
		var column = api.column( idx );
804
 
805
		$( column.header() ).removeClass( 'selected' );
806
		$( column.footer() ).removeClass( 'selected' );
807
 
808
		// Need to loop over each cell, rather than just using
809
		// `column().nodes()` as cells which are individually selected should
810
		// not have the `selected` class removed from them
811
		api.cells( null, idx ).indexes().each( function (cellIdx) {
812
			var data = ctx.aoData[ cellIdx.row ];
813
			var cellSelected = data._selected_cells;
814
 
815
			if ( data.anCells && (! cellSelected || ! cellSelected[ cellIdx.column ]) ) {
816
				$( data.anCells[ cellIdx.column  ] ).removeClass( 'selected' );
817
			}
818
		} );
819
	} );
820
 
821
	this.iterator( 'table', function ( ctx, i ) {
822
		eventTrigger( api, 'deselect', [ 'column', api[i] ], true );
823
	} );
824
 
825
	return this;
826
} );
827
 
828
apiRegisterPlural( 'cells().deselect()', 'cell().deselect()', function () {
829
	var api = this;
830
 
831
	this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
832
		var data = ctx.aoData[ rowIdx ];
833
 
834
		data._selected_cells[ colIdx ] = false;
835
 
836
		// Remove class only if the cells exist, and the cell is not column
837
		// selected, in which case the class should remain (since it is selected
838
		// in the column)
839
		if ( data.anCells && ! ctx.aoColumns[ colIdx ]._select_selected ) {
840
			$( data.anCells[ colIdx ] ).removeClass( 'selected' );
841
		}
842
	} );
843
 
844
	this.iterator( 'table', function ( ctx, i ) {
845
		eventTrigger( api, 'deselect', [ 'cell', api[i] ], true );
846
	} );
847
 
848
	return this;
849
} );
850
 
851
 
852
 
853
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
854
 * Buttons
855
 */
856
function i18n( label, def ) {
857
	return function (dt) {
858
		return dt.i18n( 'buttons.'+label, def );
859
	};
860
}
861
 
862
$.extend( DataTable.ext.buttons, {
863
	selected: {
864
		text: i18n( 'selected', 'Selected' ),
865
		className: 'buttons-selected',
866
		init: function ( dt, button, config ) {
867
			var that = this;
868
 
869
			// .DT namespace listeners are removed by DataTables automatically
870
			// on table destroy
871
			dt.on( 'draw.dt.DT select.dt.DT deselect.dt.DT', function () {
872
				var enable = that.rows( { selected: true } ).any() ||
873
				             that.columns( { selected: true } ).any() ||
874
				             that.cells( { selected: true } ).any();
875
 
876
				that.enable( enable );
877
			} );
878
 
879
			this.disable();
880
		}
881
	},
882
	selectedSingle: {
883
		text: i18n( 'selectedSingle', 'Selected single' ),
884
		className: 'buttons-selected-single',
885
		init: function ( dt, button, config ) {
886
			var that = this;
887
 
888
			dt.on( 'draw.dt.DT select.dt.DT deselect.dt.DT', function () {
889
				var count = dt.rows( { selected: true } ).flatten().length +
890
				            dt.columns( { selected: true } ).flatten().length +
891
				            dt.cells( { selected: true } ).flatten().length;
892
 
893
				that.enable( count === 1 );
894
			} );
895
 
896
			this.disable();
897
		}
898
	},
899
	selectAll: {
900
		text: i18n( 'selectAll', 'Select all' ),
901
		className: 'buttons-select-all',
902
		action: function () {
903
			var items = this.select.items();
904
			this[ items+'s' ]().select();
905
		}
906
	},
907
	selectNone: {
908
		text: i18n( 'selectNone', 'Deselect all' ),
909
		className: 'buttons-select-none',
910
		action: function () {
911
			clear( this.settings()[0], true );
912
		}
913
	}
914
} );
915
 
916
$.each( [ 'Row', 'Column', 'Cell' ], function ( i, item ) {
917
	var lc = item.toLowerCase();
918
 
919
	DataTable.ext.buttons[ 'select'+item+'s' ] = {
920
		text: i18n( 'select'+item+'s', 'Select '+lc+'s' ),
921
		className: 'buttons-select-'+lc+'s',
922
		action: function () {
923
			this.select.items( lc );
924
		},
925
		init: function ( dt, button, config ) {
926
			var that = this;
927
 
928
			dt.on( 'selectItems.dt.DT', function ( e, ctx, items ) {
929
				that.active( items === lc );
930
			} );
931
		}
932
	};
933
} );
934
 
935
 
936
 
937
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
938
 * Initialisation
939
 */
940
 
941
// DataTables creation - check if the buttons have been defined for this table,
942
// they will have been if the `B` option was used in `dom`, otherwise we should
943
// create the buttons instance here so they can be inserted into the document
944
// using the API
945
$(document).on( 'init.dt.dtSelect', function (e, ctx, json) {
946
	if ( e.namespace !== 'dt' ) {
947
		return;
948
	}
949
 
950
	var opts = ctx.oInit.select || DataTable.defaults.select;
951
	var dt = new DataTable.Api( ctx );
952
 
953
	// Set defaults
954
	var items = 'row';
955
	var style = 'api';
956
	var blurable = false;
957
	var info = true;
958
	var selector = 'td, th';
959
 
960
	ctx._select = {};
961
 
962
	// Initialisation customisations
963
	if ( opts === true ) {
964
		style = 'os';
965
	}
966
	else if ( typeof opts === 'string' ) {
967
		style = opts;
968
	}
969
	else if ( $.isPlainObject( opts ) ) {
970
		if ( opts.blurable !== undefined ) {
971
			blurable = opts.blurable;
972
		}
973
 
974
		if ( opts.info !== undefined ) {
975
			info = opts.info;
976
		}
977
 
978
		if ( opts.items !== undefined ) {
979
			items = opts.items;
980
		}
981
 
982
		if ( opts.style !== undefined ) {
983
			style = opts.style;
984
		}
985
 
986
		if ( opts.selector !== undefined ) {
987
			selector = opts.selector;
988
		}
989
	}
990
 
991
	dt.select.selector( selector );
992
	dt.select.items( items );
993
	dt.select.style( style );
994
	dt.select.blurable( blurable );
995
	dt.select.info( info );
996
 
997
	// If the init options haven't enabled select, but there is a selectable
998
	// class name, then enable
999
	if ( $( dt.table().node() ).hasClass( 'selectable' ) ) {
1000
		dt.select.style( 'os' );
1001
	}
1002
} );
1003
 
1004
 
1005
}; // /factory
1006
 
1007
 
1008
// Define as an AMD module if possible
1009
if ( typeof define === 'function' && define.amd ) {
1010
	define( ['jquery', 'datatables'], factory );
1011
}
1012
else if ( typeof exports === 'object' ) {
1013
    // Node/CommonJS
1014
    factory( require('jquery'), require('datatables') );
1015
}
1016
else if ( jQuery && !jQuery.fn.dataTable.select ) {
1017
	// Otherwise simply initialise as normal, stopping multiple evaluation
1018
	factory( jQuery, jQuery.fn.dataTable );
1019
}
1020
 
1021
 
1022
})(window, document);
1023