datatables.net-select 2.0.5 → 3.0.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.
@@ -1,4 +1,4 @@
1
- /*! Select for DataTables 2.0.5
1
+ /*! Select for DataTables 3.0.0
2
2
  * © SpryMedia Ltd - datatables.net/license/mit
3
3
  */
4
4
 
@@ -16,7 +16,7 @@ DataTable.select.classes = {
16
16
  checkbox: 'dt-select-checkbox'
17
17
  };
18
18
 
19
- DataTable.select.version = '2.0.5';
19
+ DataTable.select.version = '3.0.0';
20
20
 
21
21
  DataTable.select.init = function (dt) {
22
22
  var ctx = dt.settings()[0];
@@ -86,11 +86,13 @@ DataTable.select.init = function (dt) {
86
86
  var style = 'api';
87
87
  var blurable = false;
88
88
  var toggleable = true;
89
+ var selectable = null;
89
90
  var info = true;
90
91
  var selector = 'td, th';
91
92
  var className = 'selected';
92
93
  var headerCheckbox = true;
93
94
  var setStyle = false;
95
+ var keys = false;
94
96
 
95
97
  ctx._select = {
96
98
  infoEls: []
@@ -142,6 +144,14 @@ DataTable.select.init = function (dt) {
142
144
  if (opts.headerCheckbox !== undefined) {
143
145
  headerCheckbox = opts.headerCheckbox;
144
146
  }
147
+
148
+ if (opts.selectable !== undefined) {
149
+ selectable = opts.selectable;
150
+ }
151
+
152
+ if (opts.keys !== undefined) {
153
+ keys = opts.keys;
154
+ }
145
155
  }
146
156
 
147
157
  dt.select.selector(selector);
@@ -150,6 +160,8 @@ DataTable.select.init = function (dt) {
150
160
  dt.select.blurable(blurable);
151
161
  dt.select.toggleable(toggleable);
152
162
  dt.select.info(info);
163
+ dt.select.keys(keys);
164
+ dt.select.selectable(selectable);
153
165
  ctx._select.className = className;
154
166
 
155
167
  // If the init options haven't enabled select, but there is a selectable
@@ -159,11 +171,9 @@ DataTable.select.init = function (dt) {
159
171
  }
160
172
 
161
173
  // Insert a checkbox into the header if needed - might need to wait
162
- // for init complete, or it might already be done
174
+ // for init complete
163
175
  if (headerCheckbox || headerCheckbox === 'select-page' || headerCheckbox === 'select-all') {
164
- initCheckboxHeader(dt, headerCheckbox);
165
-
166
- dt.on('init', function () {
176
+ dt.ready(function () {
167
177
  initCheckboxHeader(dt, headerCheckbox);
168
178
  });
169
179
  }
@@ -530,9 +540,17 @@ function info(api, node) {
530
540
  return;
531
541
  }
532
542
 
533
- var rows = api.rows({ selected: true }).flatten().length;
534
- var columns = api.columns({ selected: true }).flatten().length;
535
- var cells = api.cells({ selected: true }).flatten().length;
543
+ var ctx = api.settings()[0];
544
+ var rowSetLength = ctx._select_set.length;
545
+ var rows = rowSetLength ? rowSetLength : api.rows({ selected: true }).count();
546
+ var columns = api.columns({ selected: true }).count();
547
+ var cells = api.cells({ selected: true }).count();
548
+
549
+ // If subtractive selection, then we need to take the number of rows and
550
+ // subtract those that have been deselected
551
+ if (ctx._select_mode === 'subtractive') {
552
+ rows = api.page.info().recordsDisplay - rowSetLength;
553
+ }
536
554
 
537
555
  var add = function (el, name, num) {
538
556
  el.append(
@@ -572,7 +590,8 @@ function info(api, node) {
572
590
  * @param {*} headerCheckbox the header checkbox option
573
591
  */
574
592
  function initCheckboxHeader( dt, headerCheckbox ) {
575
- var dtInternalColumns = dt.settings()[0].aoColumns;
593
+ var dtSettings = dt.settings()[0];
594
+ var dtInternalColumns = dtSettings.aoColumns;
576
595
 
577
596
  // Find any checkbox column(s)
578
597
  dt.columns().iterator('column', function (s, idx) {
@@ -596,33 +615,37 @@ function initCheckboxHeader( dt, headerCheckbox ) {
596
615
  .on('change', function () {
597
616
  if (this.checked) {
598
617
  if (headerCheckbox == 'select-page') {
599
- dt.rows({page: 'current'}).select()
600
- } else {
618
+ dt.rows({page: 'current'}).select();
619
+ }
620
+ else {
601
621
  dt.rows({search: 'applied'}).select();
602
622
  }
603
623
  }
604
624
  else {
605
- dt.rows({selected: true}).deselect();
625
+ if (headerCheckbox == 'select-page') {
626
+ dt.rows({page: 'current', selected: true}).deselect();
627
+ }
628
+ else {
629
+ dt.rows({selected: true}).deselect();
630
+ }
606
631
  }
607
632
  })
608
633
  .on('click', function (e) {
609
634
  e.stopPropagation();
610
635
  });
611
-
636
+
612
637
  // Update the header checkbox's state when the selection in the
613
638
  // table changes
614
639
  dt.on('draw select deselect', function (e, pass, type) {
615
640
  if (type === 'row' || ! type) {
616
- var count = dt.rows({selected: true}).count();
617
- var search = dt.rows({search: 'applied', selected: true}).count();
618
- var available = headerCheckbox == 'select-page' ? dt.rows({page: 'current'}).count() : dt.rows({search: 'applied'}).count();
641
+ var nums = headerCheckboxState(dt, headerCheckbox);
619
642
 
620
- if (search && search <= count && search === available) {
643
+ if (nums.search && nums.search <= nums.count && nums.search === nums.available) {
621
644
  input
622
645
  .prop('checked', true)
623
646
  .prop('indeterminate', false);
624
647
  }
625
- else if (search === 0 && count === 0) {
648
+ else if (nums.search === 0 && nums.count === 0) {
626
649
  input
627
650
  .prop('checked', false)
628
651
  .prop('indeterminate', false);
@@ -638,6 +661,188 @@ function initCheckboxHeader( dt, headerCheckbox ) {
638
661
  });
639
662
  }
640
663
 
664
+ function keysSet(dt) {
665
+ var ctx = dt.settings()[0];
666
+ var flag = ctx._select.keys;
667
+ var namespace = 'dts-keys-' + ctx.sTableId;
668
+
669
+ if (flag) {
670
+ // Need a tabindex of the `tr` elements to make them focusable by the browser
671
+ $(dt.rows({page: 'current'}).nodes()).attr('tabindex', 0);
672
+
673
+ dt.on('draw.' + namespace, function () {
674
+ $(dt.rows({page: 'current'}).nodes()).attr('tabindex', 0);
675
+ });
676
+
677
+ // Listen on document for tab, up and down
678
+ $(document).on('keydown.' + namespace, function (e) {
679
+ var key = e.keyCode;
680
+ var active = document.activeElement;
681
+
682
+ // Can't use e.key as it wasn't widely supported until 2017
683
+ // 9 Tab
684
+ // 13 Return
685
+ // 32 Space
686
+ // 38 ArrowUp
687
+ // 40 ArrowDown
688
+ if (! [9, 13, 32, 38, 40].includes(key)) {
689
+ return;
690
+ }
691
+
692
+ var nodes = dt.rows({page: 'current'}).nodes().toArray();
693
+ var idx = nodes.indexOf(active);
694
+ var preventDefault = true;
695
+
696
+ // Only take an action if a row has focus
697
+ if (idx === -1) {
698
+ return;
699
+ }
700
+
701
+ if (key === 9) {
702
+ // Tab focus change
703
+ if (e.shift === false && idx === nodes.length - 1) {
704
+ keysPageDown(dt);
705
+ }
706
+ else if (e.shift === true && idx === 0) {
707
+ keysPageUp(dt);
708
+ }
709
+ else {
710
+ // Browser will do it for us
711
+ preventDefault = false;
712
+ }
713
+ }
714
+ else if (key === 13 || key === 32) {
715
+ // Row selection / deselection
716
+ var row = dt.row(active);
717
+
718
+ if (row.selected()) {
719
+ row.deselect();
720
+ }
721
+ else {
722
+ row.select();
723
+ }
724
+ }
725
+ else if (key === 38) {
726
+ // Move up
727
+ if (idx > 0) {
728
+ nodes[idx-1].focus();
729
+ }
730
+ else {
731
+ keysPageUp(dt);
732
+ }
733
+ }
734
+ else {
735
+ // Move down
736
+ if (idx < nodes.length -1) {
737
+ nodes[idx+1].focus();
738
+ }
739
+ else {
740
+ keysPageDown(dt);
741
+ }
742
+ }
743
+
744
+ if (preventDefault) {
745
+ e.stopPropagation();
746
+ e.preventDefault();
747
+ }
748
+ });
749
+ }
750
+ else {
751
+ // Stop the rows from being able to gain focus
752
+ $(dt.rows().nodes()).removeAttr('tabindex');
753
+
754
+ // Nuke events
755
+ dt.off('draw.' + namespace);
756
+ $(document).off('keydown.' + namespace);
757
+ }
758
+ }
759
+
760
+ /**
761
+ * Change to the next page and focus on the first row
762
+ *
763
+ * @param {DataTable.Api} dt DataTable instance
764
+ */
765
+ function keysPageDown(dt) {
766
+ // Is there another page to turn to?
767
+ var info = dt.page.info();
768
+
769
+ if (info.page < info.pages - 1) {
770
+ dt
771
+ .one('draw', function () {
772
+ dt.row(':first-child').node().focus();
773
+ })
774
+ .page('next')
775
+ .draw(false);
776
+ }
777
+ }
778
+
779
+ /**
780
+ * Change to the previous page and focus on the last row
781
+ *
782
+ * @param {DataTable.Api} dt DataTable instance
783
+ */
784
+ function keysPageUp(dt) {
785
+ // Is there another page to turn to?
786
+ var info = dt.page.info();
787
+
788
+ if (info.page > 0) {
789
+ dt
790
+ .one('draw', function () {
791
+ dt.row(':last-child').node().focus();
792
+ })
793
+ .page('previous')
794
+ .draw(false);
795
+ }
796
+ }
797
+
798
+ /**
799
+ * Determine the counts used to define the header checkbox's state
800
+ *
801
+ * @param {*} dt DT API
802
+ * @param {*} headerCheckbox Configuration for what the header checkbox does
803
+ * @returns Counts object
804
+ */
805
+ function headerCheckboxState(dt, headerCheckbox) {
806
+ var ctx = dt.settings()[0];
807
+ var selectable = ctx._select.selectable;
808
+ var available = 0;
809
+ var count = headerCheckbox == 'select-page'
810
+ ? dt.rows({page: 'current', selected: true}).count()
811
+ : dt.rows({selected: true}).count();
812
+ var search = headerCheckbox == 'select-page'
813
+ ? dt.rows({page: 'current', selected: true}).count()
814
+ : dt.rows({search: 'applied', selected: true}).count();
815
+
816
+ if (! selectable) {
817
+ available = headerCheckbox == 'select-page'
818
+ ? dt.rows({page: 'current'}).count()
819
+ : dt.rows({search: 'applied'}).count();
820
+ }
821
+ else {
822
+ // Need to count how many rows are actually selectable to know if all selectable
823
+ // rows are selected or not
824
+ var indexes = headerCheckbox == 'select-page'
825
+ ? dt.rows({page: 'current'}).indexes()
826
+ : dt.rows({search: 'applied'}).indexes();
827
+
828
+ for (var i=0 ; i<indexes.length ; i++) {
829
+ // For speed I use the internal DataTables object.
830
+ var rowInternal = ctx.aoData[indexes[i]];
831
+ var result = selectable(rowInternal._aData, rowInternal.nTr, indexes[i]);
832
+
833
+ if (result) {
834
+ available++;
835
+ }
836
+ }
837
+ }
838
+
839
+ return {
840
+ available: available,
841
+ count: count,
842
+ search: search
843
+ }
844
+ }
845
+
641
846
  /**
642
847
  * Initialisation of a new table. Attach event handlers and callbacks to allow
643
848
  * Select to operate correctly.
@@ -652,6 +857,12 @@ function init(ctx) {
652
857
  var api = new DataTable.Api(ctx);
653
858
  ctx._select_init = true;
654
859
 
860
+ // When `additive` then `_select_set` contains a list of the row ids that
861
+ // are selected. If `subtractive` then all rows are selected, except those
862
+ // in `_select_set`, which is a list of ids.
863
+ ctx._select_mode = 'additive';
864
+ ctx._select_set = [];
865
+
655
866
  // Row callback so that classes can be added to rows and cells if the item
656
867
  // was selected before the element was created. This will happen with the
657
868
  // `deferRender` option enabled.
@@ -662,9 +873,16 @@ function init(ctx) {
662
873
  ctx.aoRowCreatedCallback.push(function (row, data, index) {
663
874
  var i, ien;
664
875
  var d = ctx.aoData[index];
876
+ var id = api.row(index).id();
665
877
 
666
878
  // Row
667
- if (d._select_selected) {
879
+ if (
880
+ d._select_selected ||
881
+ (ctx._select_mode === 'additive' && ctx._select_set.includes(id)) ||
882
+ (ctx._select_mode === 'subtractive' && ! ctx._select_set.includes(id))
883
+ ) {
884
+ d._select_selected = true;
885
+
668
886
  $(row)
669
887
  .addClass(ctx._select.className)
670
888
  .find('input.' + checkboxClass(true)).prop('checked', true);
@@ -683,46 +901,7 @@ function init(ctx) {
683
901
  }
684
902
  );
685
903
 
686
- // On Ajax reload we want to reselect all rows which are currently selected,
687
- // if there is an rowId (i.e. a unique value to identify each row with)
688
- api.on('preXhr.dt.dtSelect', function (e, settings) {
689
- if (settings !== api.settings()[0]) {
690
- // Not triggered by our DataTable!
691
- return;
692
- }
693
-
694
- // note that column selection doesn't need to be cached and then
695
- // reselected, as they are already selected
696
- var rows = api
697
- .rows({ selected: true })
698
- .ids(true)
699
- .filter(function (d) {
700
- return d !== undefined;
701
- });
702
-
703
- var cells = api
704
- .cells({ selected: true })
705
- .eq(0)
706
- .map(function (cellIdx) {
707
- var id = api.row(cellIdx.row).id(true);
708
- return id ? { row: id, column: cellIdx.column } : undefined;
709
- })
710
- .filter(function (d) {
711
- return d !== undefined;
712
- });
713
-
714
- // On the next draw, reselect the currently selected items
715
- api.one('draw.dt.dtSelect', function () {
716
- api.rows(rows).select();
717
-
718
- // `cells` is not a cell index selector, so it needs a loop
719
- if (cells.any()) {
720
- cells.each(function (id) {
721
- api.cells(id.row, id.column).select();
722
- });
723
- }
724
- });
725
- });
904
+ _cumulativeEvents(api);
726
905
 
727
906
  // Update the table information element with selected item summary
728
907
  api.on('info.dt', function (e, ctx, node) {
@@ -895,6 +1074,73 @@ function _safeId(node) {
895
1074
  return node.id.replace(/[^a-zA-Z0-9\-\_]/g, '-');
896
1075
  }
897
1076
 
1077
+ /**
1078
+ * Set up event handlers for cumulative selection
1079
+ *
1080
+ * @param {*} api DT API instance
1081
+ */
1082
+ function _cumulativeEvents(api) {
1083
+ // Add event listeners to add / remove from the _select_set
1084
+ api.on('select', function (e, dt, type, indexes) {
1085
+ // Only support for rows at the moment
1086
+ if (type !== 'row') {
1087
+ return;
1088
+ }
1089
+
1090
+ var ctx = api.settings()[0];
1091
+
1092
+ if (ctx._select_mode === 'additive') {
1093
+ // Add row to the selection list if it isn't already there
1094
+ _add(api, ctx._select_set, indexes);
1095
+ }
1096
+ else {
1097
+ // Subtractive - if a row is selected it should not in the list
1098
+ // as in subtractive mode the list gives the rows which are not
1099
+ // selected
1100
+ _remove(api, ctx._select_set, indexes);
1101
+ }
1102
+ });
1103
+
1104
+ api.on('deselect', function (e, dt, type, indexes) {
1105
+ // Only support for rows at the moment
1106
+ if (type !== 'row') {
1107
+ return;
1108
+ }
1109
+
1110
+ var ctx = api.settings()[0];
1111
+
1112
+ if (ctx._select_mode === 'additive') {
1113
+ // List is of those rows selected, so remove it
1114
+ _remove(api, ctx._select_set, indexes);
1115
+ }
1116
+ else {
1117
+ // List is of rows which are deselected, so add it!
1118
+ _add(api, ctx._select_set, indexes);
1119
+ }
1120
+ });
1121
+ }
1122
+
1123
+ function _add(api, arr, indexes) {
1124
+ for (var i=0 ; i<indexes.length ; i++) {
1125
+ var id = api.row(indexes[i]).id();
1126
+
1127
+ if (id && id !== 'undefined' && ! arr.includes(id)) {
1128
+ arr.push(id);
1129
+ }
1130
+ }
1131
+ }
1132
+
1133
+ function _remove(api, arr, indexes) {
1134
+ for (var i=0 ; i<indexes.length ; i++) {
1135
+ var id = api.row(indexes[i]).id();
1136
+ var idx = arr.indexOf(id);
1137
+
1138
+ if (idx !== -1) {
1139
+ arr.splice(idx, 1);
1140
+ }
1141
+ }
1142
+ }
1143
+
898
1144
  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
899
1145
  * DataTables selectors
900
1146
  */
@@ -1022,6 +1268,22 @@ apiRegister('select.items()', function (items) {
1022
1268
  });
1023
1269
  });
1024
1270
 
1271
+ apiRegister('select.keys()', function (flag) {
1272
+ if (flag === undefined) {
1273
+ return this.context[0]._select.keys;
1274
+ }
1275
+
1276
+ return this.iterator('table', function (ctx) {
1277
+ if (!ctx._select) {
1278
+ DataTable.select.init(new DataTable.Api(ctx));
1279
+ }
1280
+
1281
+ ctx._select.keys = flag;
1282
+
1283
+ keysSet(new DataTable.Api(ctx));
1284
+ });
1285
+ });
1286
+
1025
1287
  // Takes effect from the _next_ selection. None disables future selection, but
1026
1288
  // does not clear the current selection. Use the `deselect` methods for that
1027
1289
  apiRegister('select.style()', function (style) {
@@ -1043,10 +1305,15 @@ apiRegister('select.style()', function (style) {
1043
1305
  // Add / remove mouse event handlers. They aren't required when only
1044
1306
  // API selection is available
1045
1307
  var dt = new DataTable.Api(ctx);
1046
- disableMouseSelection(dt);
1047
1308
 
1048
1309
  if (style !== 'api') {
1049
- enableMouseSelection(dt);
1310
+ dt.ready(function () {
1311
+ disableMouseSelection(dt);
1312
+ enableMouseSelection(dt);
1313
+ });
1314
+ }
1315
+ else {
1316
+ disableMouseSelection(dt);
1050
1317
  }
1051
1318
 
1052
1319
  eventTrigger(new DataTable.Api(ctx), 'selectStyle', [style]);
@@ -1059,16 +1326,36 @@ apiRegister('select.selector()', function (selector) {
1059
1326
  }
1060
1327
 
1061
1328
  return this.iterator('table', function (ctx) {
1062
- disableMouseSelection(new DataTable.Api(ctx));
1329
+ var dt = new DataTable.Api(ctx);
1330
+ var style = ctx._select.style;
1331
+
1332
+ disableMouseSelection(dt);
1063
1333
 
1064
1334
  ctx._select.selector = selector;
1065
1335
 
1066
- if (ctx._select.style !== 'api') {
1067
- enableMouseSelection(new DataTable.Api(ctx));
1336
+ if (style && style !== 'api') {
1337
+ dt.ready(function () {
1338
+ disableMouseSelection(dt);
1339
+ enableMouseSelection(dt);
1340
+ });
1341
+ }
1342
+ else {
1343
+ disableMouseSelection(dt);
1068
1344
  }
1069
1345
  });
1070
1346
  });
1071
1347
 
1348
+ apiRegister('select.selectable()', function (set) {
1349
+ let ctx = this.context[0];
1350
+
1351
+ if (set) {
1352
+ ctx._select.selectable = set;
1353
+ return this;
1354
+ }
1355
+
1356
+ return ctx._select.selectable;
1357
+ });
1358
+
1072
1359
  apiRegister('select.last()', function (set) {
1073
1360
  let ctx = this.context[0];
1074
1361
 
@@ -1080,8 +1367,50 @@ apiRegister('select.last()', function (set) {
1080
1367
  return ctx._select_lastCell;
1081
1368
  });
1082
1369
 
1370
+ apiRegister('select.cumulative()', function (mode) {
1371
+ if (mode) {
1372
+ return this.iterator('table', function (ctx) {
1373
+ if (ctx._select_mode === mode) {
1374
+ return;
1375
+ }
1376
+
1377
+ var dt = new DataTable.Api(ctx);
1378
+
1379
+ // Convert from the current mode, to the new
1380
+ if (mode === 'subtractive') {
1381
+ // For subtractive mode we track the row ids which are not selected
1382
+ var unselected = dt.rows({selected: false}).ids().toArray();
1383
+
1384
+ ctx._select_mode = mode;
1385
+ ctx._select_set.length = 0;
1386
+ ctx._select_set.push.apply(ctx._select_set, unselected);
1387
+ }
1388
+ else {
1389
+ // Switching to additive, so selected rows are to be used
1390
+ var selected = dt.rows({selected: true}).ids().toArray();
1391
+
1392
+ ctx._select_mode = mode;
1393
+ ctx._select_set.length = 0;
1394
+ ctx._select_set.push.apply(ctx._select_set, selected);
1395
+ }
1396
+ }).draw(false);
1397
+ }
1398
+
1399
+ let ctx = this.context[0];
1400
+
1401
+ if (ctx && ctx._select_set) {
1402
+ return {
1403
+ mode: ctx._select_mode,
1404
+ rows: ctx._select_set
1405
+ };
1406
+ }
1407
+
1408
+ return null;
1409
+ });
1410
+
1083
1411
  apiRegisterPlural('rows().select()', 'row().select()', function (select) {
1084
1412
  var api = this;
1413
+ var selectedIndexes = [];
1085
1414
 
1086
1415
  if (select === false) {
1087
1416
  return this.deselect();
@@ -1096,9 +1425,20 @@ apiRegisterPlural('rows().select()', 'row().select()', function (select) {
1096
1425
  var dtData = ctx.aoData[idx];
1097
1426
  var dtColumns = ctx.aoColumns;
1098
1427
 
1428
+ if (ctx._select.selectable) {
1429
+ var result = ctx._select.selectable(dtData._aData, dtData.nTr, idx);
1430
+
1431
+ if (result === false) {
1432
+ // Not selectable - do nothing
1433
+ return;
1434
+ }
1435
+ }
1436
+
1099
1437
  $(dtData.nTr).addClass(ctx._select.className);
1100
1438
  dtData._select_selected = true;
1101
1439
 
1440
+ selectedIndexes.push(idx);
1441
+
1102
1442
  for (var i=0 ; i<dtColumns.length ; i++) {
1103
1443
  var col = dtColumns[i];
1104
1444
 
@@ -1123,8 +1463,8 @@ apiRegisterPlural('rows().select()', 'row().select()', function (select) {
1123
1463
  }
1124
1464
  });
1125
1465
 
1126
- this.iterator('table', function (ctx, i) {
1127
- eventTrigger(api, 'select', ['row', api[i]], true);
1466
+ this.iterator('table', function (ct) {
1467
+ eventTrigger(api, 'select', ['row', selectedIndexes], true);
1128
1468
  });
1129
1469
 
1130
1470
  return this;
@@ -1140,6 +1480,22 @@ apiRegister('row().selected()', function () {
1140
1480
  return false;
1141
1481
  });
1142
1482
 
1483
+ apiRegister('row().focus()', function () {
1484
+ var ctx = this.context[0];
1485
+
1486
+ if (ctx && this.length && ctx.aoData[this[0]] && ctx.aoData[this[0]].nTr) {
1487
+ ctx.aoData[this[0]].nTr.focus();
1488
+ }
1489
+ });
1490
+
1491
+ apiRegister('row().blur()', function () {
1492
+ var ctx = this.context[0];
1493
+
1494
+ if (ctx && this.length && ctx.aoData[this[0]] && ctx.aoData[this[0]].nTr) {
1495
+ ctx.aoData[this[0]].nTr.blur();
1496
+ }
1497
+ });
1498
+
1143
1499
  apiRegisterPlural('columns().select()', 'column().select()', function (select) {
1144
1500
  var api = this;
1145
1501
 
@@ -1486,6 +1842,8 @@ $.each(['Row', 'Column', 'Cell'], function (i, item) {
1486
1842
  init: function (dt) {
1487
1843
  var that = this;
1488
1844
 
1845
+ this.active(dt.select.items() === lc);
1846
+
1489
1847
  dt.on('selectItems.dt.DT', function (e, ctx, items) {
1490
1848
  that.active(items === lc);
1491
1849
  });
@@ -1536,8 +1894,18 @@ DataTable.render.select = function (valueProp, nameProp) {
1536
1894
  var dtRow = meta.settings.aoData[meta.row];
1537
1895
  var selected = dtRow._select_selected;
1538
1896
  var ariaLabel = meta.settings.oLanguage.select.aria.rowCheckbox;
1897
+ var selectable = meta.settings._select.selectable;
1539
1898
 
1540
1899
  if (type === 'display') {
1900
+ // Check if the row is selectable before showing the checkbox
1901
+ if (selectable) {
1902
+ var result = selectable(row, dtRow.nTr, meta.row);
1903
+
1904
+ if (result === false) {
1905
+ return '';
1906
+ }
1907
+ }
1908
+
1541
1909
  return $('<input>')
1542
1910
  .attr({
1543
1911
  'aria-label': ariaLabel,
@@ -1596,11 +1964,10 @@ $.fn.DataTable.select = DataTable.select;
1596
1964
  * Initialisation
1597
1965
  */
1598
1966
 
1599
- // DataTables creation - check if select has been defined in the options. Note
1600
- // this required that the table be in the document! If it isn't then something
1601
- // needs to trigger this method unfortunately. The next major release of
1602
- // DataTables will rework the events and address this.
1603
- $(document).on('preInit.dt.dtSelect', function (e, ctx) {
1967
+ // DataTables creation - we need this to run _before_ data is read in, but
1968
+ // for backwards compat. we also run again on preInit. If it happens twice
1969
+ // it will simply do nothing the second time around.
1970
+ $(document).on('i18n.dt.dtSelect preInit.dt.dtSelect', function (e, ctx) {
1604
1971
  if (e.namespace !== 'dt') {
1605
1972
  return;
1606
1973
  }