/*! Select for DataTables 2.0.3 * © 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.classes = { checkbox: 'dt-select-checkbox' }; DataTable.select.version = '2.0.3'; 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 || headerCheckbox === 'select-page' || headerCheckbox === 'select-all') { initCheckboxHeader(dt, headerCheckbox); dt.on('init', function () { initCheckboxHeader(dt, headerCheckbox); }); } }; /* 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(); } } /** * Get the class * @returns */ function checkboxClass(selector) { var name = DataTable.select.classes.checkbox; return selector ? name.replace(/ /g, '.') : name; } /** * 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 * @param {*} headerCheckbox the header checkbox option */ function initCheckboxHeader( dt, headerCheckbox ) { // 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: checkboxClass(true), type: 'checkbox', 'aria-label': dt.i18n('select.aria.headerCheckbox') || 'Select all rows' }) .appendTo(header) .on('change', function () { if (this.checked) { if (headerCheckbox == 'select-page') { dt.rows({page: 'current'}).select() } else { 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 = headerCheckbox == 'select-page' ? dt.rows({page: 'current'}).count() : 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) .find('input.' + checkboxClass(true)).prop('checked', true); } // 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: checkboxClass(), name: nameFn ? nameFn(row) : null, type: 'checkbox', value: valueFn ? valueFn(row) : null, checked: selected }) .on('input', function (e) { // Let Select 100% control the state of the checkbox e.preventDefault(); // And make sure this checkbox matches it's row as it is possible // to check out of sync if this was clicked on to deselect a range // but remains selected itself this.checked = $(this).closest('tr').hasClass('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;