248 lines
9.3 KiB
JavaScript
248 lines
9.3 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 jQuery from 'jquery';
|
|
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();
|
|
const self = this;
|
|
const EVENT = self.getContainer().getPlugin('event');
|
|
|
|
this._registerPagination(formSelector, EVENT);
|
|
this._registerSortableTables(formSelector, EVENT);
|
|
this._registerAlternativeSubmitActions(formSelector, this.actionClass);
|
|
this._registerSearchButtons(formSelector);
|
|
|
|
jQuery('body')
|
|
// prevent that the dropdown closes, when a form input is changed - eg. a select option was clicked
|
|
.on('click', formSelector + ' .dropdown-menu', function (event) {
|
|
const parent = jQuery(event.target).parents('.bootstrap-select');
|
|
if (parent.length === 0) {
|
|
event.stopPropagation();
|
|
jQuery(".bootstrap-select").removeClass("open");
|
|
}
|
|
})
|
|
// trying to emulate the normal behaviour fo the bootstrap-select, as using its default implementation
|
|
// leads to closing the surrounding dropdown menu
|
|
.on('click', formSelector + ' .bootstrap-select', function (event) {
|
|
const current = jQuery(this);
|
|
if (current.hasClass("open")){
|
|
jQuery(".bootstrap-select").removeClass("open");
|
|
} else {
|
|
jQuery(".bootstrap-select").not('.bs-container').each(function(index, element) {
|
|
var tmp = jQuery(element);
|
|
if (tmp.is(current)) {
|
|
return;
|
|
}
|
|
if (tmp.hasClass('open')) {
|
|
tmp.removeClass("open");
|
|
// the shown dropdown list will not be closed, using toggle hides all other lists BUT closes the containing search-dropdown
|
|
// tmp.find('select.selectpicker').selectpicker('toggle');
|
|
}
|
|
});
|
|
current.addClass("open");
|
|
}
|
|
event.stopPropagation();
|
|
})
|
|
// close bootstrap-select if a click happened outside (and none of the other clickHandler were called)
|
|
// if the click happened inside a bootstrap-select, we ignore this
|
|
.on('click', function(event) {
|
|
const parent = jQuery(event.target).parents('.bootstrap-select');
|
|
if (parent.length === 0) {
|
|
jQuery(".bootstrap-select").removeClass("open");
|
|
}
|
|
})
|
|
;
|
|
|
|
// 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
|
|
jQuery(formSelector +' input').change(function (event) {
|
|
switch (event.target.id) {
|
|
case 'order':
|
|
case 'orderBy':
|
|
case 'page':
|
|
break;
|
|
default:
|
|
jQuery(formSelector + ' input#page').val(1);
|
|
break;
|
|
}
|
|
self.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
|
|
jQuery(formSelector + ' select').change(function (event) {
|
|
let reload = true;
|
|
switch (event.target.id) {
|
|
case 'customer':
|
|
if (jQuery(formSelector + ' select#project').length > 0) {
|
|
reload = false;
|
|
}
|
|
break;
|
|
|
|
case 'project':
|
|
if (jQuery(formSelector + ' select#activity').length > 0) {
|
|
reload = false;
|
|
}
|
|
break;
|
|
}
|
|
jQuery(formSelector + ' input#page').val(1);
|
|
|
|
if (reload) {
|
|
self.triggerChange();
|
|
}
|
|
});
|
|
|
|
// close all open selectpicker upon choosing any dropdown option
|
|
jQuery(formSelector + ' select.selectpicker').on('change', function(event) {
|
|
jQuery('.bootstrap-select.open').removeClass('open');
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* The search toggle button is not part of this component, but it is directly connected to it.
|
|
* @private
|
|
*/
|
|
_registerSearchButtons(formSelector) {
|
|
jQuery('body')
|
|
// only for mobile experience currently: show the search form field
|
|
.on('click', '.btn-search.search-toggle', function (event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
jQuery(formSelector).parent('section').toggleClass('search-open');
|
|
jQuery(formSelector).toggleClass('hidden-xs');
|
|
jQuery(formSelector + ' input#searchTerm').dropdown('toggle');
|
|
jQuery(formSelector + ' input#searchTerm').focus();
|
|
})
|
|
// hide the search form field
|
|
.on('click', formSelector + ' a.search-cancel', function (event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
jQuery(formSelector).parent('section').toggleClass('search-open');
|
|
jQuery(formSelector + ' input#searchTerm').dropdown('toggle');
|
|
jQuery(formSelector).toggleClass('hidden-xs');
|
|
})
|
|
;
|
|
}
|
|
|
|
/**
|
|
* 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 && !target.matches('body')) {
|
|
if (target.classList.contains(actionBtnClass)) {
|
|
const form = document.querySelector(toolbarSelector);
|
|
if (form === null) {
|
|
return;
|
|
}
|
|
const prevAction = form.action;
|
|
const prevMethod = form.method;
|
|
form.target = '_blank';
|
|
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, EVENT) {
|
|
jQuery('body').on('click', 'th.sortable', function(event){
|
|
var $header = jQuery(event.target);
|
|
var order = 'DESC';
|
|
var orderBy = $header.data('order');
|
|
if ($header.hasClass('sorting_desc')) {
|
|
order = 'ASC';
|
|
}
|
|
jQuery(formSelector + ' input#orderBy').val(orderBy);
|
|
jQuery(formSelector + ' input#order').val(order);
|
|
// triggers the page reset - see below
|
|
jQuery(formSelector + ' input#order').trigger('change');
|
|
// triggers the datatable reload - search for the event name
|
|
EVENT.trigger('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, EVENT) {
|
|
jQuery('body').on('click', 'div.navigation ul.pagination li a', function(event) {
|
|
let pager = jQuery(formSelector + " input#page");
|
|
if (pager.length === 0) {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
let urlParts = jQuery(this).attr('href').split('/');
|
|
let page = urlParts[urlParts.length-1];
|
|
pager.val(page);
|
|
pager.trigger('change');
|
|
EVENT.trigger('pagination-change');
|
|
return false;
|
|
});
|
|
|
|
}
|
|
|
|
hide() {
|
|
jQuery(this.getSelector() + ' .dropdown-toggle').dropdown('toggle');
|
|
}
|
|
|
|
/**
|
|
* Triggers an event, that everyone can listen for.
|
|
*/
|
|
triggerChange() {
|
|
this.getContainer().getPlugin('event').trigger('toolbar-change');
|
|
}
|
|
|
|
/**
|
|
* Returns the CSS selector to target the toolbar form.
|
|
*
|
|
* @returns {string}
|
|
*/
|
|
getSelector() {
|
|
return this.formSelector;
|
|
}
|
|
|
|
}
|