refactored javascript to ES6 classes (#759)

This commit is contained in:
Kevin Papst
2019-05-06 15:22:52 +02:00
committed by GitHub
parent 80af3fb010
commit 98f386dad6
37 changed files with 1302 additions and 721 deletions

View File

@@ -0,0 +1,34 @@
/*
* 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 KimaiAPI extends KimaiPlugin {
getId() {
return 'api';
}
get(url, callback) {
jQuery.ajax({
url: url,
headers: {
'X-AUTH-SESSION': true,
'Content-Type':'application/json'
},
method: 'GET',
dataType: 'json',
success: callback
});
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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] KimaiActiveRecordsDuration: activate the updates for all active timesheet records on this page
*/
import moment from 'moment';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiActiveRecordsDuration extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
init() {
this.updateRecords();
this.registerUpdates(10000);
}
registerUpdates(interval) {
let self = this;
this._updatesHandler = setInterval(
function() {
self.updateRecords();
},
interval
);
}
unregisterUpdates() {
clearInterval(this._updatesHandler);
}
updateRecords() {
let durations = [];
for(let record of document.querySelectorAll(this.selector)) {
const since = record.getAttribute('data-since');
const format = record.getAttribute('data-format');
const duration = KimaiActiveRecordsDuration._getDuration(since, format);
if (record.getAttribute('data-title') !== null) {
durations.push(duration);
}
record.textContent = duration;
}
if (durations.length === 0) {
return this;
}
let title = durations.shift();
let prefix = ' | ';
for (let duration of durations.slice(0, 2)) {
title += prefix + duration;
}
document.title = title;
return this;
}
static _getDuration(since, format) {
const duration = moment.duration(moment(new Date()).diff(moment(since)));
let hours = parseInt(duration.asHours()).toString();
let minutes = duration.minutes();
let seconds = duration.seconds();
// special case for hours, as they can overflow the 24h barrier - Kimai does not support days as duration unit
if (hours.length === 1) {
hours = '0' + hours;
}
return format.replace('%h', hours).replace('%m', ('0'+minutes).substr(-2)).replace('%s', ('0'+seconds).substr(-2));
}
}

View File

