datatables.net-react
Version:
React component for DataTables
227 lines (223 loc) • 7.08 kB
JavaScript
import { jsx } from 'react/jsx-runtime';
import { createRoot } from 'react-dom/client';
import { forwardRef, useRef, useImperativeHandle, useEffect } from 'react';
/*
* List of events that DataTables can fire, so they can re-exported to React
*/
var dtEvents = [
'autoFill',
'preAutoFill',
'buttons-action',
'buttons-processing',
'column-reorder',
'columns-reordered',
'childRow',
'column-sizing',
'column-visibility',
'destroy',
'draw',
'dt-error',
'info',
'init',
'length',
'options',
'order',
'page',
'preDraw',
'preInit',
'preXhr',
'processing',
'requestChild',
'search',
'stateLoaded',
'stateLoadParams',
'stateSaveParams',
'xhr',
'key',
'key-blur',
'key-focus',
'key-prefocus',
'key-refocus',
'key-return-submit',
'responsive-display',
'responsive-resize',
'rowgroup-datasrc',
'pre-row-reorder',
'row-reorder',
'row-reorder-canceled',
'row-reorder-changed',
'row-reordered',
'dtsb-inserted',
'deselect',
'select',
'select-blur',
'selectItems',
'selectStyle',
'user-select',
'stateRestore-change',
];
let DataTablesLib = null;
// any here, so we can assign the `use` later - it is really a DataTableComponent though
const Component = forwardRef(function DataTable(props, ref) {
const tableEl = useRef(null);
const table = useRef(null);
const options = useRef(props.options ?? {});
const cache = useRef([]);
// Expose the DataTables API via a reference
useImperativeHandle(ref, () => ({
dt: () => table.current
}));
// Expose some of the more common settings as props
if (props.data) {
options.current.data = props.data;
}
if (props.ajax) {
options.current.ajax = props.ajax;
}
if (props.columns) {
options.current.columns = props.columns;
}
// If slots are defined, create `columnDefs` entries for them to apply
// to their target columns.
if (props.slots) {
applySlots(cache.current, options.current, props.slots);
}
// Create the DataTable when the `<table>` is ready in the document
useEffect(() => {
if (!DataTablesLib) {
throw new Error('DataTables library not set. See https://datatables.net/tn/23 for details.');
}
if (tableEl.current) {
const $ = DataTablesLib.use('jq');
const table$ = $(tableEl.current);
// Bind to DataTable's events so they can be listened to with an `on` property
dtEvents.forEach((name) => {
// Create the `on*` name from the DataTables event name, which is camelCase
// and an `on` prefix.
const onName = 'on' +
name[0].toUpperCase() +
name.slice(1).replace(/-[a-z]/g, (match) => match[1].toUpperCase());
if (props[onName]) {
table$.on(name + '.dt', props[onName]);
}
});
// Initialise the DataTable
table.current = new DataTablesLib(tableEl.current, options.current);
}
// Unmount tidy up
return () => {
if (table.current) {
// Unmount the created roots when this component unmounts
let roots = cache.current.slice();
cache.current.length = 0;
setTimeout(() => {
roots.forEach((r) => {
r.unmount();
});
}, 250);
table.current.destroy();
table.current = null;
}
};
}, []);
// On data change, clear and redraw
useEffect(() => {
if (props.data) {
if (table.current) {
table.current.clear();
table.current.rows.add(props.data).draw(false);
}
}
}, [props.data]);
return (jsx("div", { children: jsx("table", { ref: tableEl, className: props.className ?? '', id: props.id ?? '', children: props.children ?? null }) }));
});
Component.use = function (lib) {
DataTablesLib = lib;
};
const Exporter = Component;
/**
* Loop over the slots defined and apply them to their columns,
* targeting based on the slot name (object key).
*
* @param options DataTables configuration object
* @param slots Props passed in
*/
function applySlots(cache, options, slots) {
if (!options.columnDefs) {
options.columnDefs = [];
}
Object.keys(slots).forEach((name) => {
let slot = slots[name];
if (!slot) {
return;
}
// Simple column index
if (name.match(/^-?\d+$/)) {
// Note that unshift is used to make sure that this property is
// applied in DataTables _after_ the end user's own options, if
// they've provided any.
options.columnDefs.unshift({
target: parseInt(name),
render: slotRenderer(cache, slot)
});
}
else {
// Column name
options.columnDefs.unshift({
target: name + ':name',
render: slotRenderer(cache, slot)
});
}
});
}
/**
* Create a rendering function that will create a React component
* for a cell's rendering function.
*
* @param slot Function to create react component or orthogonal data
* @returns Rendering function
*/
function slotRenderer(cache, slot) {
return function (data, type, row, meta) {
if (slot.length === 4) {
let result = slot(data, type, row, meta);
return result['$$typeof'] ? renderJsx(cache, result) : result;
}
else if (slot.length === 3) {
// The function takes three parameters so it allows for
// orthogonal data - not possible to cache the response
let result = slot(data, type, row, meta);
return result['$$typeof'] ? renderJsx(cache, result) : result;
}
// Otherwise, we are expecting a JSX return from the function every
// time and we can cache it. Note the `slot as any` - Typescript
// doesn't appear to like the two argument option for `DataTableSlot`.
return slotCache(cache, () => slot(data, row));
};
}
/**
* Render a slot's element and cache it
*/
function slotCache(cache, create) {
// Execute the rendering function
let result = create();
// If the result is a JSX element, we need to render and then cache it
if (result['$$typeof']) {
let div = renderJsx(cache, result);
return div;
}
// Any other data just gets returned
return result;
}
/**
* Render JSX into a div which can be shown in a cell
*/
function renderJsx(cache, jsx) {
let div = document.createElement('div');
let root = createRoot(div);
root.render(jsx);
cache.push(root);
return div;
}
export { Exporter as default };
//# sourceMappingURL=index.mjs.map