mirror of
https://github.com/kevinpapst/kimai2.git
synced 2025-01-10 19:47:35 +00:00
01226a1243
- added missing escape to prevent HTML injection - added missing color attribute - upgrade theme - use dropdown submenu if title is set, otherwise dropdown tends to get too long - allow to use card-table instead of card-body - added `required` attribute to username and password field - fix pagination back to page 1 - prevent tag name too long - re-add missing user preferences link
195 lines
6.8 KiB
JavaScript
195 lines
6.8 KiB
JavaScript
/*
|
|
* This file is part of the Kimai time-tracking app.
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
/*!
|
|
* [KIMAI] KimaiToolbar: some event listener to handle the toolbar/data-table filter, toolbar and navigation
|
|
*/
|
|
|
|
import KimaiPlugin from "../KimaiPlugin";
|
|
|
|
export default class KimaiToolbar extends KimaiPlugin {
|
|
|
|
constructor(formSelector, formSubmitActionClass) {
|
|
super();
|
|
this._formSelector = formSelector;
|
|
this._actionClass = formSubmitActionClass;
|
|
}
|
|
|
|
getId() {
|
|
return 'toolbar';
|
|
}
|
|
|
|
init() {
|
|
const formSelector = this.getSelector();
|
|
|
|
this._registerPagination(formSelector);
|
|
this._registerSortableTables(formSelector);
|
|
this._registerAlternativeSubmitActions(formSelector, this._actionClass);
|
|
|
|
// Reset the page if filter values are changed, otherwise we might end up with a limited set of data,
|
|
// which does not support the given page - and it would be just wrong to stay in the same page
|
|
[].slice.call(document.querySelectorAll(formSelector + ' input')).map((element) => {
|
|
element.addEventListener('change', (event) => {
|
|
switch (event.target.id) {
|
|
case 'order':
|
|
case 'orderBy':
|
|
case 'page':
|
|
break;
|
|
default:
|
|
document.querySelector(formSelector + ' input#page').value = 1;
|
|
break;
|
|
}
|
|
});
|
|
this.triggerChange();
|
|
});
|
|
|
|
// when user selected a new customer or project, reset the pagination back to 1
|
|
// and then find out if the results should be reloaded
|
|
[].slice.call(document.querySelectorAll(formSelector + ' select')).map((element) => {
|
|
element.addEventListener('change', (event) => {
|
|
let reload = true;
|
|
switch (event.target.id) {
|
|
case 'customer':
|
|
if (document.querySelector(formSelector + ' select#project') !== null) {
|
|
reload = false;
|
|
}
|
|
break;
|
|
|
|
case 'project':
|
|
if (document.querySelector(formSelector + ' select#activity') !== null) {
|
|
reload = false;
|
|
}
|
|
break;
|
|
}
|
|
document.querySelector(formSelector + ' input#page').value = 1;
|
|
|
|
if (reload) {
|
|
this.triggerChange();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Some actions utilize the filter from the search form and submit it to another URL.
|
|
* @private
|
|
*/
|
|
_registerAlternativeSubmitActions(toolbarSelector, actionBtnClass) {
|
|
document.addEventListener('click', function(event) {
|
|
let target = event.target;
|
|
while (target !== null && typeof target.matches === "function" && !target.matches('body')) {
|
|
if (target.classList.contains(actionBtnClass)) {
|
|
const form = document.querySelector(toolbarSelector);
|
|
if (form === null) {
|
|
return;
|
|
}
|
|
const prevAction = form.getAttribute('action');
|
|
const prevMethod = form.getAttribute('method');
|
|
if (target.dataset.target !== undefined) {
|
|
form.target = target.dataset.target;
|
|
}
|
|
form.action = target.href;
|
|
if (target.dataset.method !== undefined) {
|
|
form.method = target.dataset.method;
|
|
}
|
|
form.submit();
|
|
form.target = '';
|
|
form.action = prevAction;
|
|
form.method = prevMethod;
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
|
|
target = target.parentNode;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sortable datatables use hidden fields in the toolbar filter/search form
|
|
* @private
|
|
*/
|
|
_registerSortableTables(formSelector) {
|
|
document.body.addEventListener('click', (event) => {
|
|
if (!event.target.matches('th.sortable')) {
|
|
return;
|
|
}
|
|
let order = 'DESC';
|
|
let orderBy = event.target.dataset['order'];
|
|
if (event.target.classList.contains('sorting_desc')) {
|
|
order = 'ASC';
|
|
}
|
|
|
|
document.querySelector(formSelector + ' #orderBy').value = orderBy;
|
|
document.querySelector(formSelector + ' #order').value = order;
|
|
|
|
// re-render the selectbox
|
|
document.querySelector(formSelector + ' #orderBy').dispatchEvent(new Event('change'));
|
|
document.querySelector(formSelector + ' #order').dispatchEvent(new Event('change'));
|
|
|
|
// triggers the datatable reload - search for the event name
|
|
document.dispatchEvent(new Event('filter-change'));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This catches all clicks on the pagination and prevents the default action,
|
|
* as we want to reload the page via JS.
|
|
*
|
|
* @private
|
|
*/
|
|
_registerPagination(formSelector) {
|
|
document.body.addEventListener('click', (event) => {
|
|
if (!event.target.matches('ul.pagination li a') && (event.target.parentNode === null || !event.target.parentNode.matches('ul.pagination li a'))) {
|
|
return;
|
|
}
|
|
|
|
let pager = document.querySelector(formSelector + " input#page");
|
|
if (pager === null) {
|
|
return;
|
|
}
|
|
let target = event.target;
|
|
|
|
// this happens for the arrows, which can be an icon <i> element
|
|
if (!target.matches('a')) {
|
|
target = target.parentNode;
|
|
}
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
let urlParts = target.href.split('/');
|
|
let pageNumber = urlParts[urlParts.length - 1];
|
|
// page number usually is the default value and is therefor missing from the URL
|
|
if (!/\d/.test(pageNumber)) {
|
|
pageNumber = 1;
|
|
}
|
|
pager.value = pageNumber;
|
|
pager.dispatchEvent(new Event('change'));
|
|
document.dispatchEvent(new Event('pagination-change'));
|
|
return false;
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Triggers an event, that everyone can listen for.
|
|
*/
|
|
triggerChange() {
|
|
document.dispatchEvent(new Event('toolbar-change'));
|
|
}
|
|
|
|
/**
|
|
* Returns the CSS selector to target the toolbar form.
|
|
*
|
|
* @returns {string}
|
|
*/
|
|
getSelector() {
|
|
return this._formSelector;
|
|
}
|
|
|
|
}
|