datatables.net-columncontrol
Version:
ColumnControl for DataTables
1,480 lines (1,457 loc) • 92.1 kB
JavaScript
/*! ColumnControl 1.0.5
* Copyright (c) SpryMedia Ltd - datatables.net/license
*
* SVG icons: ISC License
* Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT).
* All other copyright (c) for Lucide are held by Lucide Contributors 2022.
*/
import jQuery from 'jquery';
import DataTable from 'datatables.net';
// Allow reassignment of the $ variable
let $ = jQuery;
function createElement(type, classes, text, children) {
if (classes === void 0) { classes = []; }
if (text === void 0) { text = null; }
if (children === void 0) { children = []; }
var el = document.createElement(type);
addClass(el, classes);
if (text) {
el.innerHTML = text;
}
children.forEach(function (child) {
el.appendChild(child);
});
return el;
}
function addClass(el, classes) {
if (!classes) {
return;
}
if (!Array.isArray(classes)) {
classes = [classes];
}
classes.forEach(function (className) {
if (el && className) {
el.classList.add(className);
}
});
}
// The SVG for many of these icons are from Lucide ( https://lucide.dev ), which are available
// under the ISC License. There are a number of custom icons as well. These are optimised through
// https://optimize.svgomg.net/
function wrap(paths) {
return ('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
paths +
'</svg>');
}
var icons = {
chevronRight: wrap('<path d="m9 18 6-6-6-6"/>'),
// columns-3
columns: wrap('<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M15 3v18"/>'),
// Custom
contains: wrap('<path d="M10 3h4v18h-4z"/><path d="M18 8h3v9h-3"/><path d="M6 17H3V8h3"/>'),
empty: wrap('<circle cx="12" cy="12" r="10"/>'),
ends: wrap('<path d="M21 3h-4v18h4z"/><path d="M13 8H3v9h10"/>'),
// Customised
equal: wrap('<line x1="5" x2="19" y1="9" y2="9"/><line x1="5" x2="19" y1="15" y2="15"/>'),
greater: wrap('<path d="m9 18 6-6-6-6"/>'),
// Custom
greaterOrEqual: wrap('<path d="m9 16 6-6-6-6"/><path d="m9 21 6-6"/>'),
less: wrap('<path d="m15 18-6-6 6-6"/>'),
// Custom
lessOrEqual: wrap('<path d="m15 16-6-6 6-6"/><path d="m15 21-6-6"/>'),
menu: wrap('<line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/>'),
// move-horizontal
move: wrap('<line x1="12" x2="12" y1="3" y2="21"/><polyline points="8 8 4 12 8 16"/><polyline points="16 16 20 12 16 8"/>'),
// arrow-left-from-line
moveLeft: wrap('<path d="m9 6-6 6 6 6"/><path d="M3 12h14"/><path d="M21 19V5"/>'),
// arrow-right-from-line
moveRight: wrap('<path d="M3 5v14"/><path d="M21 12H7"/><path d="m15 18 6-6-6-6"/>'),
// Custom
notContains: wrap('<path d="M15 4 9 20"/><path d="M3 8h18v9H3z"/>'),
notEmpty: wrap('<circle cx="12" cy="12" r="10"/><line x1="9" x2="15" y1="15" y2="9"/>'),
notEqual: wrap('<path d="M5 9h14"/><path d="M5 15h14"/><path d="M15 5 9 19"/>'),
// Custom
orderAddAsc: wrap('<path d="M17 21v-8"/><path d="M3 4h6"/><path d="M3 8h9"/><path d="M3 12h10"/><path d="M13 17h8"/>'),
// Custom
orderAddDesc: wrap('<path d="M17 21v-8"/><path d="M3 4h12"/><path d="M3 8h9"/><path d="M3 12h6"/><path d="M13 17h8"/>'),
orderAsc: wrap('<path d="m3 8 4-4 4 4"/><path d="M7 4v16"/><path d="M11 12h4"/><path d="M11 16h7"/><path d="M11 20h10"/>'),
// Custom
orderClear: wrap('<path d="m21 21-8-8"/><path d="M3 4h12"/><path d="M3 8h9"/><path d="M3 12h6"/><path d="m13 21 8-8"/>'),
orderDesc: wrap('<path d="m3 16 4 4 4-4"/><path d="M7 20V4"/><path d="M11 4h10"/><path d="M11 8h7"/><path d="M11 12h4"/>'),
// Custom
orderRemove: wrap('<path d="M3 4h12"/><path d="M3 8h9"/><path d="M3 12h6"/><path d="M13 17h8"/>'),
// Custom
orderNone: wrap('<path d="m3 8 4-4 4 4"/><path d="m11 16-4 4-4-4"/><path d="M7 4v16"/><path d="M15 8h6"/><path d="M15 16h6"/><path d="M13 12h8"/>'),
// search
search: wrap('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>'),
// search-x
searchClear: wrap('<path d="m13.5 8.5-5 5"/><path d="m8.5 8.5 5 5"/><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>'),
// Custom
starts: wrap('<path d="M3 3h4v18H3z"/><path d="M11 8h10v9H11"/>'),
// tick
tick: wrap('<path d="M20 6 9 17l-5-5"/>'),
// x
x: wrap('<path d="M18 6 6 18"/><path d="m6 6 12 12"/>')
};
/**
* Close all or only other dropdowns
*
* @param e Event or null to close all others
*/
function close(e) {
if (e === void 0) { e = null; }
document.querySelectorAll('div.dtcc-dropdown').forEach(function (el) {
if (e === null || !el.contains(e.target)) {
el._close();
if (!e._closed) {
e._closed = [];
}
e._closed.push(el);
}
});
}
function getContainer(dt, btn) {
return btn.closest('div.dtfh-floatingparent') || dt.table().container();
}
/**
* Position the dropdown relative to the button that activated it, with possible corrections
* to make sure it is visible on the page.
*
* @param dropdown Dropdown element
* @param dt Container DataTable
* @param btn Button the dropdown emanates from
*/
function positionDropdown(dropdown, dt, btn) {
var header = btn.closest('div.dt-column-header');
var container = getContainer(dt, btn);
var headerStyle = getComputedStyle(header);
var dropdownWidth = dropdown.offsetWidth;
var position = relativePosition(container, btn);
var left, top;
top = position.top + btn.offsetHeight;
if (headerStyle.flexDirection === 'row-reverse') {
// Icon is on the left of the header - align the left hand sides
left = position.left;
}
else {
// Icon is on the right of the header - align the right hand sides
left = position.left - dropdownWidth + btn.offsetWidth;
}
// Corrections - don't extend past the DataTable to the left and right
var containerWidth = container.offsetWidth;
if (left + dropdownWidth > containerWidth) {
left -= left + dropdownWidth - containerWidth;
}
if (left < 0) {
left = 0;
}
dropdown.style.top = top + 'px';
dropdown.style.left = left + 'px';
}
/**
* Display the dropdown in the document
*
* @param dropdown Dropdown element
* @param dt Container DataTable
* @param btn Button the dropdown emanates from
* @returns Function to call when the dropdown should be removed from the document
*/
function attachDropdown(dropdown, dt, btn) {
var dtContainer = getContainer(dt, btn.element());
dropdown._shown = true;
dtContainer.append(dropdown);
positionDropdown(dropdown, dt, btn.element());
// Note that this could be called when the dropdown has already been removed from the document
// via another dropdown being shown. This will clean up the event on the next body click.
var removeDropdown = function (e) {
// Not in document, so just clean up the event handler
if (!dropdown._shown) {
document.body.removeEventListener('click', removeDropdown);
return;
}
// If the click is inside the dropdown, ignore it - we don't want to immediately close
if (e.target === dropdown || dropdown.contains(e.target)) {
return;
}
dropdown._close();
document.body.removeEventListener('click', removeDropdown);
};
document.body.addEventListener('click', removeDropdown);
return removeDropdown;
}
/**
* Get the position of an element, relative to a given parent. The origin MUST be under the
* parent's tree.
*
* @param parent Parent element to get position relative to
* @param origin Target element
*/
function relativePosition(parent, origin) {
var top = 0;
var left = 0;
while (origin && origin !== parent && origin !== document.body) {
top += origin.offsetTop;
left += origin.offsetLeft;
if (origin.scrollTop) {
left -= origin.scrollTop;
}
if (origin.scrollLeft) {
left -= origin.scrollLeft;
}
origin = origin.offsetParent;
}
return {
top: top,
left: left
};
}
/**
* Function that will provide the keyboard navigation for the dropdown
*
* @param dropdown Dropdown element in question
* @returns Function that can be bound to `keypress`
*/
function focusCapture(dropdown, host) {
return function (e) {
// Do nothing if not shown
if (!dropdown._shown) {
return;
}
// Focus trap for tab key
var elements = Array.from(dropdown.querySelectorAll('a, button, input, select'));
var active = document.activeElement;
// An escape key should close the dropdown
if (e.key === 'Escape') {
dropdown._close();
host.focus(); // Restore focus to the host
return;
}
else if (e.key !== 'Tab' || elements.length === 0) {
// Anything other than tab we aren't interested in from here
return;
}
if (!elements.includes(active)) {
// If new focus is not inside the popover we want to drag it back in
elements[0].focus();
e.preventDefault();
}
else if (e.shiftKey) {
// Reverse tabbing order when shift key is pressed
if (active === elements[0]) {
elements[elements.length - 1].focus();
e.preventDefault();
}
}
else {
if (active === elements[elements.length - 1]) {
elements[0].focus();
e.preventDefault();
}
}
};
}
var dropdownContent = {
classes: {
container: 'dtcc-dropdown',
liner: 'dtcc-dropdown-liner'
},
defaults: {
className: 'dropdown',
content: [],
icon: 'menu',
text: 'More...'
},
init: function (config) {
var dt = this.dt();
var dropdown = createElement('div', dropdownContent.classes.container, '', [
createElement('div', dropdownContent.classes.liner)
]);
dropdown._shown = false;
dropdown._close = function () {
dropdown.remove();
dropdown._shown = false;
};
dropdown.setAttribute('role', 'dialog');
dropdown.setAttribute('aria-label', dt.i18n('columnControl.dropdown', config.text));
// When FixedHeader is used, the transition between states messes up positioning, so if
// shown we just reattach the dropdown.
dt.on('fixedheader-mode', function () {
if (dropdown._shown) {
attachDropdown(dropdown, dt, config._parents ? config._parents[0] : btn);
}
});
// A liner element allows more styling options, so the contents go inside this
var liner = dropdown.childNodes[0];
var btn = new Button(dt)
.text(dt.i18n('columnControl.dropdown', config.text))
.icon(config.icon)
.className(config.className)
.dropdownDisplay(liner)
.handler(function (e) {
// Do nothing if our dropdown was just closed as part of the event (i.e. allow
// the button to toggle it closed)
if (e._closed && e._closed.includes(dropdown)) {
return;
}
attachDropdown(dropdown, dt, config._parents ? config._parents[0] : btn);
// When activated using a key - auto focus on the first item in the popover
var focusable = dropdown.querySelector('input, a, button');
console.log(e.type, e);
if (focusable && e.type === 'keypress') {
focusable.focus();
}
});
btn.element().setAttribute('aria-haspopup', 'dialog');
// Add the content for the dropdown
for (var i = 0; i < config.content.length; i++) {
var content = this.resolve(config.content[i]);
// For nested items we need to keep a reference to the top level so the sub-levels
// can communicate back - e.g. active or positioned relative to that top level.
if (!content.config._parents) {
content.config._parents = [];
}
content.config._parents.push(btn);
var el = content.plugin.init.call(this, content.config);
liner.appendChild(el);
}
// For nested dropdowns, add an extra icon element to show that it will dropdown further
if (config._parents && config._parents.length) {
btn.extra('chevronRight');
}
// Reposition if needed
dt.on('columns-reordered', function () {
positionDropdown(dropdown, dt, btn.element());
});
// Focus capture events
var capture = focusCapture(dropdown, btn.element());
document.body.addEventListener('keydown', capture);
dt.on('destroy', function () {
document.body.removeEventListener('keydown', capture);
});
return btn.element();
}
};
var _namespace = 0;
var Button = /** @class */ (function () {
/**
* Create a new button for use in ColumnControl contents. Buttons created by this class can be
* used at the top level in the header or in a dropdown.
*/
function Button(dt) {
this._s = {
active: false,
activeList: [],
buttonClick: null,
dt: null,
enabled: true,
label: '',
namespace: '',
value: null
};
this._s.dt = dt;
this._dom = {
button: createElement('button', Button.classes.container),
dropdownDisplay: null,
extra: createElement('span', 'dtcc-button-extra'),
icon: createElement('span', 'dtcc-button-icon'),
state: createElement('span', 'dtcc-button-state'),
text: createElement('span', 'dtcc-button-text')
};
this._dom.button.append(this._dom.icon);
this._dom.button.append(this._dom.text);
this._dom.button.append(this._dom.state);
this._dom.button.append(this._dom.extra);
// Default state is enabled
this.enable(true);
}
Button.prototype.active = function (active) {
if (active === undefined) {
return this._s.active;
}
this._s.active = active;
this._checkActive();
return this;
};
/**
* A button can be marked as active by any of its sub-buttons (i.e. if it is a dropdown)
* and each one needs to be able to enable this button without effecting the active state
* trigged by any other sub-buttons. This method provides a way to do that.
*
* @param unique Unique id for the activate state
* @param active If it is active
* @returns Button instance
*/
Button.prototype.activeList = function (unique, active) {
this._s.activeList[unique] = active;
this._checkActive();
return this;
};
/**
* Scan over the dropdown element looking for any visible content. If there isn't any then
* we hide this button.
*
* @returns Button instance
*/
Button.prototype.checkDisplay = function () {
var visible = 0;
var children = this._dom.dropdownDisplay.childNodes;
for (var i = 0; i < children.length; i++) {
// No need to getComputedStyle since if a button is hidden, it was done with JS writing
// to style.display, so we can check against that.
if (children[i].style.display !== 'none') {
visible++;
}
}
if (visible === 0) {
this._dom.button.style.display = 'none';
}
return this;
};
/**
* Set the class name for the button
*
* @param className Class name
* @returns Button instance
*/
Button.prototype.className = function (className) {
this._dom.button.classList.add('dtcc-button_' + className);
return this;
};
/**
* Destroy the button, cleaning up event listeners
*/
Button.prototype.destroy = function () {
if (this._s.buttonClick) {
this._dom.button.removeEventListener('click', this._s.buttonClick);
this._dom.button.removeEventListener('keypress', this._s.buttonClick);
}
if (this._s.namespace) {
this._s.dt.off('destroy.' + this._s.namespace);
}
};
/**
* Relevant for drop downs only. When a button in a dropdown is hidden, we might want to
* hide the host button as well (if it has nothing else to show). For that we need to know
* what the dropdown element is.
*
* @param el Element that can be used for telling us about drop down elements.
* @returns Button instance
*/
Button.prototype.dropdownDisplay = function (el) {
this._dom.dropdownDisplay = el;
return this;
};
/**
* Get the DOM Button element to attach into the document
*
* @returns The Button element
*/
Button.prototype.element = function () {
return this._dom.button;
};
/**
* Set if the button should be enabled or not.
*
* @param enable Toggle the enable state
* @returns Button instance
*/
Button.prototype.enable = function (enable) {
this._dom.button.classList.toggle('dtcc-button_disabled', !enable);
this._s.enabled = enable;
return this;
};
/**
* Set the extra information icon
*
* @param icon Icon name
* @returns Button instance
*/
Button.prototype.extra = function (icon) {
this._dom.extra.innerHTML = icon ? icons[icon] : '';
return this;
};
/**
* Set the event handler for when the button is activated
*
* @param fn Event handler
* @returns Button instance
*/
Button.prototype.handler = function (fn) {
var _this = this;
var buttonClick = function (e) {
// Close any dropdowns which are already open
close(e);
// Stop bubbling to the DataTables default header, which might still be enabled
e.stopPropagation();
e.preventDefault();
if (_this._s.enabled) {
fn(e);
}
};
this._s.buttonClick = buttonClick;
this._s.namespace = 'dtcc-' + _namespace++;
this._dom.button.addEventListener('click', buttonClick);
this._dom.button.addEventListener('keypress', buttonClick);
// Use a unique namespace to be able to easily remove per button
this._s.dt.on('destroy.' + this._s.namespace, function () {
_this.destroy();
});
return this;
};
/**
* Set the icon to display in the button
*
* @param icon Icon name
* @returns Button instance
*/
Button.prototype.icon = function (icon) {
this._dom.icon.innerHTML = icon ? icons[icon] : '';
return this;
};
Button.prototype.text = function (text) {
if (text === undefined) {
return this._s.label;
}
this._dom.text.innerHTML = text;
this._s.label = text; // for fast retrieval
this._dom.button.setAttribute('aria-label', text);
return this;
};
Button.prototype.value = function (val) {
if (val === undefined) {
return this._s.value;
}
this._s.value = val;
return this;
};
/**
* Check if anything is making this button active
*
* @returns Self for chaining
*/
Button.prototype._checkActive = function () {
if (this._s.active === true || Object.values(this._s.activeList).includes(true)) {
this._dom.state.innerHTML = icons.tick;
this._dom.button.classList.add('dtcc-button_active');
}
else {
this._dom.state.innerHTML = '';
this._dom.button.classList.remove('dtcc-button_active');
}
return this;
};
Button.classes = {
container: 'dtcc-button'
};
return Button;
}());
var CheckList = /** @class */ (function () {
/**
* Container for a list of buttons
*/
function CheckList(dt, opts) {
var _this = this;
this._s = {
buttons: [],
dt: null,
handler: function () { },
search: ''
};
this._s.dt = dt;
this._dom = {
buttons: createElement('div', 'dtcc-list-buttons'),
container: createElement('div', CheckList.classes.container),
controls: createElement('div', 'dtcc-list-controls'),
title: createElement('div', 'dtcc-list-title'),
selectAll: createElement('button', 'dtcc-list-selectAll', dt.i18n('columnControl.list.all', 'Select all')),
selectAllCount: createElement('span'),
selectNone: createElement('button', 'dtcc-list-selectNone', dt.i18n('columnControl.list.none', 'Deselect')),
selectNoneCount: createElement('span'),
search: createElement('input', CheckList.classes.input)
};
var dom = this._dom;
dom.search.setAttribute('type', 'text');
dom.container.append(dom.title);
dom.container.append(dom.controls);
dom.container.append(dom.buttons);
if (opts.select) {
dom.controls.append(dom.selectAll);
dom.controls.append(dom.selectNone);
dom.selectAll.append(dom.selectAllCount);
dom.selectNone.append(dom.selectNoneCount);
}
// Events
var searchInput = function () {
_this._s.search = dom.search.value;
_this._redraw();
};
var selectAllClick = function (e) {
_this.selectAll();
_this._s.handler(e, null, _this._s.buttons, true);
_this._updateCount();
};
var selectNoneClick = function (e) {
_this.selectNone();
_this._s.handler(e, null, _this._s.buttons, true);
_this._updateCount();
};
if (opts.search) {
dom.controls.append(dom.search);
dom.search.setAttribute('placeholder', dt.i18n('columnControl.list.search', 'Search...'));
dom.search.addEventListener('input', searchInput);
}
dom.selectAll.addEventListener('click', selectAllClick);
dom.selectNone.addEventListener('click', selectNoneClick);
dt.on('destroy', function () {
dom.selectAll.removeEventListener('click', selectAllClick);
dom.selectNone.removeEventListener('click', selectNoneClick);
dom.search.removeEventListener('input', searchInput);
});
}
/**
* Add one or more buttons to the list
*
* @param options Configuration for the button(s) to add
* @returns Self for chaining
*/
CheckList.prototype.add = function (options, update) {
var _this = this;
if (!Array.isArray(options)) {
options = [options];
}
var _loop_1 = function (i) {
var option = options[i];
var btn = new Button(this_1._s.dt)
.active(option.active || false)
.handler(function (e) {
_this._s.handler(e, btn, _this._s.buttons, true);
_this._updateCount();
})
.icon(option.icon || '')
.text(option.label !== ''
? option.label
: this_1._s.dt.i18n('columnControl.list.emptyOption', 'Empty'))
.value(option.value);
if (option.label === '') {
btn.className('empty');
}
this_1._s.buttons.push(btn);
};
var this_1 = this;
for (var i = 0; i < options.length; i++) {
_loop_1(i);
}
var count = this._s.buttons.length;
if (update === true || update === undefined) {
this._dom.selectAllCount.innerHTML = count ? '(' + count + ')' : '';
this._redraw();
}
return this;
};
/**
* Find a button with a given value
*
* @param val Value to search for
* @returns Found button
*/
CheckList.prototype.button = function (val) {
var buttons = this._s.buttons;
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].value() === val) {
return buttons[i];
}
}
return null;
};
/**
* Remove all buttons from the list
*
* @returns Self for chaining
*/
CheckList.prototype.clear = function () {
// Clean up the buttons
for (var i = 0; i < this._s.buttons.length; i++) {
this._s.buttons[i].destroy();
}
// Then empty them out
this._dom.buttons.replaceChildren();
this._s.buttons.length = 0;
return this;
};
/**
* Get the DOM container element to attach into the document
*
* @returns Container
*/
CheckList.prototype.element = function () {
return this._dom.container;
};
/**
* Set the event handler for what happens when a button is clicked
*
* @param fn Event handler
*/
CheckList.prototype.handler = function (fn) {
this._s.handler = fn;
return this;
};
/**
* Indicate that this is a search control and should listen for corresponding events
*
* @param dt DataTable instance
* @param idx Column index
*/
CheckList.prototype.searchListener = function (dt, parent) {
var _this = this;
// Column control search clearing (column().ccSearchClear() method)
dt.on('cc-search-clear', function (e, colIdx) {
if (colIdx === parent.idx()) {
_this.selectNone();
_this._s.handler(e, null, _this._s.buttons, false);
_this._s.search = '';
_this._dom.search.value = '';
_this._redraw();
_this._updateCount();
}
});
return this;
};
/**
* Select all buttons
*
* @returns Self for chaining
*/
CheckList.prototype.selectAll = function () {
for (var i = 0; i < this._s.buttons.length; i++) {
this._s.buttons[i].active(true);
}
return this;
};
/**
* Deselect all buttons
*
* @returns Self for chaining
*/
CheckList.prototype.selectNone = function () {
for (var i = 0; i < this._s.buttons.length; i++) {
this._s.buttons[i].active(false);
}
return this;
};
/**
* Set the list's title
*
* @param title Display title
* @returns Button instance
*/
CheckList.prototype.title = function (title) {
this._dom.title.innerHTML = title;
return this;
};
CheckList.prototype.values = function (values) {
var i;
var result = [];
var buttons = this._s.buttons;
if (values !== undefined) {
for (i = 0; i < buttons.length; i++) {
if (values.includes(buttons[i].value())) {
buttons[i].active(true);
}
}
this._updateCount();
return this;
}
for (i = 0; i < buttons.length; i++) {
if (buttons[i].active()) {
result.push(buttons[i].value());
}
}
return result;
};
/**
* Update the deselect counter
*/
CheckList.prototype._updateCount = function () {
var count = this.values().length;
this._dom.selectNoneCount.innerHTML = count ? '(' + count + ')' : '';
};
/**
* Add the buttons to the page - taking into account filtering
*/
CheckList.prototype._redraw = function () {
var buttons = this._s.buttons;
var el = this._dom.buttons;
var searchTerm = this._s.search.toLowerCase();
el.replaceChildren();
for (var i = 0; i < buttons.length; i++) {
var btn = buttons[i];
if (!searchTerm ||
btn
.text()
.toLowerCase()
.includes(searchTerm)) {
el.appendChild(btn.element());
}
}
};
CheckList.classes = {
container: 'dtcc-list',
input: 'dtcc-list-search'
};
return CheckList;
}());
var colVis = {
defaults: {
className: 'colVis',
columns: '',
search: false,
select: false,
title: 'Column visibility'
},
init: function (config) {
var dt = this.dt();
var checkList = new CheckList(dt, {
search: config.search,
select: config.select
})
.title(dt.i18n('columnControl.colVis', config.title))
.handler(function (e, btn, buttons) {
if (btn) {
btn.active(!btn.active());
}
apply(buttons);
});
// Need to apply in a loop to allow for select all / select none
var apply = function (buttons) {
for (var i = 0; i < buttons.length; i++) {
var btn = buttons[i];
var idx = btn.value();
var col = dt.column(idx);
if (btn.active() && !col.visible()) {
col.visible(true);
}
else if (!btn.active() && col.visible()) {
col.visible(false);
}
}
};
var rebuild = function () {
var columns = dt.columns(config.columns);
columns.every(function () {
checkList.add({
active: this.visible(),
label: this.title(),
value: this.index()
});
});
};
rebuild();
dt.on('column-visibility', function (e, s, colIdx, state) {
var btn = checkList.button(colIdx);
if (btn) {
btn.active(state);
}
});
dt.on('columns-reordered', function (e, details) {
checkList.clear();
rebuild();
});
return checkList.element();
}
};
var colVisDropdown = {
defaults: {
className: 'colVis',
columns: '',
search: false,
select: false,
text: 'Column visibility',
title: 'Column visibility'
},
extend: function (config) {
var dt = this.dt();
return {
extend: 'dropdown',
icon: 'columns',
text: dt.i18n('columnControl.colVisDropdown', config.text),
content: [
Object.assign(config, {
extend: 'colVis'
})
]
};
}
};
var reorder = {
defaults: {
className: 'reorder',
icon: 'move',
text: 'Reorder columns'
},
init: function (config) {
var _this = this;
var dt = this.dt();
var btn = new Button(dt)
.text(dt.i18n('columnControl.reorder', config.text))
.icon(config.icon)
.className(config.className)
.handler(function () {
var idx = _this.idx();
if (idx > 0) {
dt.colReorder.move(idx, idx - 1);
}
});
if (this.idx() === 0) {
btn.enable(false);
}
dt.on('columns-reordered', function (e, details) {
btn.enable(_this.idx() > 0);
});
// If ColReorder wasn't initialised on this DataTable, then we need to add it
if (!dt.init().colReorder) {
new DataTable.ColReorder(dt, {});
}
return btn.element();
}
};
var reorderLeft = {
defaults: {
className: 'reorderLeft',
icon: 'moveLeft',
text: 'Move column left'
},
init: function (config) {
var _this = this;
var dt = this.dt();
var btn = new Button(dt)
.text(dt.i18n('columnControl.reorderLeft', config.text))
.icon(config.icon)
.className(config.className)
.handler(function () {
var idx = _this.idx();
// TODO account for visibility
if (idx > 0) {
dt.colReorder.move(idx, idx - 1);
}
});
if (this.idx() === 0) {
btn.enable(false);
}
dt.on('columns-reordered', function (e, details) {
btn.enable(_this.idx() > 0);
});
return btn.element();
}
};
var reorderRight = {
defaults: {
className: 'reorderRight',
icon: 'moveRight',
text: 'Move column right'
},
init: function (config) {
var _this = this;
var dt = this.dt();
var btn = new Button(dt)
.text(dt.i18n('columnControl.reorderRight', config.text))
.icon(config.icon)
.className(config.className)
.handler(function () {
var idx = _this.idx();
if (idx < dt.columns().count() - 1) {
dt.colReorder.move(idx, idx + 1);
}
});
if (this.idx() === dt.columns().count() - 1) {
btn.enable(false);
}
dt.on('columns-reordered', function (e, details) {
btn.enable(_this.idx() < dt.columns().count() - 1);
});
return btn.element();
}
};
var order = {
defaults: {
className: 'order',
iconAsc: 'orderAsc',
iconDesc: 'orderDesc',
iconNone: 'orderNone',
statusOnly: false,
text: 'Toggle ordering'
},
init: function (config) {
var _this = this;
var dt = this.dt();
var btn = new Button(dt)
.text(dt.i18n('columnControl.order', config.text))
.icon('orderAsc')
.className(config.className);
if (!config.statusOnly) {
dt.order.listener(btn.element(), DataTable.versionCheck('2.3.2') ? function () { return [_this.idx()]; } : this.idx(), function () { });
}
dt.on('order', function (e, s, order) {
var found = order.find(function (o) { return o.col === _this.idx(); });
if (!found) {
btn.active(false).icon(config.iconNone);
}
else if (found.dir === 'asc') {
btn.active(true).icon(config.iconAsc);
}
else if (found.dir === 'desc') {
btn.active(true).icon(config.iconDesc);
}
});
return btn.element();
}
};
var orderAddAsc = {
defaults: {
className: 'orderAddAsc',
icon: 'orderAddAsc',
text: 'Add Sort Ascending'
},
init: function (config) {
var _this = this;
var dt = this.dt();
var btn = new Button(dt)
.text(dt.i18n('columnControl.orderAddAsc', config.text))
.icon(config.icon)
.className(config.className)
.handler(function () {
var order = dt.order();
order.push([_this.idx(), 'asc']);
dt.draw();
});
dt.on('order', function (e, s, order) {
var found = order.some(function (o) { return o.col === _this.idx(); });
btn.enable(!found);
});
return btn.element();
}
};
var orderAddDesc = {
defaults: {
className: 'orderAddDesc',
icon: 'orderAddDesc',
text: 'Add Sort Descending'
},
init: function (config) {
var _this = this;
var dt = this.dt();
var btn = new Button(dt)
.text(dt.i18n('columnControl.orderAddDesc', config.text))
.icon(config.icon)
.className(config.className)
.handler(function () {
var order = dt.order();
order.push([_this.idx(), 'desc']);
dt.draw();
});
dt.on('order', function (e, s, order) {
var found = order.some(function (o) { return o.col === _this.idx(); });
btn.enable(!found);
});
return btn.element();
}
};
var orderAsc = {
defaults: {
className: 'orderAsc',
icon: 'orderAsc',
text: 'Sort Ascending'
},
init: function (config) {
var _this = this;
var dt = this.dt();
var btn = new Button(dt)
.text(dt.i18n('columnControl.orderAsc', config.text))
.icon(config.icon)
.className(config.className)
.handler(function () {
_this.dt()
.order([
{
idx: _this.idx(),
dir: 'asc'
}
])
.draw();
});
dt.on('order', function (e, s, order) {
var found = order.some(function (o) { return o.col === _this.idx() && o.dir === 'asc'; });
btn.active(found);
});
return btn.element();
}
};
var orderClear = {
defaults: {
className: 'orderClear',
icon: 'orderClear',
text: 'Clear sort'
},
init: function (config) {
var dt = this.dt();
var btn = new Button(dt)
.text(dt.i18n('columnControl.orderClear', config.text))
.icon(config.icon)
.className(config.className)
.handler(function () {
dt.order([]).draw();
});
dt.on('order', function (e, s, order) {
btn.enable(order.length > 0);
});
if (dt.order().length === 0) {
btn.enable(false);
}
return btn.element();
}
};
var orderDesc = {
defaults: {
className: 'orderDesc',
icon: 'orderDesc',
text: 'Sort Descending'
},
init: function (config) {
var _this = this;
var dt = this.dt();
var btn = new Button(dt)
.text(dt.i18n('columnControl.orderDesc', config.text))
.icon(config.icon)
.className(config.className)
.handler(function () {
_this.dt()
.order([
{
idx: _this.idx(),
dir: 'desc'
}
])
.draw();
});
dt.on('order', function (e, s, order) {
var found = order.some(function (o) { return o.col === _this.idx() && o.dir === 'desc'; });
btn.active(found);
});
return btn.element();
}
};
var orderRemove = {
defaults: {
className: 'orderRemove',
icon: 'orderRemove',
text: 'Remove from sort'
},
init: function (config) {
var _this = this;
var dt = this.dt();
var btn = new Button(dt)
.text(dt.i18n('columnControl.orderRemove', config.text))
.icon(config.icon)
.className(config.className)
.handler(function () {
// Remove the current column from the ordering array, then reorder the table
var order = dt.order();
var idx = order.findIndex(function (o) { return o[0] === _this.idx(); });
order.splice(idx, 1);
dt.order(order).draw();
});
dt.on('order', function (e, s, order) {
var found = order.some(function (o) { return o.col === _this.idx(); });
btn.enable(found);
});
btn.enable(false);
return btn.element();
}
};
var orderStatus = {
defaults: {
className: 'order',
iconAsc: 'orderAsc',
iconDesc: 'orderDesc',
iconNone: 'orderNone',
statusOnly: true,
text: 'Sort status'
},
extend: function (config) {
return Object.assign(config, { extend: 'order' });
}
};
var SearchInput = /** @class */ (function () {
/**
* Create a container element, for consistent DOM structure and styling
*/
function SearchInput(dt, idx) {
var _this = this;
this._type = 'text';
this._dt = dt;
this._idx = idx;
this._dom = {
clear: createElement('span', 'dtcc-search-clear', icons['x']),
container: createElement('div', SearchInput.classes.container),
typeIcon: createElement('div', 'dtcc-search-type-icon'),
searchIcon: createElement('div', 'dtcc-search-icon', icons['search']),
input: createElement('input', SearchInput.classes.input),
inputs: createElement('div'),
select: createElement('select', SearchInput.classes.select),
title: createElement('div', 'dtcc-search-title')
};
var dom = this._dom;
var originalIdx = idx;
dom.input.setAttribute('type', 'text');
dom.container.append(dom.title, dom.inputs);
dom.inputs.append(dom.typeIcon, dom.select, dom.searchIcon, dom.clear, dom.input);
// Listeners
var inputInput = function () {
_this.runSearch();
};
var selectInput = function () {
dom.typeIcon.innerHTML = icons[dom.select.value];
_this.runSearch();
};
var clearClick = function () {
_this.clear();
};
dom.input.addEventListener('input', inputInput);
dom.select.addEventListener('input', selectInput);
dom.clear.addEventListener('click', clearClick);
dt.on('destroy', function () {
dom.input.removeEventListener('input', inputInput);
dom.select.removeEventListener('input', selectInput);
dom.clear.removeEventListener('click', clearClick);
});
// State handling - all components that use this class have the same state saving structure
// so shared handling can be performed here.
dt.on('stateSaveParams', function (e, s, data) {
if (!data.columnControl) {
data.columnControl = {};
}
if (!data.columnControl[idx]) {
data.columnControl[idx] = {};
}
data.columnControl[idx].searchInput = {
logic: dom.select.value,
type: _this._type,
value: dom.input.value
};
});
dt.on('stateLoaded', function (e, s, state) {
_this._stateLoad(state);
});
// Same as for ColumnControl - reassign a column index if needed.
dt.on('columns-reordered', function (e, details) {
_this._idx = dt.colReorder.transpose(originalIdx, 'fromOriginal');
});
// Column control search clearing (column().ccSearchClear() method)
dt.on('cc-search-clear', function (e, colIdx) {
if (colIdx === _this._idx) {
// Don't want an automatic redraw on this event
_this._loadingState = true;
_this.clear();
_this._loadingState = false;
}
});
}
/**
* Add a class to the container
*
* @param name Class name to add
* @returns Self for chaining
*/
SearchInput.prototype.addClass = function (name) {
this._dom.container.classList.add(name);
return this;
};
/**
* Clear any applied search
*
* @returns Self for chaining
*/
SearchInput.prototype.clear = function () {
this.set(this._dom.select.children[0].getAttribute('value'), '');
return this;
};
/**
* Set the clear icon feature can be used or not
*
* @param set Flag
* @returns Self for chaining
*/
SearchInput.prototype.clearable = function (set) {
// Note there is no add here as it is added by default and never used after setup, so
// no need.
if (!set) {
this._dom.clear.remove();
}
return this;
};
/**
* Get the container element
*
* @returns The container element
*/
SearchInput.prototype.element = function () {
return this._dom.container;
};
/**
* Get the HTML input element for this control
*
* @returns HTML Input element
*/
SearchInput.prototype.input = function () {
return this._dom.input;
};
/**
* Set the list of options for the dropdown
*
* @param opts List of options
* @returns Self for chaining
*/
SearchInput.prototype.options = function (opts) {
var select = this._dom.select;
for (var i = 0; i < opts.length; i++) {
select.add(new Option(opts[i].label, opts[i].value));
}
// Initial icon
this._dom.typeIcon.innerHTML = icons[opts[0].value];
return this;
};
/**
* Set the placeholder attribute for the input element
*
* @param placeholder Placeholder string
* @returns Self for chaining
*/
SearchInput.prototype.placeholder = function (placeholder) {
if (placeholder) {
var columnTitle = this._dt.column(this._idx).title();
this._dom.input.placeholder = placeholder.replace('[title]', columnTitle);
}
return this;
};
/**
* Run the search method
*/
SearchInput.prototype.runSearch = function () {
var dom = this._dom;
var isActive = dom.select.value === 'empty' ||
dom.select.value === 'notEmpty' ||
dom.input.value !== '';
dom.container.classList.toggle('dtcc-search_active', isActive);
if (this._search &&
(this._lastValue !== dom.input.value || this._lastType !== dom.select.value)) {
this._search(dom.select.value, dom.input.value, this._loadingState);
this._lastValue = dom.input.value;
this._lastType = dom.select.value;
}
};
/**
* Set the function that will be run when a search operation is required. Note that this can
* trigger the function to run if there is a saved state.
*
* @param fn Search callback
* @returns Self for chaining
*/
SearchInput.prototype.search = function (fn) {
this._search = fn;
// If there is a saved state, load it now that set up is done.
this._stateLoad(this._dt.state.loaded());
return this;
};
/**
* Set a value for the search input
*
* @param logic Logic type
* @param val Value
* @returns Self for chaining
*/
SearchInput.prototype.set = function (logic, val) {
var dom = this._dom;
dom.input.value = val;
dom.select.value = logic;
dom.typeIcon.innerHTML = icons[dom.select.value];
this.runSearch();
return this;
};
/**
* Set the text that will be shown as the title for the control
*
* @param text Set the title text
* @returns Self for chaining
*/
SearchInput.prototype.title = function (text) {
if (text) {
var columnTitle = this._dt.column(this._idx).title();
this._dom.title.innerHTML = text.replace('[title]', columnTitle);
}
return this;
};
/**
* Set the title attribute for the input element
*
* @param title Title attribute string
* @returns Self for chaining
*/
SearchInput.prototype.titleAttr = function (title) {
if (title) {
var columnTitle = this._dt.column(this._idx).title();
this._dom.input.title = title.replace('[title]', columnTitle);
}
return this;
};
SearchInput.prototype.type = function (t) {
this._type = t;
return this;
};
/**
* Load a DataTables state
*
* @param state State object being loaded
*/
SearchInput.prototype._stateLoad = function (state) {
var _a, _b;
var dom = this._dom;
var idx = this._idx;
var loadedState = (_b = (_a = state === null || state === void 0 ? void 0 : state.columnControl) === null || _a === void 0 ? void 0 : _a[idx]) === null || _b === void 0 ? void 0 : _b.searchInput;
if (loadedState) {
// The search callback needs to know if we are loading an existing state or not
// so it can determine if it needs to draw the table. If it was a user input, then
// it redraws, if it was a state load, then there should be no redraw.
this._loadingState = true;
dom.select.value = loadedState.logic;
dom.input.value = loadedState.value;
dom.select.dispatchEvent(new Event('input'));
this._loadingState = false;
}
};
SearchInput.classes = {
container: ['dtcc-content', 'dtcc-search'],
input: '',
select: ''
};
return SearchInput;
}());
var searchDateTime = {
defaults: {
clear: true,
placeholder: '',
title: '',
titleAttr: ''
},
init: function (config) {
var _this = this;
var fromPicker = false;
var moment = DataTable.use('moment');
var luxon = DataTable.use('luxon');
var dt = this.dt();
var i18nBase = 'columnControl.search.datetime.';
var displayFormat = '';
var dateTime;
var searchInput = new SearchInput(dt, this.idx())
.type('date')
.addClass('dtcc-searchDateTime')
.clearable(config