diff --git a/assets/controllers/elements/datatables/datatables_controller.js b/assets/controllers/elements/datatables/datatables_controller.js
index f6c1f69f..cef7779c 100644
--- a/assets/controllers/elements/datatables/datatables_controller.js
+++ b/assets/controllers/elements/datatables/datatables_controller.js
@@ -31,11 +31,15 @@ import 'datatables.net-bs5';
import 'datatables.net-buttons-bs5';
import 'datatables.net-buttons/js/buttons.colVis.js';
import 'datatables.net-fixedheader-bs5';
-import 'datatables.net-select-bs5';
import 'datatables.net-colreorder-bs5';
import 'datatables.net-responsive-bs5';
import '../../../js/lib/datatables';
+//import 'datatables.net-select-bs5';
+//Use the local version containing the fix for the select extension
+import '../../../js/lib/dataTables.select.mjs';
+
+
const EVENT_DT_LOADED = 'dt:loaded';
export default class extends Controller {
@@ -132,7 +136,7 @@ export default class extends Controller {
if(this.isSelectable()) {
options.select = {
style: 'multi+shift',
- selector: 'td.select-checkbox'
+ selector: 'td.dt-select',
};
}
@@ -186,27 +190,6 @@ export default class extends Controller {
dt.fixedHeader.headerOffset($("#navbar").outerHeight());
});
- //Register event handler to selectAllRows checkbox if available
- if (this.isSelectable()) {
- promise.then((dt) => {
- const selectAllCheckbox = this.element.querySelector('thead th.select-checkbox');
- selectAllCheckbox.addEventListener('click', () => {
- if(selectAllCheckbox.parentElement.classList.contains('selected')) {
- dt.rows().deselect();
- selectAllCheckbox.parentElement.classList.remove('selected');
- } else {
- dt.rows().select();
- selectAllCheckbox.parentElement.classList.add('selected');
- }
- });
-
- //When any row is deselected, also deselect the selectAll checkbox
- dt.on('deselect.dt', () => {
- selectAllCheckbox.parentElement.classList.remove('selected');
- });
- });
- }
-
//Allow to further configure the datatable
promise.then(this._afterLoaded.bind(this));
diff --git a/assets/js/lib/dataTables.select.mjs b/assets/js/lib/dataTables.select.mjs
new file mode 100644
index 00000000..bba97692
--- /dev/null
+++ b/assets/js/lib/dataTables.select.mjs
@@ -0,0 +1,1538 @@
+/*********************
+ * This is the fixed version of the select extension for DataTables with the fix for the issue with the select extension
+ * (https://github.com/DataTables/Select/issues/51)
+ * We use this instead of the yarn version until the PR (https://github.com/DataTables/Select/pull/52) is merged and released
+ * /*******************/
+
+
+/*! Select for DataTables 2.0.0
+ * © SpryMedia Ltd - datatables.net/license/mit
+ */
+
+import jQuery from 'jquery';
+import DataTable from 'datatables.net';
+
+// Allow reassignment of the $ variable
+let $ = jQuery;
+
+
+// Version information for debugger
+DataTable.select = {};
+
+DataTable.select.version = '2.0.0';
+
+DataTable.select.init = function (dt) {
+ var ctx = dt.settings()[0];
+
+ if (!DataTable.versionCheck('2')) {
+ throw 'Warning: Select requires DataTables 2 or newer';
+ }
+
+ if (ctx._select) {
+ return;
+ }
+
+ var savedSelected = dt.state.loaded();
+
+ var selectAndSave = function (e, settings, data) {
+ if (data === null || data.select === undefined) {
+ return;
+ }
+
+ // Clear any currently selected rows, before restoring state
+ // None will be selected on first initialisation
+ if (dt.rows({ selected: true }).any()) {
+ dt.rows().deselect();
+ }
+ if (data.select.rows !== undefined) {
+ dt.rows(data.select.rows).select();
+ }
+
+ if (dt.columns({ selected: true }).any()) {
+ dt.columns().deselect();
+ }
+ if (data.select.columns !== undefined) {
+ dt.columns(data.select.columns).select();
+ }
+
+ if (dt.cells({ selected: true }).any()) {
+ dt.cells().deselect();
+ }
+ if (data.select.cells !== undefined) {
+ for (var i = 0; i < data.select.cells.length; i++) {
+ dt.cell(data.select.cells[i].row, data.select.cells[i].column).select();
+ }
+ }
+
+ dt.state.save();
+ };
+
+ dt.on('stateSaveParams', function (e, settings, data) {
+ data.select = {};
+ data.select.rows = dt.rows({ selected: true }).ids(true).toArray();
+ data.select.columns = dt.columns({ selected: true })[0];
+ data.select.cells = dt.cells({ selected: true })[0].map(function (coords) {
+ return { row: dt.row(coords.row).id(true), column: coords.column };
+ });
+ })
+ .on('stateLoadParams', selectAndSave)
+ .one('init', function () {
+ selectAndSave(undefined, undefined, savedSelected);
+ });
+
+ var init = ctx.oInit.select;
+ var defaults = DataTable.defaults.select;
+ var opts = init === undefined ? defaults : init;
+
+ // Set defaults
+ var items = 'row';
+ var style = 'api';
+ var blurable = false;
+ var toggleable = true;
+ var info = true;
+ var selector = 'td, th';
+ var className = 'selected';
+ var headerCheckbox = true;
+ var setStyle = false;
+
+ ctx._select = {
+ infoEls: []
+ };
+
+ // Initialisation customisations
+ if (opts === true) {
+ style = 'os';
+ setStyle = true;
+ }
+ else if (typeof opts === 'string') {
+ style = opts;
+ setStyle = true;
+ }
+ else if ($.isPlainObject(opts)) {
+ if (opts.blurable !== undefined) {
+ blurable = opts.blurable;
+ }
+
+ if (opts.toggleable !== undefined) {
+ toggleable = opts.toggleable;
+ }
+
+ if (opts.info !== undefined) {
+ info = opts.info;
+ }
+
+ if (opts.items !== undefined) {
+ items = opts.items;
+ }
+
+ if (opts.style !== undefined) {
+ style = opts.style;
+ setStyle = true;
+ }
+ else {
+ style = 'os';
+ setStyle = true;
+ }
+
+ if (opts.selector !== undefined) {
+ selector = opts.selector;
+ }
+
+ if (opts.className !== undefined) {
+ className = opts.className;
+ }
+
+ if (opts.headerCheckbox !== undefined) {
+ headerCheckbox = opts.headerCheckbox;
+ }
+ }
+
+ dt.select.selector(selector);
+ dt.select.items(items);
+ dt.select.style(style);
+ dt.select.blurable(blurable);
+ dt.select.toggleable(toggleable);
+ dt.select.info(info);
+ ctx._select.className = className;
+
+ // If the init options haven't enabled select, but there is a selectable
+ // class name, then enable
+ if (!setStyle && $(dt.table().node()).hasClass('selectable')) {
+ dt.select.style('os');
+ }
+
+ // Insert a checkbox into the header if needed - might need to wait
+ // for init complete, or it might already be done
+ if (headerCheckbox) {
+ initCheckboxHeader(dt);
+
+ dt.on('init', function () {
+ initCheckboxHeader(dt);
+ });
+ }
+};
+
+/*
+
+Select is a collection of API methods, event handlers, event emitters and
+buttons (for the `Buttons` extension) for DataTables. It provides the following
+features, with an overview of how they are implemented:
+
+## Selection of rows, columns and cells. Whether an item is selected or not is
+ stored in:
+
+* rows: a `_select_selected` property which contains a boolean value of the
+ DataTables' `aoData` object for each row
+* columns: a `_select_selected` property which contains a boolean value of the
+ DataTables' `aoColumns` object for each column
+* cells: a `_selected_cells` property which contains an array of boolean values
+ of the `aoData` object for each row. The array is the same length as the
+ columns array, with each element of it representing a cell.
+
+This method of using boolean flags allows Select to operate when nodes have not
+been created for rows / cells (DataTables' defer rendering feature).
+
+## API methods
+
+A range of API methods are available for triggering selection and de-selection
+of rows. Methods are also available to configure the selection events that can
+be triggered by an end user (such as which items are to be selected). To a large
+extent, these of API methods *is* Select. It is basically a collection of helper
+functions that can be used to select items in a DataTable.
+
+Configuration of select is held in the object `_select` which is attached to the
+DataTables settings object on initialisation. Select being available on a table
+is not optional when Select is loaded, but its default is for selection only to
+be available via the API - so the end user wouldn't be able to select rows
+without additional configuration.
+
+The `_select` object contains the following properties:
+
+```
+{
+ items:string - Can be `rows`, `columns` or `cells`. Defines what item
+ will be selected if the user is allowed to activate row
+ selection using the mouse.
+ style:string - Can be `none`, `single`, `multi` or `os`. Defines the
+ interaction style when selecting items
+ blurable:boolean - If row selection can be cleared by clicking outside of
+ the table
+ toggleable:boolean - If row selection can be cancelled by repeated clicking
+ on the row
+ info:boolean - If the selection summary should be shown in the table
+ information elements
+ infoEls:element[] - List of HTML elements with info elements for a table
+}
+```
+
+In addition to the API methods, Select also extends the DataTables selector
+options for rows, columns and cells adding a `selected` option to the selector
+options object, allowing the developer to select only selected items or
+unselected items.
+
+## Mouse selection of items
+
+Clicking on items can be used to select items. This is done by a simple event
+handler that will select the items using the API methods.
+
+ */
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Local functions
+ */
+
+/**
+ * Add one or more cells to the selection when shift clicking in OS selection
+ * style cell selection.
+ *
+ * Cell range is more complicated than row and column as we want to select
+ * in the visible grid rather than by index in sequence. For example, if you
+ * click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1
+ * should also be selected (and not 1-3, 1-4. etc)
+ *
+ * @param {DataTable.Api} dt DataTable
+ * @param {object} idx Cell index to select to
+ * @param {object} last Cell index to select from
+ * @private
+ */
+function cellRange(dt, idx, last) {
+ var indexes;
+ var columnIndexes;
+ var rowIndexes;
+ var selectColumns = function (start, end) {
+ if (start > end) {
+ var tmp = end;
+ end = start;
+ start = tmp;
+ }
+
+ var record = false;
+ return dt
+ .columns(':visible')
+ .indexes()
+ .filter(function (i) {
+ if (i === start) {
+ record = true;
+ }
+
+ if (i === end) {
+ // not else if, as start might === end
+ record = false;
+ return true;
+ }
+
+ return record;
+ });
+ };
+
+ var selectRows = function (start, end) {
+ var indexes = dt.rows({ search: 'applied' }).indexes();
+
+ // Which comes first - might need to swap
+ if (indexes.indexOf(start) > indexes.indexOf(end)) {
+ var tmp = end;
+ end = start;
+ start = tmp;
+ }
+
+ var record = false;
+ return indexes.filter(function (i) {
+ if (i === start) {
+ record = true;
+ }
+
+ if (i === end) {
+ record = false;
+ return true;
+ }
+
+ return record;
+ });
+ };
+
+ if (!dt.cells({ selected: true }).any() && !last) {
+ // select from the top left cell to this one
+ columnIndexes = selectColumns(0, idx.column);
+ rowIndexes = selectRows(0, idx.row);
+ }
+ else {
+ // Get column indexes between old and new
+ columnIndexes = selectColumns(last.column, idx.column);
+ rowIndexes = selectRows(last.row, idx.row);
+ }
+
+ indexes = dt.cells(rowIndexes, columnIndexes).flatten();
+
+ if (!dt.cells(idx, { selected: true }).any()) {
+ // Select range
+ dt.cells(indexes).select();
+ }
+ else {
+ // Deselect range
+ dt.cells(indexes).deselect();
+ }
+}
+
+/**
+ * Disable mouse selection by removing the selectors
+ *
+ * @param {DataTable.Api} dt DataTable to remove events from
+ * @private
+ */
+function disableMouseSelection(dt) {
+ var ctx = dt.settings()[0];
+ var selector = ctx._select.selector;
+
+ $(dt.table().container())
+ .off('mousedown.dtSelect', selector)
+ .off('mouseup.dtSelect', selector)
+ .off('click.dtSelect', selector);
+
+ $('body').off('click.dtSelect' + _safeId(dt.table().node()));
+}
+
+/**
+ * Attach mouse listeners to the table to allow mouse selection of items
+ *
+ * @param {DataTable.Api} dt DataTable to remove events from
+ * @private
+ */
+function enableMouseSelection(dt) {
+ var container = $(dt.table().container());
+ var ctx = dt.settings()[0];
+ var selector = ctx._select.selector;
+ var matchSelection;
+
+ container
+ .on('mousedown.dtSelect', selector, function (e) {
+ // Disallow text selection for shift clicking on the table so multi
+ // element selection doesn't look terrible!
+ if (e.shiftKey || e.metaKey || e.ctrlKey) {
+ container
+ .css('-moz-user-select', 'none')
+ .one('selectstart.dtSelect', selector, function () {
+ return false;
+ });
+ }
+
+ if (window.getSelection) {
+ matchSelection = window.getSelection();
+ }
+ })
+ .on('mouseup.dtSelect', selector, function () {
+ // Allow text selection to occur again, Mozilla style (tested in FF
+ // 35.0.1 - still required)
+ container.css('-moz-user-select', '');
+ })
+ .on('click.dtSelect', selector, function (e) {
+ var items = dt.select.items();
+ var idx;
+
+ // If text was selected (click and drag), then we shouldn't change
+ // the row's selected state
+ if (matchSelection) {
+ var selection = window.getSelection();
+
+ // If the element that contains the selection is not in the table, we can ignore it
+ // This can happen if the developer selects text from the click event
+ if (
+ !selection.anchorNode ||
+ $(selection.anchorNode).closest('table')[0] === dt.table().node()
+ ) {
+ if (selection !== matchSelection) {
+ return;
+ }
+ }
+ }
+
+ var ctx = dt.settings()[0];
+ var container = dt.table().container();
+
+ // Ignore clicks inside a sub-table
+ if ($(e.target).closest('div.dt-container')[0] != container) {
+ return;
+ }
+
+ var cell = dt.cell($(e.target).closest('td, th'));
+
+ // Check the cell actually belongs to the host DataTable (so child
+ // rows, etc, are ignored)
+ if (!cell.any()) {
+ return;
+ }
+
+ var event = $.Event('user-select.dt');
+ eventTrigger(dt, event, [items, cell, e]);
+
+ if (event.isDefaultPrevented()) {
+ return;
+ }
+
+ var cellIndex = cell.index();
+ if (items === 'row') {
+ idx = cellIndex.row;
+ typeSelect(e, dt, ctx, 'row', idx);
+ }
+ else if (items === 'column') {
+ idx = cell.index().column;
+ typeSelect(e, dt, ctx, 'column', idx);
+ }
+ else if (items === 'cell') {
+ idx = cell.index();
+ typeSelect(e, dt, ctx, 'cell', idx);
+ }
+
+ ctx._select_lastCell = cellIndex;
+ });
+
+ // Blurable
+ $('body').on('click.dtSelect' + _safeId(dt.table().node()), function (e) {
+ if (ctx._select.blurable) {
+ // If the click was inside the DataTables container, don't blur
+ if ($(e.target).parents().filter(dt.table().container()).length) {
+ return;
+ }
+
+ // Ignore elements which have been removed from the DOM (i.e. paging
+ // buttons)
+ if ($(e.target).parents('html').length === 0) {
+ return;
+ }
+
+ // Don't blur in Editor form
+ if ($(e.target).parents('div.DTE').length) {
+ return;
+ }
+
+ var event = $.Event('select-blur.dt');
+ eventTrigger(dt, event, [e.target, e]);
+
+ if (event.isDefaultPrevented()) {
+ return;
+ }
+
+ clear(ctx, true);
+ }
+ });
+}
+
+/**
+ * Trigger an event on a DataTable
+ *
+ * @param {DataTable.Api} api DataTable to trigger events on
+ * @param {boolean} selected true if selected, false if deselected
+ * @param {string} type Item type acting on
+ * @param {boolean} any Require that there are values before
+ * triggering
+ * @private
+ */
+function eventTrigger(api, type, args, any) {
+ if (any && !api.flatten().length) {
+ return;
+ }
+
+ if (typeof type === 'string') {
+ type = type + '.dt';
+ }
+
+ args.unshift(api);
+
+ $(api.table().node()).trigger(type, args);
+}
+
+/**
+ * Update the information element of the DataTable showing information about the
+ * items selected. This is done by adding tags to the existing text
+ *
+ * @param {DataTable.Api} api DataTable to update
+ * @private
+ */
+function info(api, node) {
+ if (api.select.style() === 'api' || api.select.info() === false) {
+ return;
+ }
+
+ var rows = api.rows({ selected: true }).flatten().length;
+ var columns = api.columns({ selected: true }).flatten().length;
+ var cells = api.cells({ selected: true }).flatten().length;
+
+ var add = function (el, name, num) {
+ el.append(
+ $('').append(
+ api.i18n(
+ 'select.' + name + 's',
+ { _: '%d ' + name + 's selected', 0: '', 1: '1 ' + name + ' selected' },
+ num
+ )
+ )
+ );
+ };
+
+ var el = $(node);
+ var output = $('');
+
+ add(output, 'row', rows);
+ add(output, 'column', columns);
+ add(output, 'cell', cells);
+
+ var existing = el.children('span.select-info');
+
+ if (existing.length) {
+ existing.remove();
+ }
+
+ if (output.text() !== '') {
+ el.append(output);
+ }
+}
+
+/**
+ * Add a checkbox to the header for checkbox columns, allowing all rows to
+ * be selected, deselected or just to show the state.
+ *
+ * @param {*} dt API
+ */
+function initCheckboxHeader( dt ) {
+ // Find any checkbox column(s)
+ dt.columns('.dt-select').every(function () {
+ var header = this.header();
+
+ if (! $('input', header).length) {
+ // If no checkbox yet, insert one
+ var input = $('')
+ .attr({
+ class: 'dt-select-checkbox',
+ type: 'checkbox',
+ 'aria-label': dt.i18n('select.aria.headerCheckbox') || 'Select all rows'
+ })
+ .appendTo(header)
+ .on('change', function () {
+ if (this.checked) {
+ dt.rows({search: 'applied'}).select();
+ }
+ else {
+ dt.rows({selected: true}).deselect();
+ }
+ })
+ .on('click', function (e) {
+ e.stopPropagation();
+ });
+
+ // Update the header checkbox's state when the selection in the
+ // table changes
+ dt.on('draw select deselect', function (e, pass, type) {
+ if (type === 'row' || ! type) {
+ var count = dt.rows({selected: true}).count();
+ var search = dt.rows({search: 'applied', selected: true}).count();
+ var available = dt.rows({search: 'applied'}).count();
+
+ if (search && search <= count && search === available) {
+ input
+ .prop('checked', true)
+ .prop('indeterminate', false);
+ }
+ else if (search === 0 && count === 0) {
+ input
+ .prop('checked', false)
+ .prop('indeterminate', false);
+ }
+ else {
+ input
+ .prop('checked', false)
+ .prop('indeterminate', true);
+ }
+ }
+ });
+ }
+ });
+}
+
+/**
+ * Initialisation of a new table. Attach event handlers and callbacks to allow
+ * Select to operate correctly.
+ *
+ * This will occur _after_ the initial DataTables initialisation, although
+ * before Ajax data is rendered, if there is ajax data
+ *
+ * @param {DataTable.settings} ctx Settings object to operate on
+ * @private
+ */
+function init(ctx) {
+ var api = new DataTable.Api(ctx);
+ ctx._select_init = true;
+
+ // Row callback so that classes can be added to rows and cells if the item
+ // was selected before the element was created. This will happen with the
+ // `deferRender` option enabled.
+ //
+ // This method of attaching to `aoRowCreatedCallback` is a hack until
+ // DataTables has proper events for row manipulation If you are reviewing
+ // this code to create your own plug-ins, please do not do this!
+ ctx.aoRowCreatedCallback.push(function (row, data, index) {
+ var i, ien;
+ var d = ctx.aoData[index];
+
+ // Row
+ if (d._select_selected) {
+ $(row).addClass(ctx._select.className);
+ }
+
+ // Cells and columns - if separated out, we would need to do two
+ // loops, so it makes sense to combine them into a single one
+ for (i = 0, ien = ctx.aoColumns.length; i < ien; i++) {
+ if (
+ ctx.aoColumns[i]._select_selected ||
+ (d._selected_cells && d._selected_cells[i])
+ ) {
+ $(d.anCells[i]).addClass(ctx._select.className);
+ }
+ }
+ }
+ );
+
+ // On Ajax reload we want to reselect all rows which are currently selected,
+ // if there is an rowId (i.e. a unique value to identify each row with)
+ api.on('preXhr.dt.dtSelect', function (e, settings) {
+ if (settings !== api.settings()[0]) {
+ // Not triggered by our DataTable!
+ return;
+ }
+
+ // note that column selection doesn't need to be cached and then
+ // reselected, as they are already selected
+ var rows = api
+ .rows({ selected: true })
+ .ids(true)
+ .filter(function (d) {
+ return d !== undefined;
+ });
+
+ var cells = api
+ .cells({ selected: true })
+ .eq(0)
+ .map(function (cellIdx) {
+ var id = api.row(cellIdx.row).id(true);
+ return id ? { row: id, column: cellIdx.column } : undefined;
+ })
+ .filter(function (d) {
+ return d !== undefined;
+ });
+
+ // On the next draw, reselect the currently selected items
+ api.one('draw.dt.dtSelect', function () {
+ api.rows(rows).select();
+
+ // `cells` is not a cell index selector, so it needs a loop
+ if (cells.any()) {
+ cells.each(function (id) {
+ api.cells(id.row, id.column).select();
+ });
+ }
+ });
+ });
+
+ // Update the table information element with selected item summary
+ api.on('info.dt', function (e, ctx, node) {
+ // Store the info node for updating on select / deselect
+ if (!ctx._select.infoEls.includes(node)) {
+ ctx._select.infoEls.push(node);
+ }
+
+ info(api, node);
+ });
+
+ api.on('select.dtSelect.dt deselect.dtSelect.dt', function () {
+ ctx._select.infoEls.forEach(function (el) {
+ info(api, el);
+ });
+
+ api.state.save();
+ });
+
+ // Clean up and release
+ api.on('destroy.dtSelect', function () {
+ // Remove class directly rather than calling deselect - which would trigger events
+ $(api.rows({ selected: true }).nodes()).removeClass(api.settings()[0]._select.className);
+
+ disableMouseSelection(api);
+ api.off('.dtSelect');
+ $('body').off('.dtSelect' + _safeId(api.table().node()));
+ });
+}
+
+/**
+ * Add one or more items (rows or columns) to the selection when shift clicking
+ * in OS selection style
+ *
+ * @param {DataTable.Api} dt DataTable
+ * @param {string} type Row or column range selector
+ * @param {object} idx Item index to select to
+ * @param {object} last Item index to select from
+ * @private
+ */
+function rowColumnRange(dt, type, idx, last) {
+ // Add a range of rows from the last selected row to this one
+ var indexes = dt[type + 's']({ search: 'applied' }).indexes();
+ var idx1 = indexes.indexOf(last);
+ var idx2 = indexes.indexOf(idx);
+
+ if (!dt[type + 's']({ selected: true }).any() && idx1 === -1) {
+ // select from top to here - slightly odd, but both Windows and Mac OS
+ // do this
+ indexes.splice(indexes.indexOf(idx) + 1, indexes.length);
+ }
+ else {
+ // reverse so we can shift click 'up' as well as down
+ if (idx1 > idx2) {
+ var tmp = idx2;
+ idx2 = idx1;
+ idx1 = tmp;
+ }
+
+ indexes.splice(idx2 + 1, indexes.length);
+ indexes.splice(0, idx1);
+ }
+
+ if (!dt[type](idx, { selected: true }).any()) {
+ // Select range
+ dt[type + 's'](indexes).select();
+ }
+ else {
+ // Deselect range - need to keep the clicked on row selected
+ indexes.splice(indexes.indexOf(idx), 1);
+ dt[type + 's'](indexes).deselect();
+ }
+}
+
+/**
+ * Clear all selected items
+ *
+ * @param {DataTable.settings} ctx Settings object of the host DataTable
+ * @param {boolean} [force=false] Force the de-selection to happen, regardless
+ * of selection style
+ * @private
+ */
+function clear(ctx, force) {
+ if (force || ctx._select.style === 'single') {
+ var api = new DataTable.Api(ctx);
+
+ api.rows({ selected: true }).deselect();
+ api.columns({ selected: true }).deselect();
+ api.cells({ selected: true }).deselect();
+ }
+}
+
+/**
+ * Select items based on the current configuration for style and items.
+ *
+ * @param {object} e Mouse event object
+ * @param {DataTables.Api} dt DataTable
+ * @param {DataTable.settings} ctx Settings object of the host DataTable
+ * @param {string} type Items to select
+ * @param {int|object} idx Index of the item to select
+ * @private
+ */
+function typeSelect(e, dt, ctx, type, idx) {
+ var style = dt.select.style();
+ var toggleable = dt.select.toggleable();
+ var isSelected = dt[type](idx, { selected: true }).any();
+
+ if (isSelected && !toggleable) {
+ return;
+ }
+
+ if (style === 'os') {
+ if (e.ctrlKey || e.metaKey) {
+ // Add or remove from the selection
+ dt[type](idx).select(!isSelected);
+ }
+ else if (e.shiftKey) {
+ if (type === 'cell') {
+ cellRange(dt, idx, ctx._select_lastCell || null);
+ }
+ else {
+ rowColumnRange(
+ dt,
+ type,
+ idx,
+ ctx._select_lastCell ? ctx._select_lastCell[type] : null
+ );
+ }
+ }
+ else {
+ // No cmd or shift click - deselect if selected, or select
+ // this row only
+ var selected = dt[type + 's']({ selected: true });
+
+ if (isSelected && selected.flatten().length === 1) {
+ dt[type](idx).deselect();
+ }
+ else {
+ selected.deselect();
+ dt[type](idx).select();
+ }
+ }
+ }
+ else if (style == 'multi+shift') {
+ if (e.shiftKey) {
+ if (type === 'cell') {
+ cellRange(dt, idx, ctx._select_lastCell || null);
+ }
+ else {
+ rowColumnRange(
+ dt,
+ type,
+ idx,
+ ctx._select_lastCell ? ctx._select_lastCell[type] : null
+ );
+ }
+ }
+ else {
+ dt[type](idx).select(!isSelected);
+ }
+ }
+ else {
+ dt[type](idx).select(!isSelected);
+ }
+}
+
+function _safeId(node) {
+ return node.id.replace(/[^a-zA-Z0-9\-\_]/g, '-');
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * DataTables selectors
+ */
+
+// row and column are basically identical just assigned to different properties
+// and checking a different array, so we can dynamically create the functions to
+// reduce the code size
+$.each(
+ [
+ { type: 'row', prop: 'aoData' },
+ { type: 'column', prop: 'aoColumns' }
+ ],
+ function (i, o) {
+ DataTable.ext.selector[o.type].push(function (settings, opts, indexes) {
+ var selected = opts.selected;
+ var data;
+ var out = [];
+
+ if (selected !== true && selected !== false) {
+ return indexes;
+ }
+
+ for (var i = 0, ien = indexes.length; i < ien; i++) {
+ data = settings[o.prop][indexes[i]];
+
+ if (
+ data && (
+ (selected === true && data._select_selected === true) ||
+ (selected === false && !data._select_selected)
+ )
+ ) {
+ out.push(indexes[i]);
+ }
+ }
+
+ return out;
+ });
+ }
+);
+
+DataTable.ext.selector.cell.push(function (settings, opts, cells) {
+ var selected = opts.selected;
+ var rowData;
+ var out = [];
+
+ if (selected === undefined) {
+ return cells;
+ }
+
+ for (var i = 0, ien = cells.length; i < ien; i++) {
+ rowData = settings.aoData[cells[i].row];
+
+ if (
+ rowData && (
+ (selected === true &&
+ rowData._selected_cells &&
+ rowData._selected_cells[cells[i].column] === true) ||
+ (selected === false &&
+ (!rowData._selected_cells || !rowData._selected_cells[cells[i].column]))
+ )
+ ) {
+ out.push(cells[i]);
+ }
+ }
+
+ return out;
+});
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * DataTables API
+ *
+ * For complete documentation, please refer to the docs/api directory or the
+ * DataTables site
+ */
+
+// Local variables to improve compression
+var apiRegister = DataTable.Api.register;
+var apiRegisterPlural = DataTable.Api.registerPlural;
+
+apiRegister('select()', function () {
+ return this.iterator('table', function (ctx) {
+ DataTable.select.init(new DataTable.Api(ctx));
+ });
+});
+
+apiRegister('select.blurable()', function (flag) {
+ if (flag === undefined) {
+ return this.context[0]._select.blurable;
+ }
+
+ return this.iterator('table', function (ctx) {
+ ctx._select.blurable = flag;
+ });
+});
+
+apiRegister('select.toggleable()', function (flag) {
+ if (flag === undefined) {
+ return this.context[0]._select.toggleable;
+ }
+
+ return this.iterator('table', function (ctx) {
+ ctx._select.toggleable = flag;
+ });
+});
+
+apiRegister('select.info()', function (flag) {
+ if (flag === undefined) {
+ return this.context[0]._select.info;
+ }
+
+ return this.iterator('table', function (ctx) {
+ ctx._select.info = flag;
+ });
+});
+
+apiRegister('select.items()', function (items) {
+ if (items === undefined) {
+ return this.context[0]._select.items;
+ }
+
+ return this.iterator('table', function (ctx) {
+ ctx._select.items = items;
+
+ eventTrigger(new DataTable.Api(ctx), 'selectItems', [items]);
+ });
+});
+
+// Takes effect from the _next_ selection. None disables future selection, but
+// does not clear the current selection. Use the `deselect` methods for that
+apiRegister('select.style()', function (style) {
+ if (style === undefined) {
+ return this.context[0]._select.style;
+ }
+
+ return this.iterator('table', function (ctx) {
+ if (!ctx._select) {
+ DataTable.select.init(new DataTable.Api(ctx));
+ }
+
+ if (!ctx._select_init) {
+ init(ctx);
+ }
+
+ ctx._select.style = style;
+
+ // Add / remove mouse event handlers. They aren't required when only
+ // API selection is available
+ var dt = new DataTable.Api(ctx);
+ disableMouseSelection(dt);
+
+ if (style !== 'api') {
+ enableMouseSelection(dt);
+ }
+
+ eventTrigger(new DataTable.Api(ctx), 'selectStyle', [style]);
+ });
+});
+
+apiRegister('select.selector()', function (selector) {
+ if (selector === undefined) {
+ return this.context[0]._select.selector;
+ }
+
+ return this.iterator('table', function (ctx) {
+ disableMouseSelection(new DataTable.Api(ctx));
+
+ ctx._select.selector = selector;
+
+ if (ctx._select.style !== 'api') {
+ enableMouseSelection(new DataTable.Api(ctx));
+ }
+ });
+});
+
+apiRegister('select.last()', function (set) {
+ let ctx = this.context[0];
+
+ if (set) {
+ ctx._select_lastCell = set;
+ return this;
+ }
+
+ return ctx._select_lastCell;
+});
+
+apiRegisterPlural('rows().select()', 'row().select()', function (select) {
+ var api = this;
+
+ if (select === false) {
+ return this.deselect();
+ }
+
+ this.iterator('row', function (ctx, idx) {
+ clear(ctx);
+
+ // There is a good amount of knowledge of DataTables internals in
+ // this function. It _could_ be done without that, but it would hurt
+ // performance (or DT would need new APIs for this work)
+ var dtData = ctx.aoData[idx];
+ var dtColumns = ctx.aoColumns;
+
+ $(dtData.nTr).addClass(ctx._select.className);
+ dtData._select_selected = true;
+
+ for (var i=0 ; i 0);
+ });
+
+ this.disable();
+ },
+ destroy: function (dt, node, config) {
+ dt.off(config._eventNamespace);
+ }
+ },
+ showSelected: {
+ text: i18n('showSelected', 'Show only selected'),
+ className: 'buttons-show-selected',
+ action: function (e, dt) {
+ if (dt.search.fixed('dt-select')) {
+ // Remove existing function
+ dt.search.fixed('dt-select', null);
+
+ this.active(false);
+ }
+ else {
+ // Use a fixed filtering function to match on selected rows
+ // This needs to reference the internal aoData since that is
+ // where Select stores its reference for the selected state
+ var dataSrc = dt.settings()[0].aoData;
+
+ dt.search.fixed('dt-select', function (text, data, idx) {
+ // _select_selected is set by Select on the data object for the row
+ return dataSrc[idx]._select_selected;
+ });
+
+ this.active(true);
+ }
+
+ dt.draw();
+ }
+ }
+});
+
+$.each(['Row', 'Column', 'Cell'], function (i, item) {
+ var lc = item.toLowerCase();
+
+ DataTable.ext.buttons['select' + item + 's'] = {
+ text: i18n('select' + item + 's', 'Select ' + lc + 's'),
+ className: 'buttons-select-' + lc + 's',
+ action: function () {
+ this.select.items(lc);
+ },
+ init: function (dt) {
+ var that = this;
+
+ dt.on('selectItems.dt.DT', function (e, ctx, items) {
+ that.active(items === lc);
+ });
+ }
+ };
+});
+
+DataTable.type('select-checkbox', {
+ className: 'dt-select',
+ detect: function (data) {
+ // Rendering function will tell us if it is a checkbox type
+ return data === 'select-checkbox' ? data : false;
+ },
+ order: {
+ pre: function (d) {
+ return d === 'X' ? -1 : 0;
+ }
+ }
+});
+
+$.extend(true, DataTable.defaults.oLanguage, {
+ select: {
+ aria: {
+ rowCheckbox: 'Select row'
+ }
+ }
+});
+
+DataTable.render.select = function (valueProp, nameProp) {
+ var valueFn = valueProp ? DataTable.util.get(valueProp) : null;
+ var nameFn = nameProp ? DataTable.util.get(nameProp) : null;
+
+ return function (data, type, row, meta) {
+ var dtRow = meta.settings.aoData[meta.row];
+ var selected = dtRow._select_selected;
+ var ariaLabel = meta.settings.oLanguage.select.aria.rowCheckbox;
+
+ if (type === 'display') {
+ return $('')
+ .attr({
+ 'aria-label': ariaLabel,
+ class: 'dt-select-checkbox',
+ name: nameFn ? nameFn(row) : null,
+ type: 'checkbox',
+ value: valueFn ? valueFn(row) : null,
+ checked: selected
+ })[0];
+ }
+ else if (type === 'type') {
+ return 'select-checkbox';
+ }
+ else if (type === 'filter') {
+ return '';
+ }
+
+ return selected ? 'X' : '';
+ }
+}
+
+// Legacy checkbox ordering
+DataTable.ext.order['select-checkbox'] = function (settings, col) {
+ return this.api()
+ .column(col, { order: 'index' })
+ .nodes()
+ .map(function (td) {
+ if (settings._select.items === 'row') {
+ return $(td).parent().hasClass(settings._select.className);
+ }
+ else if (settings._select.items === 'cell') {
+ return $(td).hasClass(settings._select.className);
+ }
+ return false;
+ });
+};
+
+$.fn.DataTable.select = DataTable.select;
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Initialisation
+ */
+
+// DataTables creation - check if select has been defined in the options. Note
+// this required that the table be in the document! If it isn't then something
+// needs to trigger this method unfortunately. The next major release of
+// DataTables will rework the events and address this.
+$(document).on('preInit.dt.dtSelect', function (e, ctx) {
+ if (e.namespace !== 'dt') {
+ return;
+ }
+
+ DataTable.select.init(new DataTable.Api(ctx));
+});
+
+
+export default DataTable;
diff --git a/assets/js/lib/datatables.js b/assets/js/lib/datatables.js
index 8c76d89d..8e39548b 100644
--- a/assets/js/lib/datatables.js
+++ b/assets/js/lib/datatables.js
@@ -98,6 +98,15 @@
dtOpts = config.options(dtOpts);
}
+ //Choose the column where the className contains "select-column" and apply the select extension to its render field
+ //Added for Part-DB
+ for (let column of dtOpts.columns) {
+ if (column.className && column.className.includes('dt-select')) {
+ column.render = $.fn.dataTable.render.select();
+ }
+ }
+
+
root.html(data.template);
dt = $('table', root).DataTable(dtOpts);
if (config.state !== 'none') {
diff --git a/src/DataTables/Column/SelectColumn.php b/src/DataTables/Column/SelectColumn.php
index 64bd1ebc..39445ac8 100644
--- a/src/DataTables/Column/SelectColumn.php
+++ b/src/DataTables/Column/SelectColumn.php
@@ -38,7 +38,7 @@ class SelectColumn extends AbstractColumn
'label' => '',
'orderable' => false,
'searchable' => false,
- 'className' => 'select-checkbox no-colvis',
+ 'className' => 'dt-select no-colvis',
'visible' => true,
]);