@@ -0,0 +1,137 @@
/*
* 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] KimaiAjaxModalForm
*
* allows to assign the given selector to any element, which then is used as click-handler:
* opening a modal with the content from the URL given in the elements 'data-href' or 'href' attribute
*/
import jQuery from 'jquery';
import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";
export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableRow {
constructor(selector) {
super();
this.selector = selector;
}
init() {
const self = this;
this._addClickHandlerReducedInTableRow(this.selector, function(href) {
jQuery.ajax({
url: href,
success: function(html) {
self._openFormInModal(html);
},
error: function(xhr, err) {
console.log('Failed opening modal', err);
window.location = href;
}
});
});
}
_openFormInModal(html) {
const self = this;
// the modal that we use to render the form in
let formIdentifier = '#remote_form_modal .modal-content form';
let flashErrorIdentifier = 'div.alert-error';
let form = jQuery(formIdentifier);
let remoteModal = jQuery('#remote_form_modal');
// will be (re-)activated later
form.off('submit');
// load new form from given content
if (jQuery(html).find('#form_modal .modal-content').length > 0 ) {
// switch classes, in case the modal type changed
remoteModal.on('hidden.bs.modal', function () {
if (remoteModal.hasClass('modal-danger')) {
remoteModal.removeClass('modal-danger');
}
});
if (jQuery(html).find('#form_modal').hasClass('modal-danger')) {
remoteModal.addClass('modal-danger');
}
jQuery('#remote_form_modal .modal-content').replaceWith(
jQuery(html).find('#form_modal .modal-content')
);
// activate new loaded widgets
self.getContainer().getPlugin('date-time-picker').activateDateTimePicker(formIdentifier);
}
// show error flash messages
if (jQuery(html).find(flashErrorIdentifier).length > 0) {
jQuery('#remote_form_modal .modal-body').prepend(
jQuery(html).find(flashErrorIdentifier)
);
}
// -----------------------------------------------------------------------
// a fix for firefox focus problems with datepicker in modal
// see https://github.com/kevinpapst/kimai2/issues/618
let enforceModalFocusFn = jQuery.fn.modal.Constructor.prototype.enforceFocus;
jQuery.fn.modal.Constructor.prototype.enforceFocus = function() {};
remoteModal.on('hidden.bs.modal', function () {
jQuery.fn.modal.Constructor.prototype.enforceFocus = enforceModalFocusFn;
});
// -----------------------------------------------------------------------
// workaround for autofocus attribute, as the modal "steals" it
remoteModal.on('shown.bs.modal', function () {
jQuery(this).find('input[type=text],textarea,select').filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
});
remoteModal.modal('show');
// the new form that was loaded via ajax
form = jQuery(formIdentifier);
// click handler for modal save button, to send forms via ajax
form.on('submit', function(event){
let btn = jQuery(formIdentifier + ' button[type=submit]').button('loading');
event.preventDefault();
event.stopPropagation();
jQuery.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize(),
success: function(html) {
btn.button('reset');
let hasFieldError = jQuery(html).find('#form_modal .modal-content .has-error').length > 0;
let hasFormError = jQuery(html).find('#form_modal .modal-content ul.list-unstyled li.text-danger').length > 0;
let hasFlashError = jQuery(html).find(flashErrorIdentifier).length > 0;
if (hasFieldError || hasFormError || hasFlashError) {
self._openFormInModal(html);
} else {
self.getContainer().getPlugin('datatable').reload();
remoteModal.modal('hide');
}
return false;
},
error: function(xhr, err) {
console.log('Failed submitting modal form', err);
// FIXME problem in google and 500 error, keeps on submitting...
// what else could we do? submitting again at least gives us the opportunity to see errors,
// which maybe would be hidden otherwise... this one is totally up for discussion!
form.submit();
}
});
});
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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] KimaiAlternativeLinks
*
* allows to assign the given selector to any element, which then is used as click-handler
* redirecting to the URL given in the elements 'data-href' or 'href' attribute
*/
import jQuery from 'jquery';
import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";
export default class KimaiAlternativeLinks extends KimaiClickHandlerReducedInTableRow {
constructor(selector) {
super();
this.selector = selector;
}
init() {
this._addClickHandlerReducedInTableRow(this.selector, function(href) {
window.location = href;
});
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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] KimaiClickHandlerReducedInTableRow: abstract class
*/
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiClickHandlerReducedInTableRow extends KimaiPlugin {
_addClickHandlerReducedInTableRow(selector, callback)  {
jQuery('body').on('click', selector, function(event) {
// just in case an inner element is editable, than this should not be triggered
if (event.target.parentNode.isContentEditable || event.target.isContentEditable) {
return;
}
// handles the "click" on table rows to open an entry for editing: when a button within a row is clicked,
// we don't want the table row event to be processed - so we intercept it
let target = event.target;
if (event.currentTarget.matches('tr')) {
while (!target.matches('body')) {
if (target.matches('a') || target.matches ('button')) {
return;
}
target = target.parentNode;
}
}
event.preventDefault();
event.stopPropagation();
let href = jQuery(this).attr('data-href');
if (!href) {
href = jQuery(this).attr('href');
}
callback(href);
});
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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] KimaiDatatable: handles functionality for the datatable
*/
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiDatatable extends KimaiPlugin {
getId() {
return 'datatable';
}
init() {
const self = this;
document.addEventListener('KimaiDatatableRequestReload', function() {
self.reload();
});
}
reload() {
let form = jQuery('.toolbar form');
let loading = '<div class="overlay"><i class="fas fa-sync fa-spin"></i></div>';
jQuery('section.content').append(loading);
// remove the empty fields to prevent errors
let formData = jQuery('.toolbar form :input')
.filter(function(index, element) {
return jQuery(element).val() != '';
})
.serialize();
jQuery.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: formData,
success: function(html) {
jQuery('section.content').replaceWith(
jQuery(html).find('section.content')
);
},
error: function(xhr, err) {
form.submit();
}
});
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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] KimaiDatatableColumnView: manages the visibility of data-table columns in cookies
*/
import Cookies from 'js-cookie';
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiDatatableColumnView extends KimaiPlugin {
constructor(dataAttribute) {
super();
this.dataAttribute = dataAttribute;
}
getId() {
return 'datatable-column-visibility';
}
init() {
let dataTable = document.querySelector('[' + this.dataAttribute + ']');
if (dataTable === null) {
return;
}
this.id = dataTable.getAttribute(this.dataAttribute);
this.modal = document.getElementById('modal_' + this.id);
this.bindButtons();
}
bindButtons() {
let self = this;
this.modal.querySelector('button[data-type=save]').addEventListener('click', function() {
self.saveVisibility();
});
this.modal.querySelector('button[data-type=reset]').addEventListener('click', function() {
self.resetVisibility();
});
for (let checkbox of this.modal.querySelectorAll('form input[type=checkbox]')) {
checkbox.addEventListener('click', function () {
self.changeVisibility(checkbox.getAttribute('name'));
});
}
}
saveVisibility() {
const form = this.modal.getElementsByTagName('form')[0];
let settings = {};
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
settings[checkbox.getAttribute('name')] = checkbox.checked;
}
Cookies.set(form.getAttribute('name'), JSON.stringify(settings), {expires: 365});
jQuery(this.modal).modal('toggle');
}
resetVisibility() {
const form = this.modal.getElementsByTagName('form')[0];
Cookies.remove(form.getAttribute('name'));
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
if (!checkbox.checked) {
checkbox.click();
}
}
jQuery(this.modal).modal('toggle');
}
changeVisibility(columnName) {
const table = document.getElementById('datatable_' + this.id).getElementsByClassName('dataTable')[0];
let column = 0;
let foundColumn = false;
for (let columnElement of table.getElementsByTagName('th')) {
if (columnElement.getAttribute('data-field') === columnName) {
foundColumn = true;
break;
}
column++;
}
if (!foundColumn) {
console.error('Could not find column: ' + columnName);
return;
}
for (let rowElement of table.getElementsByTagName('tr')) {
rowElement.children[column].classList.toggle('hidden');
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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] KimaiDatePicker: single date selects (currently unused)
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiDatePicker extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'date-picker';
}
init() {
this.activateDatePicker(this.selector);
}
activateDatePicker(selector) {
let translator = this.getContainer().getTranslation();
jQuery(selector + ' input[data-datepickerenable="on"]').each(function(index) {
let localeFormat = jQuery(this).data('format');
jQuery(this).daterangepicker({
singleDatePicker: true,
showDropdowns: true,
autoUpdateInput: false,
locale: {
format: localeFormat,
firstDay: 1,
applyLabel: translator.get('apply'),
cancelLabel: translator.get('cancel'),
customRangeLabel: translator.get('customRange')
}
});
jQuery(this).on('apply.daterangepicker', function(ev, picker) {
jQuery(this).val(picker.startDate.format(localeFormat));
jQuery(this).trigger("change");
});
});
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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] KimaiDateRangePicker: activate the (daterange picker) compound field in toolbar
*/
import moment from 'moment';
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiDateRangePicker extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'date-range-picker';
}
init() {
this.activateDateRangePicker(this.selector);
}
activateDateRangePicker(selector) {
let translator = this.getContainer().getTranslation();
jQuery(selector + ' input[data-daterangepickerenable="on"]').each(function(index) {
let localeFormat = jQuery(this).data('format');
let separator = jQuery(this).data('separator');
let rangesList = {};
rangesList[translator.get('today')] = [moment(), moment()];
rangesList[translator.get('yesterday')] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
rangesList[translator.get('thisWeek')] = [moment().startOf('week'), moment().endOf('week')];
rangesList[translator.get('lastWeek')] = [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')];
rangesList[translator.get('thisMonth')] = [moment().startOf('month'), moment().endOf('month')];
rangesList[translator.get('lastMonth')] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
rangesList[translator.get('thisYear')] = [moment().startOf('year'), moment().endOf('year')];
rangesList[translator.get('lastYear')] = [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')];
jQuery(this).daterangepicker({
showDropdowns: true,
autoUpdateInput: false,
autoApply: false,
linkedCalendars: false,
locale: {
separator: separator,
format: localeFormat,
firstDay: 1,
applyLabel: translator.get('apply'),
cancelLabel: translator.get('cancel'),
customRangeLabel: translator.get('customRange')
},
ranges: rangesList,
alwaysShowCalendars: true
});
jQuery(this).on('apply.daterangepicker', function(ev, picker) {
jQuery(this).val(picker.startDate.format(localeFormat) + ' - ' + picker.endDate.format(localeFormat));
jQuery(this).trigger("change");
});
});
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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] KimaiDateTimePicker: activate the (datetime picker) field in timesheet edit dialog
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiDateTimePicker extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'date-time-picker';
}
init() {
this.activateDateTimePicker(this.selector);
}
activateDateTimePicker(selector) {
let translator = this.getContainer().getTranslation();
let configuration = this.getContainer().getConfiguration();
jQuery(selector + ' input[data-datetimepicker="on"]').each(function(index) {
let localeFormat = jQuery(this).data('format');
jQuery(this).daterangepicker({
singleDatePicker: true,
timePicker: true,
timePicker24Hour: configuration.get('twentyFourHours'),
showDropdowns: true,
autoUpdateInput: false,
locale: {
format: localeFormat,
firstDay: 1,
applyLabel: translator.get('apply'),
cancelLabel: translator.get('cancel'),
customRangeLabel: translator.get('customRange')
}
});
jQuery(this).on('apply.daterangepicker', function(ev, picker) {
jQuery(this).val(picker.startDate.format(localeFormat));
jQuery(this).trigger("change");
});
});
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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] KimaiJqueryPluginInitializer: initialize jQuery plugins
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiJqueryPluginInitializer extends KimaiPlugin {
init() {
// activate the dropdown functionality
jQuery('.dropdown-toggle').dropdown();
// activate the tooltip functionality
jQuery('[data-toggle="tooltip"]').tooltip();
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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] KimaiPauseRecord
*
* allows to pause records
* THIS IS JUST A DRAFT FOR THE DOM, IT IS NOT SUPPORTED IN KIMAI ITSELF!
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiPauseRecord extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
init() {
this.activate(this.selector);
}
activate(selector) {
jQuery(selector + ' .pull-left i').hover(function () {
let link = jQuery(this).parents('a');
link.attr('href', link.attr('href').replace('/stop', '/pause'));
jQuery(this).removeClass('fa-stop-circle').addClass('fa-pause-circle').addClass('text-orange');
},function () {
let link = jQuery(this).parents('a');
link.attr('href', link.attr('href').replace('/pause', '/stop'));
jQuery(this).removeClass('fa-pause-circle').removeClass('text-orange').addClass('fa-stop-circle');
});
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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] KimaiSelectDataAPI: <select> boxes with dynamic data from API
*/
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiSelectDataAPI extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'select-data-api';
}
init() {
this.activateApiSelects(this.selector);
}
activateApiSelects(selector) {
const self = this;
const API = this.getContainer().getPlugin('api');
jQuery('body').on('change', selector, function(event) {
let apiUrl = jQuery(this).attr('data-api-url').replace('-s-', jQuery(this).val());
const targetSelect = '#' + jQuery(this).attr('data-related-select');
// if the related target select does not exist, we do not need to load the related data
if (jQuery(targetSelect).length === 0) {
return;
}
if (jQuery(this).val() === '') {
if (jQuery(this).attr('data-empty-url') === undefined) {
self._updateSelect(targetSelect, {});
jQuery(targetSelect).attr('disabled', 'disabled');
return;
}
apiUrl = jQuery(this).attr('data-empty-url').replace('-s-', jQuery(this).val());
}
jQuery(targetSelect).removeAttr('disabled');
API.get(apiUrl, function(data){
self._updateSelect(targetSelect, data);
});
});
}
_updateSelect(selectName, data) {
let select = jQuery(selectName);
let emptyOption = jQuery(selectName + ' option[value=""]');
select.find('option').remove().end().find('optgroup').remove().end();
if (emptyOption.length !== 0) {
select.append('<option value="">' + emptyOption.text() + '</option>');
}
jQuery.each(data, function(i, obj) {
select.append('<option value="' + obj.id + '">' + obj.name + '</option>');
});
// if we don't trigger the change, the other selects won't be resetted
select.trigger('change');
// if the beta test kimai.theme.select_type is active, this will tell the selects to refresh
jQuery('.selectpicker').selectpicker('refresh');
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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] KimaiThemeInitializer: initialize theme functionality
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiThemeInitializer extends KimaiPlugin {
init() {
this.registerAutomaticAlertRemove('div.alert-success', 5000);
}
/**
* auto hide success messages, as they are just meant as user feedback and not as a permanent information
*
* @param {string} selector
* @param {integer} interval
*/
registerAutomaticAlertRemove(selector, interval) {
const self = this;
this._alertRemoveHandler = setInterval(
function() {
self.hideAlert(selector);
},
interval
);
}
unregisterAutomaticAlertRemove() {
clearInterval(this._alertRemoveHandler);
}
/**
* @param {string} selector
*/
hideAlert(selector) {
jQuery(selector).alert('close');
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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 {
init() {
const self = this;
// This catches all clicks on the pagination and prevents the default action, as we want to relad the page via JS
jQuery('body').on('click', 'div.navigation ul.pagination li a', function(event) {
let pager = jQuery(".toolbar form input[name='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');
return false;
});
// Reset the page if any other value is 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('.toolbar form input').change(function (event) {
switch (event.target.id) {
case 'page':
break;
default:
jQuery('.toolbar form input#page').val(1);
}
self._reloadDatatable();
});
jQuery('.toolbar form select').change(function (event) {
let reload = true;
switch (event.target.id) {
case 'customer':
if (jQuery('.toolbar form select#project').length > 0) {
reload = false;
}
break;
case 'project':
if (jQuery('.toolbar form select#activity').length > 0) {
reload = false;
}
break;
}
jQuery('.toolbar form input#page').val(1);
if (reload) {
self._reloadDatatable();
}
});
}
_reloadDatatable() {
this.getContainer().getPlugin('datatable').reload();
}
}