Datatables Javascript files
The controller
app/javascript/controllers/datatables_controller.js
import { Controller } from "@hotwired/stimulus"
import '../src/datatables-bs5'
import DataTable from 'datatables.net';
export default class extends Controller {
static values = {
simple: Boolean,
url: String
}
initialize() {
}
connect() {
// overcome morph problems
this.element.setAttribute("data-action",
"turbo:morph-element->datatables#reconnect"
)
let _this = this
let dtOptions = {}
this.compileOptions(dtOptions)
const table = this.element.querySelector('table')
// console.log(table)
// initialize datatable
let dtable = new DataTable(table, dtOptions)
// console.log(dtable.page.info().serverSide)
// catch column visibility change
this.colvis_change_listener(dtable)
if (!this.simpleValue) {
// wait for init complete if server side processing
// else call setInputFields directly
if (dtable.page.info().serverSide == true) {
dtable.on('init.dt', function() {
_this.setInputFields(dtable)
_this.process_search_input(dtable)
})
} else {
this.setInputFields(dtable)
this.process_search_input(dtable)
}
}
} // connect
disconnect() {
}
// search fields for each column
setInputFields(dtable) {
this.element.querySelectorAll("table tfoot th:not([class='nosearch'])")
.forEach((th, idx) => {
let col = th.getAttribute("data-dt-column")
let text = ''
let state = dtable.state();
if (state) {
text = state.columns[col].search.search
}
th.insertAdjacentHTML('afterbegin', this.searchField(col, text))
})
}
// single search input field
searchField(idx, text) {
return `<input type="text" placeholder="search" name="idx${idx}" value="${text}"/>`
}
// datatables options
compileOptions(options) {
// common options
options.pagingType = "full_numbers"
options.responsive = true
options.retrieve = true
options.stateSave = true
options.stateDuration = 60 * 60 * 24
options.lengthMenu = [10, 25, 100, 250, 1000, { label: 'Alle', value: -1}]
options.columnDefs = [ { "targets": "nosort", "orderable": false },
{ "targets": "notvisible", "visible": false },
{ "targets": "actions", "className": "actions" } ]
// with or without buttons
if (this.simpleValue) {
this.simpleOptions(options)
} else {
this.buttonOptions(options)
}
// remote fetch via ajax or plain html data
if (this.hasUrlValue) {
this.remoteOptions(options)
}
// this.languageOptions(options)
}
simpleOptions(options) {
options.dom = "<'row '<'col-sm-12'tr>>" +
"<'row mt-2 justify-content-between'<'col-md-auto me-auto mt-1'l><'col-md-auto me-auto mt-2'i><'col-md-auto ms-auto'p>>"
options.pagingType = "numbers"
}
buttonOptions(options) {
options.dom = "<'row mt-2 justify-content-between'<'col-md-auto me-auto'l><'col-md-auto'B>>" +
"<'row mt-2 justify-content-md-center'<'col-sm-12'tr>>" +
"<'row mt-2 justify-content-between'<'col-md-auto me-auto'i><'col-md-auto ms-auto'p>>"
options.buttons = {
dom: {
button: {
tag: 'button',
className: 'btn btn-outline-secondary btn-sm'
}
},
buttons: [
{ "text": 'Reset',
"action": function(e, dt, node, config) {
dt.state.clear();
window.location.reload();
}},
{ "extend": 'csv',
"exportOptions": { "columns": ':visible',
"search": ':applied' } },
{ "extend": 'excel',
"exportOptions": { "columns": ':visible',
"search": ':applied' } },
{ "extend": 'pdf',
"orientation": 'landscape',
"pageSize": 'A4',
"exportOptions": { "columns": ':visible',
"search": ':applied' } },
{ "extend": 'print'},
{ "extend": 'colvis', "columns": ':gt(0)' }
]
}
}
remoteOptions(options) {
//
let csrf = document.head.querySelector('meta[name="csrf-token"]')
let token = "not available"
if (csrf != null) {
token = csrf.getAttribute('content')
}
options.searchDelay = 400
options.processing = true
options.serverSide = true
options.ajax = {
'url': this.urlValue,
'type': 'POST',
'beforeSend': function(request) {
request.setRequestHeader("X-CSRF-Token", token)
}
}
}
// fix morph problems
reconnect() {
this.disconnect()
this.connect()
}
colvis_change_listener(dtable) {
const search = this.createSearchWithDebounce(dtable)
let _this = this
dtable.on('column-visibility.dt', function (e, settings, column, state) {
if (state) {
let th = e.target.querySelector('tfoot th[data-dt-column="' + column + '"]:not(.nosearch)')
if (th) {
let sf = th.querySelector('input')
if (!sf) {
th.insertAdjacentHTML('afterbegin', _this.searchField(column, ''))
}
}
$('input[name=idx'+column+']').on( 'keyup change', function() {
search(column, this.value)
})
}
})
}
process_search_input(dtable) {
const search = this.createSearchWithDebounce(dtable)
dtable.columns().eq(0).each((colIdx) => {
$('input[name=idx'+colIdx+']').on( 'keyup change', function() {
search(colIdx, this.value)
})
})
}
createSearchWithDebounce(dtable, delay = 400) {
if (!dtable || !dtable.column || !dtable.column().search || !dtable.draw || !DataTable.util.debounce) {
console.error("Invalid DataTable instance or missing debounce utility.");
return null
}
let search;
if (dtable.page.info().serverSide) {
search = DataTable.util.debounce(function (col, val) {
dtable.column(col).search(val).draw()
}, delay)
} else {
search = function (col, val) {
dtable.column(col).search(val).draw()
}
}
return search
}
languageOptions(options) {
options.language = {
"emptyTable": "Keine Daten in der Tabelle vorhanden",
"info": "_START_ bis _END_ von _TOTAL_ Einträgen",
"infoEmpty": "0 bis 0 von 0 Einträgen",
"infoFiltered": "(gefiltert von _MAX_ Einträgen)",
"infoPostFix": "",
"thousands": ".",
"lengthMenu": "_MENU_ Einträge anzeigen",
"loadingRecords": "Wird geladen...",
"processing": "Bitte warten...",
"search": "Suchen",
"zeroRecords": "Keine Einträge vorhanden.",
"paginate": {
"first": "Erste",
"previous": "Zurück",
"next": "Nächste",
"last": "Letzte"
},
"aria": {
"sortAscending": ": aktivieren, um Spalte aufsteigend zu sortieren",
"sortDescending": ": aktivieren, um Spalte absteigend zu sortieren"
}
}
}
} // Controller
Helpers
jquery preloader
app/javascript/src/jquery.js
import jquery from 'jquery'
window.jQuery = jquery
window.$ = jquery
Datatables modules
app/javascript/src/datatables-bs5.js
import '../src/jquery.js'
// used for Excel button, CSV button doesn't need it
import JSZip from 'jszip'
window.JSZip = JSZip
// used for PDF button
import pdfMake from "pdfmake/build/pdfmake";
import pdfFonts from "pdfmake/build/vfs_fonts";
pdfMake.addVirtualFileSystem(pdfFonts);
// import DataTable from 'datatables.net';
import 'datatables.net-bs5';
import 'datatables.net-buttons-bs5';
import 'datatables.net-responsive-bs5';
import 'datatables.net-buttons/js/buttons.colVis.mjs';
import 'datatables.net-buttons/js/buttons.html5.mjs';
import 'datatables.net-buttons/js/buttons.print.mjs';