mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-12-27 05:19:31 +00:00
Use stimulus for parts tables and select actions.
This commit is contained in:
parent
565cb3a790
commit
452f0a8362
9 changed files with 1369 additions and 1053 deletions
145
assets/controllers/elements/datatables/datatables_controller.js
Normal file
145
assets/controllers/elements/datatables/datatables_controller.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
//Styles
|
||||
import 'datatables.net-bs5/css/dataTables.bootstrap5.css'
|
||||
import 'datatables.net-buttons-bs5/css/buttons.bootstrap5.css'
|
||||
import 'datatables.net-fixedheader-bs5/css/fixedHeader.bootstrap5.css'
|
||||
import 'datatables.net-select-bs5/css/select.bootstrap5.css'
|
||||
import 'datatables.net-responsive-bs5/css/responsive.bootstrap5.css';
|
||||
|
||||
//JS
|
||||
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';
|
||||
|
||||
const EVENT_DT_LOADED = 'dt:loaded';
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = ['dt'];
|
||||
|
||||
/** The datatable instance associated with this controller instance */
|
||||
_dt;
|
||||
|
||||
connect() {
|
||||
//$($.fn.DataTable.tables()).DataTable().fixedHeader.disable();
|
||||
//$($.fn.DataTable.tables()).DataTable().destroy();
|
||||
|
||||
const settings = JSON.parse(this.element.dataset.dtSettings);
|
||||
if(!settings) {
|
||||
throw new Error("No settings provided for datatable!");
|
||||
}
|
||||
|
||||
//Add url info, as the one available in the history is not enough, as Turbo may have not changed it yet
|
||||
settings.url = this.element.dataset.dtUrl;
|
||||
|
||||
|
||||
//@ts-ignore
|
||||
const promise = $(this.dtTarget).initDataTables(settings,
|
||||
{
|
||||
colReorder: true,
|
||||
responsive: true,
|
||||
fixedHeader: {
|
||||
header: $(window).width() >= 768, //Only enable fixedHeaders on devices with big screen. Fixes scrolling issues on smartphones.
|
||||
headerOffset: $("#navbar").height()
|
||||
},
|
||||
buttons: [{
|
||||
"extend": 'colvis',
|
||||
'className': 'mr-2 btn-light',
|
||||
"text": "<i class='fa fa-cog'></i>"
|
||||
}],
|
||||
select: this.isSelectable(),
|
||||
rowCallback: this._rowCallback.bind(this),
|
||||
})
|
||||
//Register error handler
|
||||
.catch(err => {
|
||||
console.error("Error initializing datatables: " + err);
|
||||
});
|
||||
|
||||
//Dispatch an event to let others know that the datatables has been loaded
|
||||
promise.then((dt) => {
|
||||
const event = new CustomEvent(EVENT_DT_LOADED, {bubbles: true});
|
||||
this.element.dispatchEvent(event);
|
||||
|
||||
this._dt = dt;
|
||||
});
|
||||
|
||||
//Register event handlers
|
||||
promise.then((dt) => {
|
||||
dt.on('select.dt deselect.dt', this._onSelectionChange.bind(this));
|
||||
});
|
||||
|
||||
//Allow to further configure the datatable
|
||||
promise.then(this._afterLoaded.bind(this));
|
||||
|
||||
|
||||
|
||||
//Register links.
|
||||
/*promise.then(function() {
|
||||
|
||||
//Set the correct title in the table.
|
||||
let title = $('#part-card-header-src');
|
||||
$('#part-card-header').html(title.html());
|
||||
$(document).trigger('ajaxUI:dt_loaded');
|
||||
|
||||
|
||||
if($table.data('part_table')) {
|
||||
//@ts-ignore
|
||||
$('#dt').on( 'select.dt deselect.dt', function ( e, dt, items ) {
|
||||
let selected_elements = dt.rows({selected: true});
|
||||
let count = selected_elements.count();
|
||||
|
||||
if(count > 0) {
|
||||
$('#select_panel').removeClass('d-none');
|
||||
} else {
|
||||
$('#select_panel').addClass('d-none');
|
||||
}
|
||||
|
||||
$('#select_count').text(count);
|
||||
|
||||
let selected_ids_string = selected_elements.data().map(function(value, index) {
|
||||
return value['id']; }
|
||||
).join(",");
|
||||
|
||||
$('#select_ids').val(selected_ids_string);
|
||||
|
||||
} );
|
||||
}
|
||||
|
||||
//Attach event listener to update links after new page selection:
|
||||
$('#dt').on('draw.dt column-visibility.dt', function() {
|
||||
//ajaxUI.registerLinks();
|
||||
$(document).trigger('ajaxUI:dt_loaded');
|
||||
});
|
||||
});*/
|
||||
|
||||
console.debug('Datatables inited.');
|
||||
}
|
||||
|
||||
_rowCallback(row, data, index) {
|
||||
//Empty by default but can be overridden by child classes
|
||||
}
|
||||
|
||||
_onSelectionChange(e, dt, items ) {
|
||||
//Empty by default but can be overridden by child classes
|
||||
alert("Test");
|
||||
}
|
||||
|
||||
_afterLoaded(dt) {
|
||||
//Empty by default but can be overridden by child classes
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this datatable has selection feature enabled
|
||||
*/
|
||||
isSelectable()
|
||||
{
|
||||
return this.element.dataset.select ?? false;
|
||||
}
|
||||
|
||||
}
|
||||
32
assets/controllers/elements/datatables/log_controller.js
Normal file
32
assets/controllers/elements/datatables/log_controller.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import DatatablesController from "./datatables_controller.js";
|
||||
|
||||
/**
|
||||
* This is the datatables controller for log pages, it includes an mechanism to color lines based on their level.
|
||||
*/
|
||||
export default class extends DatatablesController {
|
||||
_rowCallback(row, data, index) {
|
||||
//Check if we have a level, then change color of this row
|
||||
if (data.level) {
|
||||
let style = "";
|
||||
switch (data.level) {
|
||||
case "emergency":
|
||||
case "alert":
|
||||
case "critical":
|
||||
case "error":
|
||||
style = "table-danger";
|
||||
break;
|
||||
case "warning":
|
||||
style = "table-warning";
|
||||
break;
|
||||
case "notice":
|
||||
style = "table-info";
|
||||
break;
|
||||
}
|
||||
|
||||
if (style) {
|
||||
$(row).addClass(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
83
assets/controllers/elements/datatables/parts_controller.js
Normal file
83
assets/controllers/elements/datatables/parts_controller.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import DatatablesController from "./datatables_controller.js";
|
||||
|
||||
/**
|
||||
* This is the datatables controller for parts lists
|
||||
*/
|
||||
export default class extends DatatablesController {
|
||||
|
||||
static targets = ['dt', 'selectPanel', 'selectIDs', 'selectCount', 'selectTargetPicker'];
|
||||
|
||||
isSelectable() {
|
||||
//Parts controller is always selectable
|
||||
return true;
|
||||
}
|
||||
|
||||
_onSelectionChange(e, dt, items) {
|
||||
const selected_elements = dt.rows({selected: true});
|
||||
const count = selected_elements.count();
|
||||
|
||||
const selectPanel = this.selectPanelTarget;
|
||||
|
||||
//Hide/Unhide panel with the selection tools
|
||||
if (count > 0) {
|
||||
selectPanel.classList.remove('d-none');
|
||||
} else {
|
||||
selectPanel.classList.add('d-none');
|
||||
}
|
||||
|
||||
//Update selection count text
|
||||
this.selectCountTarget.innerText = count;
|
||||
|
||||
//Fill selection ID input
|
||||
let selected_ids_string = selected_elements.data().map(function(value, index) {
|
||||
return value['id']; }
|
||||
).join(",");
|
||||
|
||||
this.selectIDsTarget.value = selected_ids_string;
|
||||
}
|
||||
|
||||
updateOptions(select_element, json)
|
||||
{
|
||||
//Clear options
|
||||
select_element.innerHTML = null;
|
||||
$(select_element).selectpicker('destroy');
|
||||
|
||||
for(let i=0; i<json.length; i++) {
|
||||
let json_opt = json[i];
|
||||
let opt = document.createElement('option');
|
||||
opt.value = json_opt.value;
|
||||
opt.innerHTML = json_opt.text;
|
||||
|
||||
if(json_opt['data-subtext']) {
|
||||
opt.dataset.subtext = json_opt['data-subtext'];
|
||||
}
|
||||
|
||||
select_element.appendChild(opt);
|
||||
}
|
||||
|
||||
$(select_element).selectpicker('show');
|
||||
|
||||
}
|
||||
|
||||
updateTargetPicker(event) {
|
||||
const element = event.target;
|
||||
|
||||
//Extract the url from the selected option
|
||||
const selected_option = element.options[element.options.selectedIndex];
|
||||
const url = selected_option.dataset.url;
|
||||
|
||||
const select_target = this.selectTargetPickerTarget;
|
||||
|
||||
if (url) {
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
response.json().then(json => {
|
||||
this.updateOptions(select_target, json);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$(select_target).selectpicker('hide');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
return new Promise((fulfill, reject) => {
|
||||
// Perform initial load
|
||||
$.ajax(config.url, {
|
||||
$.ajax(typeof config.url === 'function' ? config.url(null) : config.url, {
|
||||
method: config.method,
|
||||
data: {
|
||||
_dt: config.name,
|
||||
|
|
@ -53,15 +53,17 @@
|
|||
var baseState;
|
||||
|
||||
// Merge all options from different sources together and add the Ajax loader
|
||||
var dtOpts = $.extend({}, data.options, config.options, options, persistOptions, {
|
||||
var dtOpts = $.extend({}, data.options, typeof config.options === 'function' ? {} : config.options, options, persistOptions, {
|
||||
ajax: function (request, drawCallback, settings) {
|
||||
if (data) {
|
||||
data.draw = request.draw;
|
||||
drawCallback(data);
|
||||
data = null;
|
||||
if (Object.keys(state).length && dt.state != null) {
|
||||
var merged = $.extend(true, {}, dt.state(), state);
|
||||
dt
|
||||
if (Object.keys(state).length) {
|
||||
var api = new $.fn.dataTable.Api( settings );
|
||||
var merged = $.extend(true, {}, api.state(), state);
|
||||
|
||||
api
|
||||
.order(merged.order)
|
||||
.search(merged.search.search)
|
||||
.page.len(merged.length)
|
||||
|
|
@ -70,7 +72,7 @@
|
|||
}
|
||||
} else {
|
||||
request._dt = config.name;
|
||||
$.ajax(config.url, {
|
||||
$.ajax(typeof config.url === 'function' ? config.url(dt) : config.url, {
|
||||
method: config.method,
|
||||
data: request
|
||||
}).done(function(data) {
|
||||
|
|
@ -80,6 +82,10 @@
|
|||
}
|
||||
});
|
||||
|
||||
if (typeof config.options === 'function') {
|
||||
dtOpts = config.options(dtOpts);
|
||||
}
|
||||
|
||||
root.html(data.template);
|
||||
dt = $('table', root).DataTable(dtOpts);
|
||||
if (config.state !== 'none') {
|
||||
|
|
@ -122,6 +128,80 @@
|
|||
url: window.location.origin + window.location.pathname
|
||||
};
|
||||
|
||||
/**
|
||||
* Server-side export.
|
||||
*/
|
||||
$.fn.initDataTables.exportBtnAction = function(exporterName, settings) {
|
||||
settings = $.extend({}, $.fn.initDataTables.defaults, settings);
|
||||
|
||||
return function(e, dt) {
|
||||
const params = $.param($.extend({}, dt.ajax.params(), {'_dt': settings.name, '_exporter': exporterName}));
|
||||
|
||||
// Credit: https://stackoverflow.com/a/23797348
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(settings.method, settings.method === 'GET' ? (settings.url + '?' + params) : settings.url, true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.onload = function () {
|
||||
if (this.status === 200) {
|
||||
let filename = "";
|
||||
const disposition = xhr.getResponseHeader('Content-Disposition');
|
||||
if (disposition && disposition.indexOf('attachment') !== -1) {
|
||||
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
|
||||
const matches = filenameRegex.exec(disposition);
|
||||
if (matches != null && matches[1]) {
|
||||
filename = matches[1].replace(/['"]/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
const type = xhr.getResponseHeader('Content-Type');
|
||||
|
||||
let blob;
|
||||
if (typeof File === 'function') {
|
||||
try {
|
||||
blob = new File([this.response], filename, { type: type });
|
||||
} catch (e) { /* Edge */ }
|
||||
}
|
||||
|
||||
if (typeof blob === 'undefined') {
|
||||
blob = new Blob([this.response], { type: type });
|
||||
}
|
||||
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
|
||||
window.navigator.msSaveBlob(blob, filename);
|
||||
}
|
||||
else {
|
||||
const URL = window.URL || window.webkitURL;
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
|
||||
if (filename) {
|
||||
// use HTML5 a[download] attribute to specify filename
|
||||
const a = document.createElement("a");
|
||||
// safari doesn't support this yet
|
||||
if (typeof a.download === 'undefined') {
|
||||
window.location = downloadUrl;
|
||||
}
|
||||
else {
|
||||
a.href = downloadUrl;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
}
|
||||
}
|
||||
else {
|
||||
window.location = downloadUrl;
|
||||
}
|
||||
|
||||
setTimeout(function() { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
xhr.send(settings.method === 'POST' ? params : null);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a querystring to a proper array - reverses $.param
|
||||
*/
|
||||
|
|
@ -182,4 +262,4 @@
|
|||
|
||||
return obj;
|
||||
}
|
||||
}($));
|
||||
}(jQuery));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue