mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 01:25:55 +02: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));
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
{% import "components/datatables.macro.html.twig" as datatables %}
|
||||
|
||||
<form method="post" action="{{ url("log_undo") }}"
|
||||
{{ stimulus_controller('elements/delete_btn') }} {{ stimulus_action('elements/delete_btn', "submit", "submit") }}
|
||||
data-delete-title="{% trans %}log.undo.confirm_title{% endtrans %}"
|
||||
data-delete-message="{% trans %}log.undo.confirm_message{% endtrans %}">
|
||||
<input type="hidden" name="redirect_back" value="{{ app.request.uri }}">
|
||||
<div id="part_list" class="" data-datatable data-settings='{{ datatable_settings(datatable) }}'>
|
||||
<div class="card-body">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4>{% trans %}part_list.loading.caption{% endtrans %}</h4>
|
||||
<h6>{% trans %}part_list.loading.message{% endtrans %}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ datatables.logDataTable(datatable) }}
|
||||
|
||||
</form>
|
|
@ -1,90 +1,3 @@
|
|||
<form method="post" action="{{ url("table_action") }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('table_action') }}">
|
||||
{% import "components/datatables.macro.html.twig" as datatables %}
|
||||
|
||||
<input type="hidden" name="redirect_back" value="{{ app.request.uri }}">
|
||||
|
||||
<input type="hidden" name="ids" id="select_ids" value="">
|
||||
|
||||
<div class="d-none mb-2" id="select_panel">
|
||||
{# <span id="select_count"></span> #}
|
||||
<span class="badge bg-secondary">{% trans with {'%count%': '<span id="select_count"></span>'} %}part_list.action.part_count{% endtrans %}</span>
|
||||
|
||||
<select class="selectpicker" name="action" id="select_action" data-controller="elements--selectpicker"
|
||||
title="{% trans %}part_list.action.action.title{% endtrans %}" onchange="updateTargetSelect()" required>
|
||||
<optgroup label="{% trans %}part_list.action.action.group.favorite{% endtrans %}">
|
||||
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="favorite">{% trans %}part_list.action.action.favorite{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="unfavorite">{% trans %}part_list.action.action.unfavorite{% endtrans %}</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="{% trans %}part_list.action.action.group.change_field{% endtrans %}">
|
||||
<option {% if not is_granted('@parts_category.edit') %}disabled{% endif %} value="change_category" data-url="{{ path('select_category') }}">{% trans %}part_list.action.action.change_category{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts_footprint.edit') %}disabled{% endif %} value="change_footprint" data-url="{{ path('select_footprint') }}">{% trans %}part_list.action.action.change_footprint{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts_manufacturer.edit') %}disabled{% endif %} value="change_manufacturer" data-url="{{ path('select_manufacturer') }}">{% trans %}part_list.action.action.change_manufacturer{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts_unit.edit') %}disabled{% endif %} value="change_unit" data-url="{{ path('select_measurement_unit') }}">{% trans %}part_list.action.action.change_unit{% endtrans %}</option>
|
||||
</optgroup>
|
||||
|
||||
<option {% if not is_granted('@parts.delete') %}disabled{% endif %} value="delete">{% trans %}part_list.action.action.delete{% endtrans %}</option>
|
||||
</select>
|
||||
|
||||
<select class="" style="display: none;" data-live-search="true" name="target" id="select_target">
|
||||
{# This is left empty, as this will be filled by Javascript #}
|
||||
</select>
|
||||
|
||||
<button type="submit" class="btn btn-secondary">{% trans %}part_list.action.submit{% endtrans %}</button>
|
||||
</div>
|
||||
|
||||
<div id="part_list" class="" data-select="true" data-part_table="true" data-datatable data-settings='{{ datatable_settings(datatable)|escape('html_attr') }}'>
|
||||
<div class="card-body">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4>{% trans %}part_list.loading.caption{% endtrans %}</h4>
|
||||
<h6>{% trans %}part_list.loading.message{% endtrans %}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
|
||||
function updateOptions(selector, json)
|
||||
{
|
||||
var select = document.querySelector(selector);
|
||||
|
||||
//Clear options
|
||||
select.innerHTML = null;
|
||||
|
||||
for(i=0; i<json.length; i++) {
|
||||
var json_opt = json[i];
|
||||
var 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.appendChild(opt);
|
||||
}
|
||||
}
|
||||
|
||||
function updateTargetSelect() {
|
||||
var element = document.querySelector('#select_action');
|
||||
|
||||
var selected = element.options[element.options.selectedIndex];
|
||||
|
||||
var url = selected.dataset.url;
|
||||
|
||||
if (url) {
|
||||
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => updateOptions('#select_target', data))
|
||||
.then(data => $('#select_target').selectpicker('refresh'));
|
||||
|
||||
} else {
|
||||
$('#select_target').selectpicker('hide');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{{ datatables.partsDatatableWithForm(datatable) }}
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
{% extends "base.html.twig" %}
|
||||
|
||||
{% import "components/datatables.macro.html.twig" as datatables %}
|
||||
|
||||
{% block title %}{% trans %}attachment.list.title{% endtrans %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="part_list" class="" data-datatable data-settings='{{ datatable_settings(datatable) }}'>
|
||||
<div class="card-body">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4>{% trans %}part_list.loading.caption{% endtrans %}</h4>
|
||||
<h6>{% trans %}part_list.loading.message{% endtrans %}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ datatables.datatable(datatable) }}
|
||||
{% endblock %}
|
69
templates/components/datatables.macro.html.twig
Normal file
69
templates/components/datatables.macro.html.twig
Normal file
|
@ -0,0 +1,69 @@
|
|||
{% macro datatable(datatable, controller = 'elements/datatables/datatables') %}
|
||||
<div {{ stimulus_controller(controller) }} data-dt-settings='{{ datatable_settings(datatable)|escape('html_attr') }}' data-dt-url="{{ app.request.pathInfo }}">
|
||||
<div {{ stimulus_target(controller, 'dt') }}>
|
||||
<div class="card-body">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4>{% trans %}part_list.loading.caption{% endtrans %}</h4>
|
||||
<h6>{% trans %}part_list.loading.message{% endtrans %}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro logDataTable(dt) %}
|
||||
{{ _self.datatable(dt, 'elements/datatables/log') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro partsDatatableWithForm(datatable) %}
|
||||
<form method="post" action="{{ path("table_action") }}"
|
||||
{{ stimulus_controller('elements/datatables/parts') }} data-dt-settings='{{ datatable_settings(datatable)|escape('html_attr') }}' data-dt-url="{{ app.request.pathInfo }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('table_action') }}">
|
||||
|
||||
<input type="hidden" name="redirect_back" value="{{ app.request.uri }}">
|
||||
|
||||
<input type="hidden" name="ids" {{ stimulus_target('elements/datatables/parts', 'selectIDs') }} value="">
|
||||
|
||||
<div class="d-none mb-2" {{ stimulus_target('elements/datatables/parts', 'selectPanel') }}>
|
||||
{# <span id="select_count"></span> #}
|
||||
<span class="badge bg-secondary">{% trans with {'%count%': '<span ' ~ stimulus_target('elements/datatables/parts', 'selectCount') ~ '></span>'} %}part_list.action.part_count{% endtrans %}</span>
|
||||
|
||||
<select class="selectpicker" name="action" data-controller="elements--selectpicker" {{ stimulus_action('elements/datatables/parts', 'updateTargetPicker', 'change') }}
|
||||
title="{% trans %}part_list.action.action.title{% endtrans %}" required>
|
||||
<optgroup label="{% trans %}part_list.action.action.group.favorite{% endtrans %}">
|
||||
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="favorite">{% trans %}part_list.action.action.favorite{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="unfavorite">{% trans %}part_list.action.action.unfavorite{% endtrans %}</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="{% trans %}part_list.action.action.group.change_field{% endtrans %}">
|
||||
<option {% if not is_granted('@parts_category.edit') %}disabled{% endif %} value="change_category" data-url="{{ path('select_category') }}">{% trans %}part_list.action.action.change_category{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts_footprint.edit') %}disabled{% endif %} value="change_footprint" data-url="{{ path('select_footprint') }}">{% trans %}part_list.action.action.change_footprint{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts_manufacturer.edit') %}disabled{% endif %} value="change_manufacturer" data-url="{{ path('select_manufacturer') }}">{% trans %}part_list.action.action.change_manufacturer{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts_unit.edit') %}disabled{% endif %} value="change_unit" data-url="{{ path('select_measurement_unit') }}">{% trans %}part_list.action.action.change_unit{% endtrans %}</option>
|
||||
</optgroup>
|
||||
|
||||
<option {% if not is_granted('@parts.delete') %}disabled{% endif %} value="delete">{% trans %}part_list.action.action.delete{% endtrans %}</option>
|
||||
</select>
|
||||
|
||||
<select class="" style="display: none;" data-live-search="true" name="target" {{ stimulus_target('elements/datatables/parts', 'selectTargetPicker') }}>
|
||||
{# This is left empty, as this will be filled by Javascript #}
|
||||
</select>
|
||||
|
||||
<button type="submit" class="btn btn-secondary">{% trans %}part_list.action.submit{% endtrans %}</button>
|
||||
</div>
|
||||
|
||||
<div {{ stimulus_target('elements/datatables/parts', 'dt') }}>
|
||||
<div class="card-body">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4>{% trans %}part_list.loading.caption{% endtrans %}</h4>
|
||||
<h6>{% trans %}part_list.loading.message{% endtrans %}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endmacro %}
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue