datatables.net-select 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1323 @@
1
+ /*! Select for DataTables 1.6.0
2
+ * 2015-2023 SpryMedia Ltd - datatables.net/license/mit
3
+ */
4
+
5
+ import $ from 'jquery';
6
+ import DataTable from 'datatables.net';
7
+
8
+
9
+
10
+ // Version information for debugger
11
+ DataTable.select = {};
12
+
13
+ DataTable.select.version = '1.6.0';
14
+
15
+ DataTable.select.init = function ( dt ) {
16
+ var ctx = dt.settings()[0];
17
+
18
+ if (ctx._select) {
19
+ return;
20
+ }
21
+
22
+ var savedSelected = dt.state.loaded();
23
+
24
+ var selectAndSave = function(e, settings, data) {
25
+ if(data === null || data.select === undefined) {
26
+ return;
27
+ }
28
+
29
+ // Clear any currently selected rows, before restoring state
30
+ // None will be selected on first initialisation
31
+ if (dt.rows({selected: true}).any()) {
32
+ dt.rows().deselect();
33
+ }
34
+ if (data.select.rows !== undefined) {
35
+ dt.rows(data.select.rows).select();
36
+ }
37
+
38
+ if (dt.columns({selected: true}).any()) {
39
+ dt.columns().deselect();
40
+ }
41
+ if (data.select.columns !== undefined) {
42
+ dt.columns(data.select.columns).select();
43
+ }
44
+
45
+ if (dt.cells({selected: true}).any()) {
46
+ dt.cells().deselect();
47
+ }
48
+ if (data.select.cells !== undefined) {
49
+ for(var i = 0; i < data.select.cells.length; i++) {
50
+ dt.cell(data.select.cells[i].row, data.select.cells[i].column).select();
51
+ }
52
+ }
53
+
54
+ dt.state.save();
55
+ }
56
+
57
+ dt
58
+ .on('stateSaveParams', function(e, settings, data) {
59
+ data.select = {};
60
+ data.select.rows = dt.rows({selected:true}).ids(true).toArray();
61
+ data.select.columns = dt.columns({selected:true})[0];
62
+ data.select.cells = dt.cells({selected:true})[0].map(function(coords) {
63
+ return {row: dt.row(coords.row).id(true), column: coords.column}
64
+ });
65
+ })
66
+ .on('stateLoadParams', selectAndSave)
67
+ .one('init', function() {
68
+ selectAndSave(undefined, undefined, savedSelected);
69
+ });
70
+
71
+ var init = ctx.oInit.select;
72
+ var defaults = DataTable.defaults.select;
73
+ var opts = init === undefined ?
74
+ defaults :
75
+ init;
76
+
77
+ // Set defaults
78
+ var items = 'row';
79
+ var style = 'api';
80
+ var blurable = false;
81
+ var toggleable = true;
82
+ var info = true;
83
+ var selector = 'td, th';
84
+ var className = 'selected';
85
+ var setStyle = false;
86
+
87
+ ctx._select = {};
88
+
89
+ // Initialisation customisations
90
+ if ( opts === true ) {
91
+ style = 'os';
92
+ setStyle = true;
93
+ }
94
+ else if ( typeof opts === 'string' ) {
95
+ style = opts;
96
+ setStyle = true;
97
+ }
98
+ else if ( $.isPlainObject( opts ) ) {
99
+ if ( opts.blurable !== undefined ) {
100
+ blurable = opts.blurable;
101
+ }
102
+
103
+ if ( opts.toggleable !== undefined ) {
104
+ toggleable = opts.toggleable;
105
+ }
106
+
107
+ if ( opts.info !== undefined ) {
108
+ info = opts.info;
109
+ }
110
+
111
+ if ( opts.items !== undefined ) {
112
+ items = opts.items;
113
+ }
114
+
115
+ if ( opts.style !== undefined ) {
116
+ style = opts.style;
117
+ setStyle = true;
118
+ }
119
+ else {
120
+ style = 'os';
121
+ setStyle = true;
122
+ }
123
+
124
+ if ( opts.selector !== undefined ) {
125
+ selector = opts.selector;
126
+ }
127
+
128
+ if ( opts.className !== undefined ) {
129
+ className = opts.className;
130
+ }
131
+ }
132
+
133
+ dt.select.selector( selector );
134
+ dt.select.items( items );
135
+ dt.select.style( style );
136
+ dt.select.blurable( blurable );
137
+ dt.select.toggleable( toggleable );
138
+ dt.select.info( info );
139
+ ctx._select.className = className;
140
+
141
+
142
+ // Sort table based on selected rows. Requires Select Datatables extension
143
+ $.fn.dataTable.ext.order['select-checkbox'] = function ( settings, col ) {
144
+ return this.api().column( col, {order: 'index'} ).nodes().map( function ( td ) {
145
+ if ( settings._select.items === 'row' ) {
146
+ return $( td ).parent().hasClass( settings._select.className );
147
+ } else if ( settings._select.items === 'cell' ) {
148
+ return $( td ).hasClass( settings._select.className );
149
+ }
150
+ return false;
151
+ });
152
+ };
153
+
154
+ // If the init options haven't enabled select, but there is a selectable
155
+ // class name, then enable
156
+ if ( ! setStyle && $( dt.table().node() ).hasClass( 'selectable' ) ) {
157
+ dt.select.style( 'os' );
158
+ }
159
+ };
160
+
161
+ /*
162
+
163
+ Select is a collection of API methods, event handlers, event emitters and
164
+ buttons (for the `Buttons` extension) for DataTables. It provides the following
165
+ features, with an overview of how they are implemented:
166
+
167
+ ## Selection of rows, columns and cells. Whether an item is selected or not is
168
+ stored in:
169
+
170
+ * rows: a `_select_selected` property which contains a boolean value of the
171
+ DataTables' `aoData` object for each row
172
+ * columns: a `_select_selected` property which contains a boolean value of the
173
+ DataTables' `aoColumns` object for each column
174
+ * cells: a `_selected_cells` property which contains an array of boolean values
175
+ of the `aoData` object for each row. The array is the same length as the
176
+ columns array, with each element of it representing a cell.
177
+
178
+ This method of using boolean flags allows Select to operate when nodes have not
179
+ been created for rows / cells (DataTables' defer rendering feature).
180
+
181
+ ## API methods
182
+
183
+ A range of API methods are available for triggering selection and de-selection
184
+ of rows. Methods are also available to configure the selection events that can
185
+ be triggered by an end user (such as which items are to be selected). To a large
186
+ extent, these of API methods *is* Select. It is basically a collection of helper
187
+ functions that can be used to select items in a DataTable.
188
+
189
+ Configuration of select is held in the object `_select` which is attached to the
190
+ DataTables settings object on initialisation. Select being available on a table
191
+ is not optional when Select is loaded, but its default is for selection only to
192
+ be available via the API - so the end user wouldn't be able to select rows
193
+ without additional configuration.
194
+
195
+ The `_select` object contains the following properties:
196
+
197
+ ```
198
+ {
199
+ items:string - Can be `rows`, `columns` or `cells`. Defines what item
200
+ will be selected if the user is allowed to activate row
201
+ selection using the mouse.
202
+ style:string - Can be `none`, `single`, `multi` or `os`. Defines the
203
+ interaction style when selecting items
204
+ blurable:boolean - If row selection can be cleared by clicking outside of
205
+ the table
206
+ toggleable:boolean - If row selection can be cancelled by repeated clicking
207
+ on the row
208
+ info:boolean - If the selection summary should be shown in the table
209
+ information elements
210
+ }
211
+ ```
212
+
213
+ In addition to the API methods, Select also extends the DataTables selector
214
+ options for rows, columns and cells adding a `selected` option to the selector
215
+ options object, allowing the developer to select only selected items or
216
+ unselected items.
217
+
218
+ ## Mouse selection of items
219
+
220
+ Clicking on items can be used to select items. This is done by a simple event
221
+ handler that will select the items using the API methods.
222
+
223
+ */
224
+
225
+
226
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
227
+ * Local functions
228
+ */
229
+
230
+ /**
231
+ * Add one or more cells to the selection when shift clicking in OS selection
232
+ * style cell selection.
233
+ *
234
+ * Cell range is more complicated than row and column as we want to select
235
+ * in the visible grid rather than by index in sequence. For example, if you
236
+ * click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1
237
+ * should also be selected (and not 1-3, 1-4. etc)
238
+ *
239
+ * @param {DataTable.Api} dt DataTable
240
+ * @param {object} idx Cell index to select to
241
+ * @param {object} last Cell index to select from
242
+ * @private
243
+ */
244
+ function cellRange( dt, idx, last )
245
+ {
246
+ var indexes;
247
+ var columnIndexes;
248
+ var rowIndexes;
249
+ var selectColumns = function ( start, end ) {
250
+ if ( start > end ) {
251
+ var tmp = end;
252
+ end = start;
253
+ start = tmp;
254
+ }
255
+
256
+ var record = false;
257
+ return dt.columns( ':visible' ).indexes().filter( function (i) {
258
+ if ( i === start ) {
259
+ record = true;
260
+ }
261
+
262
+ if ( i === end ) { // not else if, as start might === end
263
+ record = false;
264
+ return true;
265
+ }
266
+
267
+ return record;
268
+ } );
269
+ };
270
+
271
+ var selectRows = function ( start, end ) {
272
+ var indexes = dt.rows( { search: 'applied' } ).indexes();
273
+
274
+ // Which comes first - might need to swap
275
+ if ( indexes.indexOf( start ) > indexes.indexOf( end ) ) {
276
+ var tmp = end;
277
+ end = start;
278
+ start = tmp;
279
+ }
280
+
281
+ var record = false;
282
+ return indexes.filter( function (i) {
283
+ if ( i === start ) {
284
+ record = true;
285
+ }
286
+
287
+ if ( i === end ) {
288
+ record = false;
289
+ return true;
290
+ }
291
+
292
+ return record;
293
+ } );
294
+ };
295
+
296
+ if ( ! dt.cells( { selected: true } ).any() && ! last ) {
297
+ // select from the top left cell to this one
298
+ columnIndexes = selectColumns( 0, idx.column );
299
+ rowIndexes = selectRows( 0 , idx.row );
300
+ }
301
+ else {
302
+ // Get column indexes between old and new
303
+ columnIndexes = selectColumns( last.column, idx.column );
304
+ rowIndexes = selectRows( last.row , idx.row );
305
+ }
306
+
307
+ indexes = dt.cells( rowIndexes, columnIndexes ).flatten();
308
+
309
+ if ( ! dt.cells( idx, { selected: true } ).any() ) {
310
+ // Select range
311
+ dt.cells( indexes ).select();
312
+ }
313
+ else {
314
+ // Deselect range
315
+ dt.cells( indexes ).deselect();
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Disable mouse selection by removing the selectors
321
+ *
322
+ * @param {DataTable.Api} dt DataTable to remove events from
323
+ * @private
324
+ */
325
+ function disableMouseSelection( dt )
326
+ {
327
+ var ctx = dt.settings()[0];
328
+ var selector = ctx._select.selector;
329
+
330
+ $( dt.table().container() )
331
+ .off( 'mousedown.dtSelect', selector )
332
+ .off( 'mouseup.dtSelect', selector )
333
+ .off( 'click.dtSelect', selector );
334
+
335
+ $('body').off( 'click.dtSelect' + _safeId(dt.table().node()) );
336
+ }
337
+
338
+ /**
339
+ * Attach mouse listeners to the table to allow mouse selection of items
340
+ *
341
+ * @param {DataTable.Api} dt DataTable to remove events from
342
+ * @private
343
+ */
344
+ function enableMouseSelection ( dt )
345
+ {
346
+ var container = $( dt.table().container() );
347
+ var ctx = dt.settings()[0];
348
+ var selector = ctx._select.selector;
349
+ var matchSelection;
350
+
351
+ container
352
+ .on( 'mousedown.dtSelect', selector, function(e) {
353
+ // Disallow text selection for shift clicking on the table so multi
354
+ // element selection doesn't look terrible!
355
+ if ( e.shiftKey || e.metaKey || e.ctrlKey ) {
356
+ container
357
+ .css( '-moz-user-select', 'none' )
358
+ .one('selectstart.dtSelect', selector, function () {
359
+ return false;
360
+ } );
361
+ }
362
+
363
+ if ( window.getSelection ) {
364
+ matchSelection = window.getSelection();
365
+ }
366
+ } )
367
+ .on( 'mouseup.dtSelect', selector, function() {
368
+ // Allow text selection to occur again, Mozilla style (tested in FF
369
+ // 35.0.1 - still required)
370
+ container.css( '-moz-user-select', '' );
371
+ } )
372
+ .on( 'click.dtSelect', selector, function ( e ) {
373
+ var items = dt.select.items();
374
+ var idx;
375
+
376
+ // If text was selected (click and drag), then we shouldn't change
377
+ // the row's selected state
378
+ if ( matchSelection ) {
379
+ var selection = window.getSelection();
380
+
381
+ // If the element that contains the selection is not in the table, we can ignore it
382
+ // This can happen if the developer selects text from the click event
383
+ if ( ! selection.anchorNode || $(selection.anchorNode).closest('table')[0] === dt.table().node() ) {
384
+ if ( selection !== matchSelection ) {
385
+ return;
386
+ }
387
+ }
388
+ }
389
+
390
+ var ctx = dt.settings()[0];
391
+ var wrapperClass = dt.settings()[0].oClasses.sWrapper.trim().replace(/ +/g, '.');
392
+
393
+ // Ignore clicks inside a sub-table
394
+ if ( $(e.target).closest('div.'+wrapperClass)[0] != dt.table().container() ) {
395
+ return;
396
+ }
397
+
398
+ var cell = dt.cell( $(e.target).closest('td, th') );
399
+
400
+ // Check the cell actually belongs to the host DataTable (so child
401
+ // rows, etc, are ignored)
402
+ if ( ! cell.any() ) {
403
+ return;
404
+ }
405
+
406
+ var event = $.Event('user-select.dt');
407
+ eventTrigger( dt, event, [ items, cell, e ] );
408
+
409
+ if ( event.isDefaultPrevented() ) {
410
+ return;
411
+ }
412
+
413
+ var cellIndex = cell.index();
414
+ if ( items === 'row' ) {
415
+ idx = cellIndex.row;
416
+ typeSelect( e, dt, ctx, 'row', idx );
417
+ }
418
+ else if ( items === 'column' ) {
419
+ idx = cell.index().column;
420
+ typeSelect( e, dt, ctx, 'column', idx );
421
+ }
422
+ else if ( items === 'cell' ) {
423
+ idx = cell.index();
424
+ typeSelect( e, dt, ctx, 'cell', idx );
425
+ }
426
+
427
+ ctx._select_lastCell = cellIndex;
428
+ } );
429
+
430
+ // Blurable
431
+ $('body').on( 'click.dtSelect' + _safeId(dt.table().node()), function ( e ) {
432
+ if ( ctx._select.blurable ) {
433
+ // If the click was inside the DataTables container, don't blur
434
+ if ( $(e.target).parents().filter( dt.table().container() ).length ) {
435
+ return;
436
+ }
437
+
438
+ // Ignore elements which have been removed from the DOM (i.e. paging
439
+ // buttons)
440
+ if ( $(e.target).parents('html').length === 0 ) {
441
+ return;
442
+ }
443
+
444
+ // Don't blur in Editor form
445
+ if ( $(e.target).parents('div.DTE').length ) {
446
+ return;
447
+ }
448
+
449
+ var event = $.Event('select-blur.dt');
450
+ eventTrigger( dt, event, [ e.target, e ] );
451
+
452
+ if ( event.isDefaultPrevented() ) {
453
+ return;
454
+ }
455
+
456
+ clear( ctx, true );
457
+ }
458
+ } );
459
+ }
460
+
461
+ /**
462
+ * Trigger an event on a DataTable
463
+ *
464
+ * @param {DataTable.Api} api DataTable to trigger events on
465
+ * @param {boolean} selected true if selected, false if deselected
466
+ * @param {string} type Item type acting on
467
+ * @param {boolean} any Require that there are values before
468
+ * triggering
469
+ * @private
470
+ */
471
+ function eventTrigger ( api, type, args, any )
472
+ {
473
+ if ( any && ! api.flatten().length ) {
474
+ return;
475
+ }
476
+
477
+ if ( typeof type === 'string' ) {
478
+ type = type +'.dt';
479
+ }
480
+
481
+ args.unshift( api );
482
+
483
+ $(api.table().node()).trigger( type, args );
484
+ }
485
+
486
+ /**
487
+ * Update the information element of the DataTable showing information about the
488
+ * items selected. This is done by adding tags to the existing text
489
+ *
490
+ * @param {DataTable.Api} api DataTable to update
491
+ * @private
492
+ */
493
+ function info ( api )
494
+ {
495
+ var ctx = api.settings()[0];
496
+
497
+ if ( ! ctx._select.info || ! ctx.aanFeatures.i ) {
498
+ return;
499
+ }
500
+
501
+ if ( api.select.style() === 'api' ) {
502
+ return;
503
+ }
504
+
505
+ var rows = api.rows( { selected: true } ).flatten().length;
506
+ var columns = api.columns( { selected: true } ).flatten().length;
507
+ var cells = api.cells( { selected: true } ).flatten().length;
508
+
509
+ var add = function ( el, name, num ) {
510
+ el.append( $('<span class="select-item"/>').append( api.i18n(
511
+ 'select.'+name+'s',
512
+ { _: '%d '+name+'s selected', 0: '', 1: '1 '+name+' selected' },
513
+ num
514
+ ) ) );
515
+ };
516
+
517
+ // Internal knowledge of DataTables to loop over all information elements
518
+ $.each( ctx.aanFeatures.i, function ( i, el ) {
519
+ el = $(el);
520
+
521
+ var output = $('<span class="select-info"/>');
522
+ add( output, 'row', rows );
523
+ add( output, 'column', columns );
524
+ add( output, 'cell', cells );
525
+
526
+ var exisiting = el.children('span.select-info');
527
+ if ( exisiting.length ) {
528
+ exisiting.remove();
529
+ }
530
+
531
+ if ( output.text() !== '' ) {
532
+ el.append( output );
533
+ }
534
+ } );
535
+ }
536
+
537
+ /**
538
+ * Initialisation of a new table. Attach event handlers and callbacks to allow
539
+ * Select to operate correctly.
540
+ *
541
+ * This will occur _after_ the initial DataTables initialisation, although
542
+ * before Ajax data is rendered, if there is ajax data
543
+ *
544
+ * @param {DataTable.settings} ctx Settings object to operate on
545
+ * @private
546
+ */
547
+ function init ( ctx ) {
548
+ var api = new DataTable.Api( ctx );
549
+ ctx._select_init = true;
550
+
551
+ // Row callback so that classes can be added to rows and cells if the item
552
+ // was selected before the element was created. This will happen with the
553
+ // `deferRender` option enabled.
554
+ //
555
+ // This method of attaching to `aoRowCreatedCallback` is a hack until
556
+ // DataTables has proper events for row manipulation If you are reviewing
557
+ // this code to create your own plug-ins, please do not do this!
558
+ ctx.aoRowCreatedCallback.push( {
559
+ fn: function ( row, data, index ) {
560
+ var i, ien;
561
+ var d = ctx.aoData[ index ];
562
+
563
+ // Row
564
+ if ( d._select_selected ) {
565
+ $( row ).addClass( ctx._select.className );
566
+ }
567
+
568
+ // Cells and columns - if separated out, we would need to do two
569
+ // loops, so it makes sense to combine them into a single one
570
+ for ( i=0, ien=ctx.aoColumns.length ; i<ien ; i++ ) {
571
+ if ( ctx.aoColumns[i]._select_selected || (d._selected_cells && d._selected_cells[i]) ) {
572
+ $(d.anCells[i]).addClass( ctx._select.className );
573
+ }
574
+ }
575
+ },
576
+ sName: 'select-deferRender'
577
+ } );
578
+
579
+ // On Ajax reload we want to reselect all rows which are currently selected,
580
+ // if there is an rowId (i.e. a unique value to identify each row with)
581
+ api.on( 'preXhr.dt.dtSelect', function (e, settings) {
582
+ if (settings !== api.settings()[0]) {
583
+ // Not triggered by our DataTable!
584
+ return;
585
+ }
586
+
587
+ // note that column selection doesn't need to be cached and then
588
+ // reselected, as they are already selected
589
+ var rows = api.rows( { selected: true } ).ids( true ).filter( function ( d ) {
590
+ return d !== undefined;
591
+ } );
592
+
593
+ var cells = api.cells( { selected: true } ).eq(0).map( function ( cellIdx ) {
594
+ var id = api.row( cellIdx.row ).id( true );
595
+ return id ?
596
+ { row: id, column: cellIdx.column } :
597
+ undefined;
598
+ } ).filter( function ( d ) {
599
+ return d !== undefined;
600
+ } );
601
+
602
+ // On the next draw, reselect the currently selected items
603
+ api.one( 'draw.dt.dtSelect', function () {
604
+ api.rows( rows ).select();
605
+
606
+ // `cells` is not a cell index selector, so it needs a loop
607
+ if ( cells.any() ) {
608
+ cells.each( function ( id ) {
609
+ api.cells( id.row, id.column ).select();
610
+ } );
611
+ }
612
+ } );
613
+ } );
614
+
615
+ // Update the table information element with selected item summary
616
+ api.on( 'draw.dtSelect.dt select.dtSelect.dt deselect.dtSelect.dt info.dt', function () {
617
+ info( api );
618
+ api.state.save();
619
+ } );
620
+
621
+ // Clean up and release
622
+ api.on( 'destroy.dtSelect', function () {
623
+ // Remove class directly rather than calling deselect - which would trigger events
624
+ $(api.rows({selected: true}).nodes()).removeClass(api.settings()[0]._select.className);
625
+
626
+ disableMouseSelection( api );
627
+ api.off( '.dtSelect' );
628
+ $('body').off('.dtSelect' + _safeId(api.table().node()));
629
+ } );
630
+ }
631
+
632
+ /**
633
+ * Add one or more items (rows or columns) to the selection when shift clicking
634
+ * in OS selection style
635
+ *
636
+ * @param {DataTable.Api} dt DataTable
637
+ * @param {string} type Row or column range selector
638
+ * @param {object} idx Item index to select to
639
+ * @param {object} last Item index to select from
640
+ * @private
641
+ */
642
+ function rowColumnRange( dt, type, idx, last )
643
+ {
644
+ // Add a range of rows from the last selected row to this one
645
+ var indexes = dt[type+'s']( { search: 'applied' } ).indexes();
646
+ var idx1 = $.inArray( last, indexes );
647
+ var idx2 = $.inArray( idx, indexes );
648
+
649
+ if ( ! dt[type+'s']( { selected: true } ).any() && idx1 === -1 ) {
650
+ // select from top to here - slightly odd, but both Windows and Mac OS
651
+ // do this
652
+ indexes.splice( $.inArray( idx, indexes )+1, indexes.length );
653
+ }
654
+ else {
655
+ // reverse so we can shift click 'up' as well as down
656
+ if ( idx1 > idx2 ) {
657
+ var tmp = idx2;
658
+ idx2 = idx1;
659
+ idx1 = tmp;
660
+ }
661
+
662
+ indexes.splice( idx2+1, indexes.length );
663
+ indexes.splice( 0, idx1 );
664
+ }
665
+
666
+ if ( ! dt[type]( idx, { selected: true } ).any() ) {
667
+ // Select range
668
+ dt[type+'s']( indexes ).select();
669
+ }
670
+ else {
671
+ // Deselect range - need to keep the clicked on row selected
672
+ indexes.splice( $.inArray( idx, indexes ), 1 );
673
+ dt[type+'s']( indexes ).deselect();
674
+ }
675
+ }
676
+
677
+ /**
678
+ * Clear all selected items
679
+ *
680
+ * @param {DataTable.settings} ctx Settings object of the host DataTable
681
+ * @param {boolean} [force=false] Force the de-selection to happen, regardless
682
+ * of selection style
683
+ * @private
684
+ */
685
+ function clear( ctx, force )
686
+ {
687
+ if ( force || ctx._select.style === 'single' ) {
688
+ var api = new DataTable.Api( ctx );
689
+
690
+ api.rows( { selected: true } ).deselect();
691
+ api.columns( { selected: true } ).deselect();
692
+ api.cells( { selected: true } ).deselect();
693
+ }
694
+ }
695
+
696
+ /**
697
+ * Select items based on the current configuration for style and items.
698
+ *
699
+ * @param {object} e Mouse event object
700
+ * @param {DataTables.Api} dt DataTable
701
+ * @param {DataTable.settings} ctx Settings object of the host DataTable
702
+ * @param {string} type Items to select
703
+ * @param {int|object} idx Index of the item to select
704
+ * @private
705
+ */
706
+ function typeSelect ( e, dt, ctx, type, idx )
707
+ {
708
+ var style = dt.select.style();
709
+ var toggleable = dt.select.toggleable();
710
+ var isSelected = dt[type]( idx, { selected: true } ).any();
711
+
712
+ if ( isSelected && ! toggleable ) {
713
+ return;
714
+ }
715
+
716
+ if ( style === 'os' ) {
717
+ if ( e.ctrlKey || e.metaKey ) {
718
+ // Add or remove from the selection
719
+ dt[type]( idx ).select( ! isSelected );
720
+ }
721
+ else if ( e.shiftKey ) {
722
+ if ( type === 'cell' ) {
723
+ cellRange( dt, idx, ctx._select_lastCell || null );
724
+ }
725
+ else {
726
+ rowColumnRange( dt, type, idx, ctx._select_lastCell ?
727
+ ctx._select_lastCell[type] :
728
+ null
729
+ );
730
+ }
731
+ }
732
+ else {
733
+ // No cmd or shift click - deselect if selected, or select
734
+ // this row only
735
+ var selected = dt[type+'s']( { selected: true } );
736
+
737
+ if ( isSelected && selected.flatten().length === 1 ) {
738
+ dt[type]( idx ).deselect();
739
+ }
740
+ else {
741
+ selected.deselect();
742
+ dt[type]( idx ).select();
743
+ }
744
+ }
745
+ } else if ( style == 'multi+shift' ) {
746
+ if ( e.shiftKey ) {
747
+ if ( type === 'cell' ) {
748
+ cellRange( dt, idx, ctx._select_lastCell || null );
749
+ }
750
+ else {
751
+ rowColumnRange( dt, type, idx, ctx._select_lastCell ?
752
+ ctx._select_lastCell[type] :
753
+ null
754
+ );
755
+ }
756
+ }
757
+ else {
758
+ dt[ type ]( idx ).select( ! isSelected );
759
+ }
760
+ }
761
+ else {
762
+ dt[ type ]( idx ).select( ! isSelected );
763
+ }
764
+ }
765
+
766
+ function _safeId( node ) {
767
+ return node.id.replace(/[^a-zA-Z0-9\-\_]/g, '-');
768
+ }
769
+
770
+
771
+
772
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
773
+ * DataTables selectors
774
+ */
775
+
776
+ // row and column are basically identical just assigned to different properties
777
+ // and checking a different array, so we can dynamically create the functions to
778
+ // reduce the code size
779
+ $.each( [
780
+ { type: 'row', prop: 'aoData' },
781
+ { type: 'column', prop: 'aoColumns' }
782
+ ], function ( i, o ) {
783
+ DataTable.ext.selector[ o.type ].push( function ( settings, opts, indexes ) {
784
+ var selected = opts.selected;
785
+ var data;
786
+ var out = [];
787
+
788
+ if ( selected !== true && selected !== false ) {
789
+ return indexes;
790
+ }
791
+
792
+ for ( var i=0, ien=indexes.length ; i<ien ; i++ ) {
793
+ data = settings[ o.prop ][ indexes[i] ];
794
+
795
+ if ( (selected === true && data._select_selected === true) ||
796
+ (selected === false && ! data._select_selected )
797
+ ) {
798
+ out.push( indexes[i] );
799
+ }
800
+ }
801
+
802
+ return out;
803
+ } );
804
+ } );
805
+
806
+ DataTable.ext.selector.cell.push( function ( settings, opts, cells ) {
807
+ var selected = opts.selected;
808
+ var rowData;
809
+ var out = [];
810
+
811
+ if ( selected === undefined ) {
812
+ return cells;
813
+ }
814
+
815
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
816
+ rowData = settings.aoData[ cells[i].row ];
817
+
818
+ if ( (selected === true && rowData._selected_cells && rowData._selected_cells[ cells[i].column ] === true) ||
819
+ (selected === false && ( ! rowData._selected_cells || ! rowData._selected_cells[ cells[i].column ] ) )
820
+ ) {
821
+ out.push( cells[i] );
822
+ }
823
+ }
824
+
825
+ return out;
826
+ } );
827
+
828
+
829
+
830
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
831
+ * DataTables API
832
+ *
833
+ * For complete documentation, please refer to the docs/api directory or the
834
+ * DataTables site
835
+ */
836
+
837
+ // Local variables to improve compression
838
+ var apiRegister = DataTable.Api.register;
839
+ var apiRegisterPlural = DataTable.Api.registerPlural;
840
+
841
+ apiRegister( 'select()', function () {
842
+ return this.iterator( 'table', function ( ctx ) {
843
+ DataTable.select.init( new DataTable.Api( ctx ) );
844
+ } );
845
+ } );
846
+
847
+ apiRegister( 'select.blurable()', function ( flag ) {
848
+ if ( flag === undefined ) {
849
+ return this.context[0]._select.blurable;
850
+ }
851
+
852
+ return this.iterator( 'table', function ( ctx ) {
853
+ ctx._select.blurable = flag;
854
+ } );
855
+ } );
856
+
857
+ apiRegister( 'select.toggleable()', function ( flag ) {
858
+ if ( flag === undefined ) {
859
+ return this.context[0]._select.toggleable;
860
+ }
861
+
862
+ return this.iterator( 'table', function ( ctx ) {
863
+ ctx._select.toggleable = flag;
864
+ } );
865
+ } );
866
+
867
+ apiRegister( 'select.info()', function ( flag ) {
868
+ if ( flag === undefined ) {
869
+ return this.context[0]._select.info;
870
+ }
871
+
872
+ return this.iterator( 'table', function ( ctx ) {
873
+ ctx._select.info = flag;
874
+ } );
875
+ } );
876
+
877
+ apiRegister( 'select.items()', function ( items ) {
878
+ if ( items === undefined ) {
879
+ return this.context[0]._select.items;
880
+ }
881
+
882
+ return this.iterator( 'table', function ( ctx ) {
883
+ ctx._select.items = items;
884
+
885
+ eventTrigger( new DataTable.Api( ctx ), 'selectItems', [ items ] );
886
+ } );
887
+ } );
888
+
889
+ // Takes effect from the _next_ selection. None disables future selection, but
890
+ // does not clear the current selection. Use the `deselect` methods for that
891
+ apiRegister( 'select.style()', function ( style ) {
892
+ if ( style === undefined ) {
893
+ return this.context[0]._select.style;
894
+ }
895
+
896
+ return this.iterator( 'table', function ( ctx ) {
897
+ if ( ! ctx._select ) {
898
+ DataTable.select.init( new DataTable.Api(ctx) );
899
+ }
900
+
901
+ if ( ! ctx._select_init ) {
902
+ init(ctx);
903
+ }
904
+
905
+ ctx._select.style = style;
906
+
907
+ // Add / remove mouse event handlers. They aren't required when only
908
+ // API selection is available
909
+ var dt = new DataTable.Api( ctx );
910
+ disableMouseSelection( dt );
911
+
912
+ if ( style !== 'api' ) {
913
+ enableMouseSelection( dt );
914
+ }
915
+
916
+ eventTrigger( new DataTable.Api( ctx ), 'selectStyle', [ style ] );
917
+ } );
918
+ } );
919
+
920
+ apiRegister( 'select.selector()', function ( selector ) {
921
+ if ( selector === undefined ) {
922
+ return this.context[0]._select.selector;
923
+ }
924
+
925
+ return this.iterator( 'table', function ( ctx ) {
926
+ disableMouseSelection( new DataTable.Api( ctx ) );
927
+
928
+ ctx._select.selector = selector;
929
+
930
+ if ( ctx._select.style !== 'api' ) {
931
+ enableMouseSelection( new DataTable.Api( ctx ) );
932
+ }
933
+ } );
934
+ } );
935
+
936
+
937
+
938
+ apiRegisterPlural( 'rows().select()', 'row().select()', function ( select ) {
939
+ var api = this;
940
+
941
+ if ( select === false ) {
942
+ return this.deselect();
943
+ }
944
+
945
+ this.iterator( 'row', function ( ctx, idx ) {
946
+ clear( ctx );
947
+
948
+ ctx.aoData[ idx ]._select_selected = true;
949
+ $( ctx.aoData[ idx ].nTr ).addClass( ctx._select.className );
950
+ } );
951
+
952
+ this.iterator( 'table', function ( ctx, i ) {
953
+ eventTrigger( api, 'select', [ 'row', api[i] ], true );
954
+ } );
955
+
956
+ return this;
957
+ } );
958
+
959
+ apiRegister( 'row().selected()', function () {
960
+ var ctx = this.context[0];
961
+
962
+ if (
963
+ ctx &&
964
+ this.length &&
965
+ ctx.aoData[this[0]] &&
966
+ ctx.aoData[this[0]]._select_selected
967
+ ) {
968
+ return true;
969
+ }
970
+
971
+ return false;
972
+ } );
973
+
974
+ apiRegisterPlural( 'columns().select()', 'column().select()', function ( select ) {
975
+ var api = this;
976
+
977
+ if ( select === false ) {
978
+ return this.deselect();
979
+ }
980
+
981
+ this.iterator( 'column', function ( ctx, idx ) {
982
+ clear( ctx );
983
+
984
+ ctx.aoColumns[ idx ]._select_selected = true;
985
+
986
+ var column = new DataTable.Api( ctx ).column( idx );
987
+
988
+ $( column.header() ).addClass( ctx._select.className );
989
+ $( column.footer() ).addClass( ctx._select.className );
990
+
991
+ column.nodes().to$().addClass( ctx._select.className );
992
+ } );
993
+
994
+ this.iterator( 'table', function ( ctx, i ) {
995
+ eventTrigger( api, 'select', [ 'column', api[i] ], true );
996
+ } );
997
+
998
+ return this;
999
+ } );
1000
+
1001
+ apiRegister( 'column().selected()', function () {
1002
+ var ctx = this.context[0];
1003
+
1004
+ if (
1005
+ ctx &&
1006
+ this.length &&
1007
+ ctx.aoColumns[this[0]] &&
1008
+ ctx.aoColumns[this[0]]._select_selected
1009
+ ) {
1010
+ return true;
1011
+ }
1012
+
1013
+ return false;
1014
+ } );
1015
+
1016
+ apiRegisterPlural( 'cells().select()', 'cell().select()', function ( select ) {
1017
+ var api = this;
1018
+
1019
+ if ( select === false ) {
1020
+ return this.deselect();
1021
+ }
1022
+
1023
+ this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
1024
+ clear( ctx );
1025
+
1026
+ var data = ctx.aoData[ rowIdx ];
1027
+
1028
+ if ( data._selected_cells === undefined ) {
1029
+ data._selected_cells = [];
1030
+ }
1031
+
1032
+ data._selected_cells[ colIdx ] = true;
1033
+
1034
+ if ( data.anCells ) {
1035
+ $( data.anCells[ colIdx ] ).addClass( ctx._select.className );
1036
+ }
1037
+ } );
1038
+
1039
+ this.iterator( 'table', function ( ctx, i ) {
1040
+ eventTrigger( api, 'select', [ 'cell', api.cells(api[i]).indexes().toArray() ], true );
1041
+ } );
1042
+
1043
+ return this;
1044
+ } );
1045
+
1046
+ apiRegister( 'cell().selected()', function () {
1047
+ var ctx = this.context[0];
1048
+
1049
+ if (ctx && this.length) {
1050
+ var row = ctx.aoData[this[0][0].row];
1051
+
1052
+ if (row && row._selected_cells && row._selected_cells[this[0][0].column]) {
1053
+ return true;
1054
+ }
1055
+ }
1056
+
1057
+ return false;
1058
+ } );
1059
+
1060
+
1061
+ apiRegisterPlural( 'rows().deselect()', 'row().deselect()', function () {
1062
+ var api = this;
1063
+
1064
+ this.iterator( 'row', function ( ctx, idx ) {
1065
+ ctx.aoData[ idx ]._select_selected = false;
1066
+ ctx._select_lastCell = null;
1067
+ $( ctx.aoData[ idx ].nTr ).removeClass( ctx._select.className );
1068
+ } );
1069
+
1070
+ this.iterator( 'table', function ( ctx, i ) {
1071
+ eventTrigger( api, 'deselect', [ 'row', api[i] ], true );
1072
+ } );
1073
+
1074
+ return this;
1075
+ } );
1076
+
1077
+ apiRegisterPlural( 'columns().deselect()', 'column().deselect()', function () {
1078
+ var api = this;
1079
+
1080
+ this.iterator( 'column', function ( ctx, idx ) {
1081
+ ctx.aoColumns[ idx ]._select_selected = false;
1082
+
1083
+ var api = new DataTable.Api( ctx );
1084
+ var column = api.column( idx );
1085
+
1086
+ $( column.header() ).removeClass( ctx._select.className );
1087
+ $( column.footer() ).removeClass( ctx._select.className );
1088
+
1089
+ // Need to loop over each cell, rather than just using
1090
+ // `column().nodes()` as cells which are individually selected should
1091
+ // not have the `selected` class removed from them
1092
+ api.cells( null, idx ).indexes().each( function (cellIdx) {
1093
+ var data = ctx.aoData[ cellIdx.row ];
1094
+ var cellSelected = data._selected_cells;
1095
+
1096
+ if ( data.anCells && (! cellSelected || ! cellSelected[ cellIdx.column ]) ) {
1097
+ $( data.anCells[ cellIdx.column ] ).removeClass( ctx._select.className );
1098
+ }
1099
+ } );
1100
+ } );
1101
+
1102
+ this.iterator( 'table', function ( ctx, i ) {
1103
+ eventTrigger( api, 'deselect', [ 'column', api[i] ], true );
1104
+ } );
1105
+
1106
+ return this;
1107
+ } );
1108
+
1109
+ apiRegisterPlural( 'cells().deselect()', 'cell().deselect()', function () {
1110
+ var api = this;
1111
+
1112
+ this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
1113
+ var data = ctx.aoData[ rowIdx ];
1114
+
1115
+ if(data._selected_cells !== undefined) {
1116
+ data._selected_cells[ colIdx ] = false;
1117
+ }
1118
+
1119
+ // Remove class only if the cells exist, and the cell is not column
1120
+ // selected, in which case the class should remain (since it is selected
1121
+ // in the column)
1122
+ if ( data.anCells && ! ctx.aoColumns[ colIdx ]._select_selected ) {
1123
+ $( data.anCells[ colIdx ] ).removeClass( ctx._select.className );
1124
+ }
1125
+ } );
1126
+
1127
+ this.iterator( 'table', function ( ctx, i ) {
1128
+ eventTrigger( api, 'deselect', [ 'cell', api[i] ], true );
1129
+ } );
1130
+
1131
+ return this;
1132
+ } );
1133
+
1134
+
1135
+
1136
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1137
+ * Buttons
1138
+ */
1139
+ function i18n( label, def ) {
1140
+ return function (dt) {
1141
+ return dt.i18n( 'buttons.'+label, def );
1142
+ };
1143
+ }
1144
+
1145
+ // Common events with suitable namespaces
1146
+ function namespacedEvents ( config ) {
1147
+ var unique = config._eventNamespace;
1148
+
1149
+ return 'draw.dt.DT'+unique+' select.dt.DT'+unique+' deselect.dt.DT'+unique;
1150
+ }
1151
+
1152
+ function enabled ( dt, config ) {
1153
+ if ( $.inArray( 'rows', config.limitTo ) !== -1 && dt.rows( { selected: true } ).any() ) {
1154
+ return true;
1155
+ }
1156
+
1157
+ if ( $.inArray( 'columns', config.limitTo ) !== -1 && dt.columns( { selected: true } ).any() ) {
1158
+ return true;
1159
+ }
1160
+
1161
+ if ( $.inArray( 'cells', config.limitTo ) !== -1 && dt.cells( { selected: true } ).any() ) {
1162
+ return true;
1163
+ }
1164
+
1165
+ return false;
1166
+ }
1167
+
1168
+ var _buttonNamespace = 0;
1169
+
1170
+ $.extend( DataTable.ext.buttons, {
1171
+ selected: {
1172
+ text: i18n( 'selected', 'Selected' ),
1173
+ className: 'buttons-selected',
1174
+ limitTo: [ 'rows', 'columns', 'cells' ],
1175
+ init: function ( dt, node, config ) {
1176
+ var that = this;
1177
+ config._eventNamespace = '.select'+(_buttonNamespace++);
1178
+
1179
+ // .DT namespace listeners are removed by DataTables automatically
1180
+ // on table destroy
1181
+ dt.on( namespacedEvents(config), function () {
1182
+ that.enable( enabled(dt, config) );
1183
+ } );
1184
+
1185
+ this.disable();
1186
+ },
1187
+ destroy: function ( dt, node, config ) {
1188
+ dt.off( config._eventNamespace );
1189
+ }
1190
+ },
1191
+ selectedSingle: {
1192
+ text: i18n( 'selectedSingle', 'Selected single' ),
1193
+ className: 'buttons-selected-single',
1194
+ init: function ( dt, node, config ) {
1195
+ var that = this;
1196
+ config._eventNamespace = '.select'+(_buttonNamespace++);
1197
+
1198
+ dt.on( namespacedEvents(config), function () {
1199
+ var count = dt.rows( { selected: true } ).flatten().length +
1200
+ dt.columns( { selected: true } ).flatten().length +
1201
+ dt.cells( { selected: true } ).flatten().length;
1202
+
1203
+ that.enable( count === 1 );
1204
+ } );
1205
+
1206
+ this.disable();
1207
+ },
1208
+ destroy: function ( dt, node, config ) {
1209
+ dt.off( config._eventNamespace );
1210
+ }
1211
+ },
1212
+ selectAll: {
1213
+ text: i18n( 'selectAll', 'Select all' ),
1214
+ className: 'buttons-select-all',
1215
+ action: function () {
1216
+ var items = this.select.items();
1217
+ this[ items+'s' ]().select();
1218
+ }
1219
+ },
1220
+ selectNone: {
1221
+ text: i18n( 'selectNone', 'Deselect all' ),
1222
+ className: 'buttons-select-none',
1223
+ action: function () {
1224
+ clear( this.settings()[0], true );
1225
+ },
1226
+ init: function ( dt, node, config ) {
1227
+ var that = this;
1228
+ config._eventNamespace = '.select'+(_buttonNamespace++);
1229
+
1230
+ dt.on( namespacedEvents(config), function () {
1231
+ var count = dt.rows( { selected: true } ).flatten().length +
1232
+ dt.columns( { selected: true } ).flatten().length +
1233
+ dt.cells( { selected: true } ).flatten().length;
1234
+
1235
+ that.enable( count > 0 );
1236
+ } );
1237
+
1238
+ this.disable();
1239
+ },
1240
+ destroy: function ( dt, node, config ) {
1241
+ dt.off( config._eventNamespace );
1242
+ }
1243
+ },
1244
+ showSelected: {
1245
+ text: i18n( 'showSelected', 'Show only selected' ),
1246
+ className: 'buttons-show-selected',
1247
+ action: function (e, dt, node, conf) {
1248
+ // Works by having a filtering function which will reduce to the selected
1249
+ // items only. So we can re-reference the function it gets stored in the
1250
+ // `conf` object
1251
+ if (conf._filter) {
1252
+ var idx = DataTable.ext.search.indexOf(conf._filter);
1253
+
1254
+ if (idx !== -1) {
1255
+ DataTable.ext.search.splice(idx, 1);
1256
+ conf._filter = null;
1257
+ }
1258
+
1259
+ this.active(false);
1260
+ }
1261
+ else {
1262
+ var fn = function (s, data, idx) {
1263
+ // Need to be sure we are operating on our table!
1264
+ if (s !== dt.settings()[0]) {
1265
+ return true;
1266
+ }
1267
+
1268
+ let row = s.aoData[idx];
1269
+
1270
+ return row._select_selected;
1271
+ }
1272
+
1273
+ conf._filter = fn;
1274
+ DataTable.ext.search.push(fn);
1275
+
1276
+ this.active(true);
1277
+ }
1278
+
1279
+ dt.draw();
1280
+ }
1281
+ }
1282
+ } );
1283
+
1284
+ $.each( [ 'Row', 'Column', 'Cell' ], function ( i, item ) {
1285
+ var lc = item.toLowerCase();
1286
+
1287
+ DataTable.ext.buttons[ 'select'+item+'s' ] = {
1288
+ text: i18n( 'select'+item+'s', 'Select '+lc+'s' ),
1289
+ className: 'buttons-select-'+lc+'s',
1290
+ action: function () {
1291
+ this.select.items( lc );
1292
+ },
1293
+ init: function ( dt ) {
1294
+ var that = this;
1295
+
1296
+ dt.on( 'selectItems.dt.DT', function ( e, ctx, items ) {
1297
+ that.active( items === lc );
1298
+ } );
1299
+ }
1300
+ };
1301
+ } );
1302
+
1303
+
1304
+ $.fn.DataTable.select = DataTable.select;
1305
+
1306
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1307
+ * Initialisation
1308
+ */
1309
+
1310
+ // DataTables creation - check if select has been defined in the options. Note
1311
+ // this required that the table be in the document! If it isn't then something
1312
+ // needs to trigger this method unfortunately. The next major release of
1313
+ // DataTables will rework the events and address this.
1314
+ $(document).on( 'preInit.dt.dtSelect', function (e, ctx) {
1315
+ if ( e.namespace !== 'dt' ) {
1316
+ return;
1317
+ }
1318
+
1319
+ DataTable.select.init( new DataTable.Api( ctx ) );
1320
+ } );
1321
+
1322
+
1323
+ export default DataTable;