From 98f386dad64184e45786fee4e3a029b4528ff6a4 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Mon, 6 May 2019 15:22:52 +0200 Subject: [PATCH] refactored javascript to ES6 classes (#759) --- assets/app.js | 5 +- assets/js/KimaiActiveRecordsDuration.js | 90 ---- assets/js/KimaiConfiguration.js | 26 ++ assets/js/KimaiContainer.js | 91 ++++ assets/js/KimaiDatatableColumnView.js | 111 ----- assets/js/KimaiLoader.js | 91 ++++ assets/js/KimaiPlugin.js | 53 +++ assets/js/KimaiTranslation.js | 26 ++ assets/js/KimaiWebLoader.js | 33 ++ assets/js/kimai.js | 400 ------------------ assets/js/plugins/KimaiAPI.js | 34 ++ .../js/plugins/KimaiActiveRecordsDuration.js | 82 ++++ assets/js/plugins/KimaiAjaxModalForm.js | 137 ++++++ assets/js/plugins/KimaiAlternativeLinks.js | 31 ++ .../KimaiClickHandlerReducedInTableRow.js | 48 +++ assets/js/plugins/KimaiDatatable.js | 55 +++ assets/js/plugins/KimaiDatatableColumnView.js | 95 +++++ assets/js/plugins/KimaiDatePicker.js | 54 +++ assets/js/plugins/KimaiDateRangePicker.js | 71 ++++ assets/js/plugins/KimaiDateTimePicker.js | 58 +++ .../plugins/KimaiJqueryPluginInitializer.js | 24 ++ assets/js/plugins/KimaiPauseRecord.js | 41 ++ assets/js/plugins/KimaiSelectDataAPI.js | 81 ++++ assets/js/plugins/KimaiThemeInitializer.js | 48 +++ assets/js/plugins/KimaiToolbar.js | 73 ++++ assets/js/toolbar.js | 61 --- assets/sass/navbar.scss | 3 + package.json | 1 + public/build/app.css | 2 +- public/build/app.js | 39 +- public/build/manifest.json | 4 +- templates/base.html.twig | 38 +- templates/macros/datatables.html.twig | 7 +- templates/navbar/active-entries.html.twig | 1 - templates/user/index.html.twig | 2 +- webpack.config.js | 2 +- yarn.lock | 5 + 37 files changed, 1302 insertions(+), 721 deletions(-) delete mode 100644 assets/js/KimaiActiveRecordsDuration.js create mode 100644 assets/js/KimaiConfiguration.js create mode 100644 assets/js/KimaiContainer.js delete mode 100644 assets/js/KimaiDatatableColumnView.js create mode 100644 assets/js/KimaiLoader.js create mode 100644 assets/js/KimaiPlugin.js create mode 100644 assets/js/KimaiTranslation.js create mode 100644 assets/js/KimaiWebLoader.js delete mode 100644 assets/js/kimai.js create mode 100644 assets/js/plugins/KimaiAPI.js create mode 100644 assets/js/plugins/KimaiActiveRecordsDuration.js create mode 100644 assets/js/plugins/KimaiAjaxModalForm.js create mode 100644 assets/js/plugins/KimaiAlternativeLinks.js create mode 100644 assets/js/plugins/KimaiClickHandlerReducedInTableRow.js create mode 100644 assets/js/plugins/KimaiDatatable.js create mode 100644 assets/js/plugins/KimaiDatatableColumnView.js create mode 100644 assets/js/plugins/KimaiDatePicker.js create mode 100644 assets/js/plugins/KimaiDateRangePicker.js create mode 100644 assets/js/plugins/KimaiDateTimePicker.js create mode 100644 assets/js/plugins/KimaiJqueryPluginInitializer.js create mode 100644 assets/js/plugins/KimaiPauseRecord.js create mode 100644 assets/js/plugins/KimaiSelectDataAPI.js create mode 100644 assets/js/plugins/KimaiThemeInitializer.js create mode 100644 assets/js/plugins/KimaiToolbar.js delete mode 100644 assets/js/toolbar.js diff --git a/assets/app.js b/assets/app.js index dfd412c5..c8f5f0bb 100644 --- a/assets/app.js +++ b/assets/app.js @@ -62,9 +62,6 @@ require('fullcalendar/dist/fullcalendar.min.css'); require('chart.js/dist/Chart.min'); // ------ Kimai itself ------ -require('./js/kimai.js'); -require('./js/KimaiDatatableColumnView.js'); -require('./js/KimaiActiveRecordsDuration.js'); -require('./js/toolbar.js'); +require('./js/KimaiWebLoader.js'); require('./images/default_avatar.png'); require('./images/signature.png'); diff --git a/assets/js/KimaiActiveRecordsDuration.js b/assets/js/KimaiActiveRecordsDuration.js deleted file mode 100644 index a05ed3c2..00000000 --- a/assets/js/KimaiActiveRecordsDuration.js +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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: updates active records on your personal timesheet view - */ - -// Following the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define(['moment'], function (moment) { - return (root.KimaiActiveRecordsDuration = factory(moment)); - }); - } else if (typeof module === 'object' && module.exports) { - let moment = (typeof window != 'undefined') ? window.moment : undefined; - if (!moment) { - moment = require('moment'); - } - module.exports = factory(moment); - } else { - root.KimaiActiveRecordsDuration = factory(root.moment); - } -}(typeof self !== 'undefined' ? self : this, function (moment) { - - class KimaiActiveRecordsDuration { - constructor(selector) { - this.selector = selector; - } - - registerUpdates(timeout) { - let self = this; - window.setTimeout( - function() { - self.updateRecords().registerUpdates(timeout); - }, - timeout - ); - } - - 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 = this.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; - } - - getDuration(since, format) { - let duration = moment.duration(moment(new Date()).diff(moment(since))); - - let hours = parseInt(duration.asHours()); - let minutes = duration.minutes(); - let seconds = duration.seconds(); - let formatted = ''; - - // 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)); - } - } - - return KimaiActiveRecordsDuration; - -})); diff --git a/assets/js/KimaiConfiguration.js b/assets/js/KimaiConfiguration.js new file mode 100644 index 00000000..05548b91 --- /dev/null +++ b/assets/js/KimaiConfiguration.js @@ -0,0 +1,26 @@ +/* + * 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] KimaiConfiguration: handling all configuration and runtime settings + */ + +export default class KimaiConfiguration { + + constructor(configurations) { + this._configurations = configurations; + } + + get(name) { + return this._configurations[name]; + } + + has(name) { + return name in this._configurations; + } + +} diff --git a/assets/js/KimaiContainer.js b/assets/js/KimaiContainer.js new file mode 100644 index 00000000..821f5233 --- /dev/null +++ b/assets/js/KimaiContainer.js @@ -0,0 +1,91 @@ +/* + * 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] KimaiContainer + * + * ServiceContainer for Kimai + */ + +import KimaiConfiguration from './KimaiConfiguration'; +import KimaiTranslation from './KimaiTranslation'; +import KimaiPlugin from './KimaiPlugin'; + +export default class KimaiContainer { + + /** + * Create a new Container with the given configurations and translations. + * + * @param {Object} configuration + * @param {Object} translation + */ + constructor(configuration, translation) { + if (!(configuration instanceof KimaiConfiguration)) { + throw new Error('Configuration needs to a KimaiConfiguration instance'); + } + this._configuration = configuration; + + if (!(translation instanceof KimaiTranslation)) { + throw new Error('Configuration needs to a KimaiTranslation instance'); + } + this._translation = translation; + this._plugins = []; + } + + /** + * Register a new Plugin. + * + * @param {KimaiPlugin} plugin + * @returns {KimaiPlugin} + */ + registerPlugin(plugin) { + if (!(plugin instanceof KimaiPlugin)) { + throw new Error('Invalid plugin given, needs to be a KimaiPlugin instance'); + } + + plugin.setContainer(this); + + this._plugins.push(plugin); + + return plugin; + } + + /** + * @param {string} name + * @returns {KimaiPlugin} + */ + getPlugin(name) { + for (let plugin of this._plugins) { + if (plugin.getId() !== null && plugin.getId() === name) { + return plugin; + } + } + throw new Error('Unknown plugin: ' + name); + } + + /** + * @returns {Array} + */ + getPlugins() { + return this._plugins; + } + + /** + * @returns {KimaiTranslation} + */ + getTranslation() { + return this._translation; + } + + /** + * @returns {KimaiConfiguration} + */ + getConfiguration() { + return this._configuration; + } + +} diff --git a/assets/js/KimaiDatatableColumnView.js b/assets/js/KimaiDatatableColumnView.js deleted file mode 100644 index dd1362d2..00000000 --- a/assets/js/KimaiDatatableColumnView.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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'; - -// Following the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define(['jquery'], function (jquery) { - return (root.KimaiDatatableColumnView = factory(jquery)); - }); - } else if (typeof module === 'object' && module.exports) { - let jQuery = (typeof window != 'undefined') ? window.jQuery : undefined; - if (!jQuery) { - jQuery = require('jquery'); - if (!jQuery.fn) { - jQuery.fn = {}; - } - } - module.exports = factory(jQuery); - } else { - root.KimaiDatatableColumnView = factory(root.jQuery); - } -}(typeof self !== 'undefined' ? self : this, function ($) { - - /** - * This is my first approach on ES6, so it can be optimized. - * Please: show your JS skills and teach a PHP backend developer how to do it properly, sent a PR! - * - * BTW: I tried to get rid of it, but jQuery is still required for the bootstrap modal ... - */ - class KimaiDatatableColumnView { - - constructor(selector) { - this.id = selector; - this.modal = document.getElementById('modal_' + selector); - 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}); - $(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(); - } - } - $(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'); - } - } - - } - - return KimaiDatatableColumnView; - -})); - diff --git a/assets/js/KimaiLoader.js b/assets/js/KimaiLoader.js new file mode 100644 index 00000000..168e2d7a --- /dev/null +++ b/assets/js/KimaiLoader.js @@ -0,0 +1,91 @@ +/* + * 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] KimaiLoader: bootstrap the application and all plugins + */ + +import moment from 'moment'; +import KimaiTranslation from "./KimaiTranslation"; +import KimaiConfiguration from "./KimaiConfiguration"; +import KimaiContainer from "./KimaiContainer"; +import KimaiActiveRecordsDuration from './plugins/KimaiActiveRecordsDuration.js'; +import KimaiDatatableColumnView from './plugins/KimaiDatatableColumnView.js'; +import KimaiThemeInitializer from "./plugins/KimaiThemeInitializer"; +import KimaiJqueryPluginInitializer from "./plugins/KimaiJqueryPluginInitializer"; +import KimaiDateRangePicker from "./plugins/KimaiDateRangePicker"; +import KimaiDatatable from "./plugins/KimaiDatatable"; +import KimaiToolbar from "./plugins/KimaiToolbar"; +import KimaiAPI from "./plugins/KimaiAPI"; +import KimaiSelectDataAPI from "./plugins/KimaiSelectDataAPI"; +import KimaiDateTimePicker from "./plugins/KimaiDateTimePicker"; +import KimaiAlternativeLinks from "./plugins/KimaiAlternativeLinks"; +import KimaiAjaxModalForm from "./plugins/KimaiAjaxModalForm"; + +export default class KimaiLoader { + + constructor(configurations, translations) { + const defaultTranslations = { + today: 'Today', + yesterday: 'Yesterday', + apply: 'Apply', + cancel: 'Cancel', + thisWeek: 'This week', + lastWeek: 'Last week', + thisMonth: 'This month', + lastMonth: 'Last month', + thisYear: 'This year', + lastYear: 'Last year', + customRange: 'Custom range', + }; + + translations = Object.assign(defaultTranslations, translations); + + const defaultConfigurations = { + locale: 'en', + twentyFourHours: true + }; + + configurations = Object.assign(defaultConfigurations, configurations); + + // set the current locale for all javascript components + moment.locale(configurations['locale']); + + const kimai = new KimaiContainer( + new KimaiConfiguration(configurations), + new KimaiTranslation(translations) + ); + + kimai.registerPlugin(new KimaiAPI()); + kimai.registerPlugin(new KimaiActiveRecordsDuration('[data-since]')); + kimai.registerPlugin(new KimaiDatatableColumnView('data-column-visibility')); + kimai.registerPlugin(new KimaiThemeInitializer()); + kimai.registerPlugin(new KimaiJqueryPluginInitializer()); + kimai.registerPlugin(new KimaiDateRangePicker('.content-wrapper')); + kimai.registerPlugin(new KimaiDateTimePicker('.content-wrapper')); + kimai.registerPlugin(new KimaiDatatable()); + kimai.registerPlugin(new KimaiToolbar()); + kimai.registerPlugin(new KimaiSelectDataAPI('select[data-related-select]')); + kimai.registerPlugin(new KimaiAlternativeLinks('.alternative-link')); + kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form')); + //kimai.registerPlugin(new KimaiPauseRecord('li.messages-menu ul.menu li')); + + // notify all listeners that Kimai plugins can now be registered + this._sendEvent('kimai.pluginRegister'); + + // initialize all plugins + kimai.getPlugins().map(plugin => { plugin.init(); }); + + // notify all listeners that Kimai is now ready to be used + this._sendEvent('kimai.initialized'); + } + + _sendEvent(name) { + document.dispatchEvent(new Event(name)); + } + +} diff --git a/assets/js/KimaiPlugin.js b/assets/js/KimaiPlugin.js new file mode 100644 index 00000000..4d2662eb --- /dev/null +++ b/assets/js/KimaiPlugin.js @@ -0,0 +1,53 @@ +/* + * 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] KimaiPlugin: base class for all plugins + */ + +import KimaiContainer from "./KimaiContainer"; + +export default class KimaiPlugin { + + /** + * Overwrite this method to initialize your plugin. + * + * It is called AFTER setContainer() and AFTER DOMContentLoaded was fired. + * You don't have access to the container before this method! + */ + init() { + } + + /** + * If you return an ID, you indicate that your plugin can be used by other plugins. + * + * @returns {string|null} + */ + getId() { + return null; + } + + /** + * @param {KimaiContainer} core + */ + setContainer(core) { + if (!(core instanceof KimaiContainer)) { + throw new Error('Plugin was given an invalid KimaiContainer'); + } + this._core = core; + } + + /** + * This function returns null, if xou call it BEFORE init(). + * + * @returns {KimaiContainer} + */ + getContainer() { + return this._core; + } + +} diff --git a/assets/js/KimaiTranslation.js b/assets/js/KimaiTranslation.js new file mode 100644 index 00000000..7ed3ed1a --- /dev/null +++ b/assets/js/KimaiTranslation.js @@ -0,0 +1,26 @@ +/* + * 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] KimaiTranslation: handling translation strings + */ + +export default class KimaiTranslation { + + constructor(translations) { + this._translations = translations; + } + + get(name) { + return this._translations[name]; + } + + has(name) { + return name in this._translations; + } + +} diff --git a/assets/js/KimaiWebLoader.js b/assets/js/KimaiWebLoader.js new file mode 100644 index 00000000..b99c7149 --- /dev/null +++ b/assets/js/KimaiWebLoader.js @@ -0,0 +1,33 @@ +/* + * This file is part of the Kimai time-tracking app. + * + * Main JS application file for Kimai 2. This file should be included in all pages. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/*! + * [KIMAI] Wrapper class for loading Kimai app in browser script scope + */ + +import KimaiLoader from "./KimaiLoader"; + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define([], function () { + return (root.KimaiWebLoader = factory()); + }); + } else if (typeof module === 'object' && module.exports) { + module.exports = factory(); + } else { + root.KimaiWebLoader = factory(); + } +}(typeof self !== 'undefined' ? self : this, function () { + + class KimaiWebLoader extends KimaiLoader { + } + + return KimaiWebLoader; + +})); diff --git a/assets/js/kimai.js b/assets/js/kimai.js deleted file mode 100644 index 0b33db11..00000000 --- a/assets/js/kimai.js +++ /dev/null @@ -1,400 +0,0 @@ -/* - * This file is part of the Kimai time-tracking app. - * - * Main JS application file for Kimai 2. This file should be included in all pages. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/*! - * [KIMAI] Main JS application file for Kimai 2 - */ - -/** global: jQuery */ -/** global: moment */ - -if (typeof jQuery === 'undefined') { - throw new Error('Kimai requires jQuery'); -} - -if (typeof moment === 'undefined') { - throw new Error('Kimai requires moment.js'); -} - -/* kimai - * - * @type Object - * @description $.kimai is the main object for the template's app. - * It's used for implementing functions and options related - * to the template. Keeping everything wrapped in an object - * prevents conflict with other plugins and is a better - * way to organize our code. - */ -$.kimai = {}; - -$(function() { -"use strict"; - - $.kimai = { - init: function(options) { - if (typeof options !== 'undefined') { - $.kimai.settings = $.extend({}, $.kimai.defaults, options); - } - - // set the current locale for all javascript components - moment.locale($.kimai.settings['locale']); - - // activate the dropdown functionality - $('.dropdown-toggle').dropdown(); - // activate the tooltip functionality - $('[data-toggle="tooltip"]').tooltip(); - // auto hide success messages, as they are just meant as user feedback and not as a permanent information - this.activateAutomaticAlertRemove('div.alert-success', 5000); - // activate the (daterangepicker) compound field in toolbar - this.activateDateRangePicker('.content-wrapper'); - // single select boxes in toolbars - this.activateDatePicker('.content-wrapper'); - // edit timesheet - date with time - this.activateDateTimePicker('.content-wrapper'); - // some actions can be performed in a modal for a better UX - this.activateAjaxFormInModal('.modal-ajax-form'); - // activate select boxes that load dynamic data via API - this.activateApiSelects('select[data-related-select]'); - }, - reloadDatatableWithToolbarFilter: function() { - // TODO check if toolbar form is present, if not, reload current URL - var $form = $('.toolbar form'); - var loading = '
'; - $('section.content').append(loading); - - // remove the empty fields to prevent errors - var formData = $('.toolbar form :input') - .filter(function(index, element) { - return $(element).val() != ''; - }) - .serialize(); - - $.ajax({ - url: $form.attr('action'), - type: $form.attr('method'), - data: formData, - success: function(html) { - $('section.content').replaceWith( - $(html).find('section.content') - ); - }, - error: function(xhr, err) { - $form.submit(); - } - }); - }, - pauseRecord: function(selector) { - $(selector + ' .pull-left i').hover(function () { - var link = $(this).parents('a'); - link.attr('href', link.attr('href').replace('/stop', '/pause')); - $(this).removeClass('fa-stop-circle').addClass('fa-pause-circle').addClass('text-orange'); - },function () { - var link = $(this).parents('a'); - link.attr('href', link.attr('href').replace('/pause', '/stop')); - $(this).removeClass('fa-pause-circle').removeClass('text-orange').addClass('fa-stop-circle'); - }); - }, - activateAutomaticAlertRemove(selector, mseconds) { - setTimeout( - function() { - $(selector).alert('close'); - }, - mseconds - ); - }, - activateApiSelects: function(selector) { - const self = this; - $('body').on('change', selector, function(event) { - let apiUrl = $(this).attr('data-api-url').replace('-s-', $(this).val()); - const targetSelect = '#' + $(this).attr('data-related-select'); - - // if the related target select does not exist, we do not need to load the related data - if ($(targetSelect).length === 0) { - return; - } - - if ($(this).val() === '') { - if ($(this).attr('data-empty-url') === undefined) { - self.updateSelect(targetSelect, {}); - $(targetSelect).attr('disabled', 'disabled'); - return; - } - apiUrl = $(this).attr('data-empty-url').replace('-s-', $(this).val()); - } - - $(targetSelect).removeAttr('disabled'); - - $.ajax({ - url: apiUrl, - headers: { - 'X-AUTH-SESSION': true, - 'Content-Type':'application/json' - }, - method: 'GET', - dataType: 'json', - success: function(data){ - self.updateSelect(targetSelect, data); - } - }); - }); - }, - updateSelect: function(selectName, data) { - var $select = $(selectName); - var $emptyOption = $(selectName + ' option[value=""]'); - - $select.find('option').remove().end().find('optgroup').remove().end(); - - if ($emptyOption.length !== 0) { - $select.append(''); - } - - $.each(data, function(i, obj) { - $select.append(''); - }); - - // 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 - $('.selectpicker').selectpicker('refresh'); - }, - activateDatePicker: function(selector) { - $(selector + ' input[data-datepickerenable="on"]').each(function(index) { - var localeFormat = $(this).data('format'); - $(this).daterangepicker({ - singleDatePicker: true, - showDropdowns: true, - autoUpdateInput: false, - locale: { - format: localeFormat, - firstDay: 1, - applyLabel: $.kimai.settings['apply'], - cancelLabel: $.kimai.settings['cancel'], - customRangeLabel: $.kimai.settings['customRange'] - } - }); - - $(this).on('apply.daterangepicker', function(ev, picker) { - $(this).val(picker.startDate.format(localeFormat)); - $(this).trigger("change"); - }); - }); - }, - activateDateTimePicker: function(selector) { - $(selector + ' input[data-datetimepicker="on"]').each(function(index) { - var localeFormat = $(this).data('format'); - $(this).daterangepicker({ - singleDatePicker: true, - timePicker: true, - timePicker24Hour: $.kimai.settings['twentyFourHours'], - showDropdowns: true, - autoUpdateInput: false, - locale: { - format: localeFormat, - firstDay: 1, - applyLabel: $.kimai.settings['apply'], - cancelLabel: $.kimai.settings['cancel'], - customRangeLabel: $.kimai.settings['customRange'] - } - }); - - $(this).on('apply.daterangepicker', function(ev, picker) { - $(this).val(picker.startDate.format(localeFormat)); - $(this).trigger("change"); - }); - }); - }, - activateDateRangePicker: function(selector) { - $(selector + ' input[data-daterangepickerenable="on"]').each(function(index) { - var localeFormat = $(this).data('format'); - var separator = $(this).data('separator'); - var rangesList = {}; - rangesList[$.kimai.settings['today']] = [moment(), moment()]; - rangesList[$.kimai.settings['yesterday']] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')]; - rangesList[$.kimai.settings['thisWeek']] = [moment().startOf('week'), moment().endOf('week')]; - rangesList[$.kimai.settings['lastWeek']] = [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')]; - rangesList[$.kimai.settings['thisMonth']] = [moment().startOf('month'), moment().endOf('month')]; - rangesList[$.kimai.settings['lastMonth']] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]; - rangesList[$.kimai.settings['thisYear']] = [moment().startOf('year'), moment().endOf('year')]; - rangesList[$.kimai.settings['lastYear']] = [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')]; - - $(this).daterangepicker({ - showDropdowns: true, - autoUpdateInput: false, - autoApply: false, - linkedCalendars: false, - locale: { - separator: separator, - format: localeFormat, - firstDay: 1, - applyLabel: $.kimai.settings['apply'], - cancelLabel: $.kimai.settings['cancel'], - customRangeLabel: $.kimai.settings['customRange'] - }, - ranges: rangesList, - alwaysShowCalendars: true - }); - - $(this).on('apply.daterangepicker', function(ev, picker) { - $(this).val(picker.startDate.format(localeFormat) + ' - ' + picker.endDate.format(localeFormat)); - $(this).trigger("change"); - }); - }); - }, - ajaxFormInModal: function(html) { - // the modal that we use to render the form in - var formIdentifier = '#remote_form_modal .modal-content form'; - var flashErrorIdentifier = 'div.alert-error'; - var $form = $(formIdentifier); - var $modal = $('#remote_form_modal'); - - // will be (re-)activated later - $form.off('submit'); - - // load new form from given content - if ($(html).find('#form_modal .modal-content').length > 0 ) { - // switch classes, in case the modal type changed - $modal.on('hidden.bs.modal', function () { - if ($modal.hasClass('modal-danger')) { - $modal.removeClass('modal-danger'); - } - }); - - if ($(html).find('#form_modal').hasClass('modal-danger')) { - $modal.addClass('modal-danger'); - } - - // TODO cleanup widgets before replacing the content? - $('#remote_form_modal .modal-content').replaceWith( - $(html).find('#form_modal .modal-content') - ); - // activate new loaded widgets - $.kimai.activateDateTimePicker(formIdentifier); - } - - // show error flash messages - if ($(html).find(flashErrorIdentifier).length > 0) { - $('#remote_form_modal .modal-body').prepend( - $(html).find(flashErrorIdentifier) - ); - } - - // ----------------------------------------------------------------------- - // a fix for firefox focus problems with datepicker in modal - // see https://github.com/kevinpapst/kimai2/issues/618 - var enforceModalFocusFn = $.fn.modal.Constructor.prototype.enforceFocus; - $.fn.modal.Constructor.prototype.enforceFocus = function() {}; - $modal.on('hidden.bs.modal', function () { - $.fn.modal.Constructor.prototype.enforceFocus = enforceModalFocusFn; - }); - // ----------------------------------------------------------------------- - - // workaround for autofocus attribute, as the modal "steals" it - $modal.on('shown.bs.modal', function () { - $(this).find('input[type=text],textarea,select').filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus(); - }); - - $modal.modal('show'); - - // the new form that was loaded via ajax - $form = $(formIdentifier); - - // click handler for modal save button, to send forms via ajax - $form.on('submit', function(event){ - var btn = $(formIdentifier + ' button[type=submit]').button('loading'); - event.preventDefault(); - event.stopPropagation(); - $.ajax({ - url: $form.attr('action'), - type: $form.attr('method'), - data: $form.serialize(), - success: function(html) { - btn.button('reset'); - var hasFieldError = $(html).find('#form_modal .modal-content .has-error').length > 0; - var hasFormError = $(html).find('#form_modal .modal-content ul.list-unstyled li.text-danger').length > 0; - var hasFlashError = $(html).find(flashErrorIdentifier).length > 0; - - if (hasFieldError || hasFormError || hasFlashError) { - $.kimai.ajaxFormInModal(html); - } else { - $.kimai.reloadDatatableWithToolbarFilter(); - $modal.modal('hide'); - } - return false; - }, - error: function(xhr, 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(); - } - }); - }); - }, - activateAjaxFormInModal: function(selector) { - $('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 - var target = event.target; - if (event.currentTarget.tagName === 'TR') { - while (target.tagName !== 'BODY') { - if (target.tagName === 'A' || target.tagName === 'BUTTON') { - return; - } - target = target.parentNode; - } - } - - event.preventDefault(); - event.stopPropagation(); - - // any element can open the modal - for none elements use "data-href" instead of "href" attribute - var href = $(this).attr('data-href'); - if (!href) { - href = $(this).attr('href'); - } - $.ajax({ - url: href, - success: function(html) { - $.kimai.ajaxFormInModal(html); - }, - error: function(xhr, err) { - window.location = href; - } - }); - }); - } - }; - - // default values - $.kimai.defaults = { - locale: 'en', - today: 'Today', - yesterday: 'Yesterday', - apply: 'Apply', - cancel: 'Cancel', - thisWeek: 'This week', - lastWeek: 'Last week', - thisMonth: 'This month', - lastMonth: 'Last month', - thisYear: 'This year', - lastYear: 'Last year', - customRange: 'Custom range', - twentyFourHours: true - }; - - // once initialized, here are all values - $.kimai.settings = {}; - -}); diff --git a/assets/js/plugins/KimaiAPI.js b/assets/js/plugins/KimaiAPI.js new file mode 100644 index 00000000..6d5982a3 --- /dev/null +++ b/assets/js/plugins/KimaiAPI.js @@ -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 + }); + } + +} diff --git a/assets/js/plugins/KimaiActiveRecordsDuration.js b/assets/js/plugins/KimaiActiveRecordsDuration.js new file mode 100644 index 00000000..537d63dc --- /dev/null +++ b/assets/js/plugins/KimaiActiveRecordsDuration.js @@ -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)); + } +} diff --git a/assets/js/plugins/KimaiAjaxModalForm.js b/assets/js/plugins/KimaiAjaxModalForm.js new file mode 100644 index 00000000..6e18380f --- /dev/null +++ b/assets/js/plugins/KimaiAjaxModalForm.js @@ -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(); + } + }); + }); + } + +} diff --git a/assets/js/plugins/KimaiAlternativeLinks.js b/assets/js/plugins/KimaiAlternativeLinks.js new file mode 100644 index 00000000..ecfa088d --- /dev/null +++ b/assets/js/plugins/KimaiAlternativeLinks.js @@ -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; + }); + } + +} diff --git a/assets/js/plugins/KimaiClickHandlerReducedInTableRow.js b/assets/js/plugins/KimaiClickHandlerReducedInTableRow.js new file mode 100644 index 00000000..9a59bf06 --- /dev/null +++ b/assets/js/plugins/KimaiClickHandlerReducedInTableRow.js @@ -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); + }); + } + +} diff --git a/assets/js/plugins/KimaiDatatable.js b/assets/js/plugins/KimaiDatatable.js new file mode 100644 index 00000000..1a414236 --- /dev/null +++ b/assets/js/plugins/KimaiDatatable.js @@ -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 = '
'; + 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(); + } + }); + + } +} diff --git a/assets/js/plugins/KimaiDatatableColumnView.js b/assets/js/plugins/KimaiDatatableColumnView.js new file mode 100644 index 00000000..b17fb786 --- /dev/null +++ b/assets/js/plugins/KimaiDatatableColumnView.js @@ -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'); + } + } + +} diff --git a/assets/js/plugins/KimaiDatePicker.js b/assets/js/plugins/KimaiDatePicker.js new file mode 100644 index 00000000..4b061c28 --- /dev/null +++ b/assets/js/plugins/KimaiDatePicker.js @@ -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"); + }); + }); + } + +} diff --git a/assets/js/plugins/KimaiDateRangePicker.js b/assets/js/plugins/KimaiDateRangePicker.js new file mode 100644 index 00000000..35218b83 --- /dev/null +++ b/assets/js/plugins/KimaiDateRangePicker.js @@ -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"); + }); + }); + } + +} diff --git a/assets/js/plugins/KimaiDateTimePicker.js b/assets/js/plugins/KimaiDateTimePicker.js new file mode 100644 index 00000000..f658bb36 --- /dev/null +++ b/assets/js/plugins/KimaiDateTimePicker.js @@ -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"); + }); + }); + } + +} diff --git a/assets/js/plugins/KimaiJqueryPluginInitializer.js b/assets/js/plugins/KimaiJqueryPluginInitializer.js new file mode 100644 index 00000000..d7657d45 --- /dev/null +++ b/assets/js/plugins/KimaiJqueryPluginInitializer.js @@ -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(); + } + +} diff --git a/assets/js/plugins/KimaiPauseRecord.js b/assets/js/plugins/KimaiPauseRecord.js new file mode 100644 index 00000000..b91895d2 --- /dev/null +++ b/assets/js/plugins/KimaiPauseRecord.js @@ -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'); + }); + } + +} diff --git a/assets/js/plugins/KimaiSelectDataAPI.js b/assets/js/plugins/KimaiSelectDataAPI.js new file mode 100644 index 00000000..e640c85a --- /dev/null +++ b/assets/js/plugins/KimaiSelectDataAPI.js @@ -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: ",t.querySelectorAll("[msallowcapture^='']").length&&O.push("[*^$]="+tt+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||O.push("\\["+tt+"*(?:value|"+K+")"),t.querySelectorAll("[id~="+N+"-]").length||O.push("~="),t.querySelectorAll(":checked").length||O.push(":checked"),t.querySelectorAll("a#"+N+"+*").length||O.push(".#.+[+~]")}),o(function(t){t.innerHTML="
";var e=P.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&O.push("name"+tt+"*[*^$|!~]?="),2!==t.querySelectorAll(":enabled").length&&O.push(":enabled",":disabled"),R.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&O.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),O.push(",.*:")})),(w.matchesSelector=pt.test(A=R.matches||R.webkitMatchesSelector||R.mozMatchesSelector||R.oMatchesSelector||R.msMatchesSelector))&&o(function(t){w.disconnectedMatch=A.call(t,"*"),A.call(t,"[s!='']:x"),H.push("!=",it)}),O=O.length&&new RegExp(O.join("|")),H=H.length&&new RegExp(H.join("|")),e=pt.test(R.compareDocumentPosition),F=e||pt.test(R.contains)?function(t,e){var n=9===t.nodeType?t.documentElement:t,i=e&&e.parentNode;return t===i||!(!i||1!==i.nodeType||!(n.contains?n.contains(i):t.compareDocumentPosition&&16&t.compareDocumentPosition(i)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},V=e?function(t,e){if(t===e)return E=!0,0;var n=!t.compareDocumentPosition-!e.compareDocumentPosition;return n||(n=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1,1&n||!w.sortDetached&&e.compareDocumentPosition(t)===n?t===P||t.ownerDocument===Y&&F(Y,t)?-1:e===P||e.ownerDocument===Y&&F(Y,e)?1:T?Q(T,t)-Q(T,e):0:4&n?-1:1)}:function(t,e){if(t===e)return E=!0,0;var n,i=0,o=t.parentNode,r=e.parentNode,s=[t],l=[e];if(!o||!r)return t===P?-1:e===P?1:o?-1:r?1:T?Q(T,t)-Q(T,e):0;if(o===r)return a(t,e);for(n=t;n=n.parentNode;)s.unshift(n);for(n=e;n=n.parentNode;)l.unshift(n);for(;s[i]===l[i];)i++;return i?a(s[i],l[i]):s[i]===Y?-1:l[i]===Y?1:0},P):P},e.matches=function(t,n){return e(t,null,null,n)},e.matchesSelector=function(t,n){if((t.ownerDocument||t)!==P&&I(t),n=n.replace(lt,"='$1']"),w.matchesSelector&&L&&!$[n+" "]&&(!H||!H.test(n))&&(!O||!O.test(n)))try{var i=A.call(t,n);if(i||w.disconnectedMatch||t.document&&11!==t.document.nodeType)return i}catch(t){}return e(n,P,null,[t]).length>0},e.contains=function(t,e){return(t.ownerDocument||t)!==P&&I(t),F(t,e)},e.attr=function(t,e){(t.ownerDocument||t)!==P&&I(t);var n=x.attrHandle[e.toLowerCase()],i=n&&U.call(x.attrHandle,e.toLowerCase())?n(t,e,!L):void 0;return void 0!==i?i:w.attributes||!L?t.getAttribute(e):(i=t.getAttributeNode(e))&&i.specified?i.value:null},e.escape=function(t){return(t+"").replace(bt,wt)},e.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},e.uniqueSort=function(t){var e,n=[],i=0,o=0;if(E=!w.detectDuplicates,T=!w.sortStable&&t.slice(0),t.sort(V),E){for(;e=t[o++];)e===t[o]&&(i=n.push(o));for(;i--;)t.splice(n[i],1)}return T=null,t},D=e.getText=function(t){var e,n="",i=0,o=t.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)n+=D(t)}else if(3===o||4===o)return t.nodeValue}else for(;e=t[i++];)n+=D(e);return n},x=e.selectors={cacheLength:50,createPseudo:i,match:ct,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(vt,yt),t[3]=(t[3]||t[4]||t[5]||"").replace(vt,yt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||e.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&e.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return ct.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&ut.test(n)&&(e=S(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(vt,yt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=B[t+" "];return e||(e=new RegExp("(^|"+tt+")"+t+"("+tt+"|$)"))&&B(t,function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,n,i){return function(o){var r=e.attr(o,t);return null==r?"!="===n:!n||(r+="","="===n?r===i:"!="===n?r!==i:"^="===n?i&&0===r.indexOf(i):"*="===n?i&&r.indexOf(i)>-1:"$="===n?i&&r.slice(-i.length)===i:"~="===n?(" "+r.replace(ot," ")+" ").indexOf(i)>-1:"|="===n&&(r===i||r.slice(0,i.length+1)===i+"-"))}},CHILD:function(t,e,n,i,o){var r="nth"!==t.slice(0,3),a="last"!==t.slice(-4),s="of-type"===e;return 1===i&&0===o?function(t){return!!t.parentNode}:function(e,n,l){var u,d,c,h,f,p,g=r!==a?"nextSibling":"previousSibling",m=e.parentNode,v=s&&e.nodeName.toLowerCase(),y=!l&&!s,b=!1;if(m){if(r){for(;g;){for(h=e;h=h[g];)if(s?h.nodeName.toLowerCase()===v:1===h.nodeType)return!1;p=g="only"===t&&!p&&"nextSibling"}return!0}if(p=[a?m.firstChild:m.lastChild],a&&y){for(h=m,c=h[N]||(h[N]={}),d=c[h.uniqueID]||(c[h.uniqueID]={}),u=d[t]||[],f=u[0]===z&&u[1],b=f&&u[2],h=f&&m.childNodes[f];h=++f&&h&&h[g]||(b=f=0)||p.pop();)if(1===h.nodeType&&++b&&h===e){d[t]=[z,f,b];break}}else if(y&&(h=e,c=h[N]||(h[N]={}),d=c[h.uniqueID]||(c[h.uniqueID]={}),u=d[t]||[],f=u[0]===z&&u[1],b=f),!1===b)for(;(h=++f&&h&&h[g]||(b=f=0)||p.pop())&&((s?h.nodeName.toLowerCase()!==v:1!==h.nodeType)||!++b||(y&&(c=h[N]||(h[N]={}),d=c[h.uniqueID]||(c[h.uniqueID]={}),d[t]=[z,b]),h!==e)););return(b-=o)===i||b%i==0&&b/i>=0}}},PSEUDO:function(t,n){var o,r=x.pseudos[t]||x.setFilters[t.toLowerCase()]||e.error("unsupported pseudo: "+t);return r[N]?r(n):r.length>1?(o=[t,t,"",n],x.setFilters.hasOwnProperty(t.toLowerCase())?i(function(t,e){for(var i,o=r(t,n),a=o.length;a--;)i=Q(t,o[a]),t[i]=!(e[i]=o[a])}):function(t){return r(t,0,o)}):r}},pseudos:{not:i(function(t){var e=[],n=[],o=M(t.replace(rt,"$1"));return o[N]?i(function(t,e,n,i){for(var r,a=o(t,null,i,[]),s=t.length;s--;)(r=a[s])&&(t[s]=!(e[s]=r))}):function(t,i,r){return e[0]=t,o(e,null,r,n),e[0]=null,!n.pop()}}),has:i(function(t){return function(n){return e(t,n).length>0}}),contains:i(function(t){return t=t.replace(vt,yt),function(e){return(e.textContent||e.innerText||D(e)).indexOf(t)>-1}}),lang:i(function(t){return dt.test(t||"")||e.error("unsupported lang: "+t),t=t.replace(vt,yt).toLowerCase(),function(e){var n;do{if(n=L?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(n=n.toLowerCase())===t||0===n.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var n=t.location&&t.location.hash;return n&&n.slice(1)===e.id},root:function(t){return t===R},focus:function(t){return t===P.activeElement&&(!P.hasFocus||P.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:s(!1),disabled:s(!0),checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!x.pseudos.empty(t)},header:function(t){return ft.test(t.nodeName)},input:function(t){return ht.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:l(function(){return[0]}),last:l(function(t,e){return[e-1]}),eq:l(function(t,e,n){return[n<0?n+e:n]}),even:l(function(t,e){for(var n=0;n=0;)t.push(i);return t}),gt:l(function(t,e,n){for(var i=n<0?n+e:n;++i2&&"ID"===(a=r[0]).type&&9===e.nodeType&&L&&x.relative[r[1].type]){if(!(e=(x.find.ID(a.matches[0].replace(vt,yt),e)||[])[0]))return n;d&&(e=e.parentNode),t=t.slice(r.shift().value.length)}for(o=ct.needsContext.test(t)?0:r.length;o--&&(a=r[o],!x.relative[s=a.type]);)if((l=x.find[s])&&(i=l(a.matches[0].replace(vt,yt),mt.test(r[0].type)&&u(e.parentNode)||e))){if(r.splice(o,1),!(t=i.length&&c(r)))return Z.apply(n,i),n;break}}return(d||M(t,h))(i,e,!L,n,!e||mt.test(t)&&u(e.parentNode)||e),n},w.sortStable=N.split("").sort(V).join("")===N,w.detectDuplicates=!!E,I(),w.sortDetached=o(function(t){return 1&t.compareDocumentPosition(P.createElement("fieldset"))}),o(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||r("type|href|height|width",function(t,e,n){if(!n)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),w.attributes&&o(function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||r("value",function(t,e,n){if(!n&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),o(function(t){return null==t.getAttribute("disabled")})||r(K,function(t,e,n){var i;if(!n)return!0===t[e]?e.toLowerCase():(i=t.getAttributeNode(e))&&i.specified?i.value:null}),e}(n);Mt.find=Ct,Mt.expr=Ct.selectors,Mt.expr[":"]=Mt.expr.pseudos,Mt.uniqueSort=Mt.unique=Ct.uniqueSort,Mt.text=Ct.getText,Mt.isXMLDoc=Ct.isXML,Mt.contains=Ct.contains,Mt.escapeSelector=Ct.escape;var Tt=function(t,e,n){for(var i=[],o=void 0!==n;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(o&&Mt(t).is(n))break;i.push(t)}return i},Et=function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n},It=Mt.expr.match.needsContext,Pt=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;Mt.filter=function(t,e,n){var i=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===i.nodeType?Mt.find.matchesSelector(i,t)?[i]:[]:Mt.find.matches(t,Mt.grep(e,function(t){return 1===t.nodeType}))},Mt.fn.extend({find:function(t){var e,n,i=this.length,o=this;if("string"!=typeof t)return this.pushStack(Mt(t).filter(function(){for(e=0;e1?Mt.uniqueSort(n):n},filter:function(t){return this.pushStack(d(this,t||[],!1))},not:function(t){return this.pushStack(d(this,t||[],!0))},is:function(t){return!!d(this,"string"==typeof t&&It.test(t)?Mt(t):t||[],!1).length}});var Rt,Lt=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(Mt.fn.init=function(t,e,n){var i,o;if(!t)return this;if(n=n||Rt,"string"==typeof t){if(!(i="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:Lt.exec(t))||!i[1]&&e)return!e||e.jquery?(e||n).find(t):this.constructor(e).find(t);if(i[1]){if(e=e instanceof Mt?e[0]:e,Mt.merge(this,Mt.parseHTML(i[1],e&&e.nodeType?e.ownerDocument||e:dt,!0)),Pt.test(i[1])&&Mt.isPlainObject(e))for(i in e)Dt(this[i])?this[i](e[i]):this.attr(i,e[i]);return this}return o=dt.getElementById(i[2]),o&&(this[0]=o,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):Dt(t)?void 0!==n.ready?n.ready(t):t(Mt):Mt.makeArray(t,this)}).prototype=Mt.fn,Rt=Mt(dt);var Ot=/^(?:parents|prev(?:Until|All))/,Ht={children:!0,contents:!0,next:!0,prev:!0};Mt.fn.extend({has:function(t){var e=Mt(t,this),n=e.length;return this.filter(function(){for(var t=0;t-1:1===n.nodeType&&Mt.find.matchesSelector(n,t))){r.push(n);break}return this.pushStack(r.length>1?Mt.uniqueSort(r):r)},index:function(t){return t?"string"==typeof t?gt.call(Mt(t),this[0]):gt.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(Mt.uniqueSort(Mt.merge(this.get(),Mt(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),Mt.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return Tt(t,"parentNode")},parentsUntil:function(t,e,n){return Tt(t,"parentNode",n)},next:function(t){return c(t,"nextSibling")},prev:function(t){return c(t,"previousSibling")},nextAll:function(t){return Tt(t,"nextSibling")},prevAll:function(t){return Tt(t,"previousSibling")},nextUntil:function(t,e,n){return Tt(t,"nextSibling",n)},prevUntil:function(t,e,n){return Tt(t,"previousSibling",n)},siblings:function(t){return Et((t.parentNode||{}).firstChild,t)},children:function(t){return Et(t.firstChild)},contents:function(t){return u(t,"iframe")?t.contentDocument:(u(t,"template")&&(t=t.content||t),Mt.merge([],t.childNodes))}},function(t,e){Mt.fn[t]=function(n,i){var o=Mt.map(this,e,n);return"Until"!==t.slice(-5)&&(i=n),i&&"string"==typeof i&&(o=Mt.filter(i,o)),this.length>1&&(Ht[t]||Mt.uniqueSort(o),Ot.test(t)&&o.reverse()),this.pushStack(o)}});var At=/[^\x20\t\r\n\f]+/g;Mt.Callbacks=function(t){t="string"==typeof t?h(t):Mt.extend({},t);var e,n,i,o,r=[],a=[],l=-1,u=function(){for(o=o||t.once,i=e=!0;a.length;l=-1)for(n=a.shift();++l-1;)r.splice(n,1),n<=l&&l--}),this},has:function(t){return t?Mt.inArray(t,r)>-1:r.length>0},empty:function(){return r&&(r=[]),this},disable:function(){return o=a=[],r=n="",this},disabled:function(){return!r},lock:function(){return o=a=[],n||e||(r=n=""),this},locked:function(){return!!o},fireWith:function(t,n){return o||(n=n||[],n=[t,n.slice?n.slice():n],a.push(n),e||u()),this},fire:function(){return d.fireWith(this,arguments),this},fired:function(){return!!i}};return d},Mt.extend({Deferred:function(t){var e=[["notify","progress",Mt.Callbacks("memory"),Mt.Callbacks("memory"),2],["resolve","done",Mt.Callbacks("once memory"),Mt.Callbacks("once memory"),0,"resolved"],["reject","fail",Mt.Callbacks("once memory"),Mt.Callbacks("once memory"),1,"rejected"]],i="pending",o={state:function(){return i},always:function(){return r.done(arguments).fail(arguments),this},catch:function(t){return o.then(null,t)},pipe:function(){var t=arguments;return Mt.Deferred(function(n){Mt.each(e,function(e,i){var o=Dt(t[i[4]])&&t[i[4]];r[i[1]](function(){var t=o&&o.apply(this,arguments);t&&Dt(t.promise)?t.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[i[0]+"With"](this,o?[t]:arguments)})}),t=null}).promise()},then:function(t,i,o){function r(t,e,i,o){return function(){var s=this,l=arguments,u=function(){var n,u;if(!(t=a&&(i!==p&&(s=void 0,l=[n]),e.rejectWith(s,l))}};t?d():(Mt.Deferred.getStackHook&&(d.stackTrace=Mt.Deferred.getStackHook()),n.setTimeout(d))}}var a=0;return Mt.Deferred(function(n){e[0][3].add(r(0,n,Dt(o)?o:f,n.notifyWith)),e[1][3].add(r(0,n,Dt(t)?t:f)),e[2][3].add(r(0,n,Dt(i)?i:p))}).promise()},promise:function(t){return null!=t?Mt.extend(t,o):o}},r={};return Mt.each(e,function(t,n){var a=n[2],s=n[5];o[n[1]]=a.add,s&&a.add(function(){i=s},e[3-t][2].disable,e[3-t][3].disable,e[0][2].lock,e[0][3].lock),a.add(n[3].fire),r[n[0]]=function(){return r[n[0]+"With"](this===r?void 0:this,arguments),this},r[n[0]+"With"]=a.fireWith}),o.promise(r),t&&t.call(r,r),r},when:function(t){var e=arguments.length,n=e,i=Array(n),o=ht.call(arguments),r=Mt.Deferred(),a=function(t){return function(n){i[t]=this,o[t]=arguments.length>1?ht.call(arguments):n,--e||r.resolveWith(i,o)}};if(e<=1&&(g(t,r.done(a(n)).resolve,r.reject,!e),"pending"===r.state()||Dt(o[n]&&o[n].then)))return r.then();for(;n--;)g(o[n],a(n),r.reject);return r.promise()}});var Ft=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;Mt.Deferred.exceptionHook=function(t,e){n.console&&n.console.warn&&t&&Ft.test(t.name)&&n.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},Mt.readyException=function(t){n.setTimeout(function(){throw t})};var Nt=Mt.Deferred();Mt.fn.ready=function(t){return Nt.then(t).catch(function(t){Mt.readyException(t)}),this},Mt.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--Mt.readyWait:Mt.isReady)||(Mt.isReady=!0,!0!==t&&--Mt.readyWait>0||Nt.resolveWith(dt,[Mt]))}}),Mt.ready.then=Nt.then,"complete"===dt.readyState||"loading"!==dt.readyState&&!dt.documentElement.doScroll?n.setTimeout(Mt.ready):(dt.addEventListener("DOMContentLoaded",m),n.addEventListener("load",m));var Yt=function(t,e,n,i,o,r,a){var l=0,u=t.length,d=null==n;if("object"===s(n)){o=!0;for(l in n)Yt(t,e,l,n[l],!0,r,a)}else if(void 0!==i&&(o=!0,Dt(i)||(a=!0),d&&(a?(e.call(t,i),e=null):(d=e,e=function(t,e,n){return d.call(Mt(t),n)})),e))for(;l1,null,!0)},removeData:function(t){return this.each(function(){$t.remove(this,t)})}}),Mt.extend({queue:function(t,e,n){var i;if(t)return e=(e||"fx")+"queue",i=Wt.get(t,e),n&&(!i||Array.isArray(n)?i=Wt.access(t,e,Mt.makeArray(n)):i.push(n)),i||[]},dequeue:function(t,e){e=e||"fx";var n=Mt.queue(t,e),i=n.length,o=n.shift(),r=Mt._queueHooks(t,e),a=function(){Mt.dequeue(t,e)};"inprogress"===o&&(o=n.shift(),i--),o&&("fx"===e&&n.unshift("inprogress"),delete r.stop,o.call(t,a,r)),!i&&r&&r.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return Wt.get(t,n)||Wt.access(t,n,{empty:Mt.Callbacks("once memory").add(function(){Wt.remove(t,[e+"queue",n])})})}}),Mt.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length\x20\t\r\n\f]+)/i,ee=/^$|^module$|\/(?:java|ecma)script/i,ne={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ne.optgroup=ne.option,ne.tbody=ne.tfoot=ne.colgroup=ne.caption=ne.thead,ne.th=ne.td;var ie=/<|&#?\w+;/;!function(){var t=dt.createDocumentFragment(),e=t.appendChild(dt.createElement("div")),n=dt.createElement("input");n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),e.appendChild(n),xt.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",xt.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var oe=dt.documentElement,re=/^key/,ae=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,se=/^([^.]*)(?:\.(.+)|)/;Mt.event={global:{},add:function(t,e,n,i,o){var r,a,s,l,u,d,c,h,f,p,g,m=Wt.get(t);if(m)for(n.handler&&(r=n,n=r.handler,o=r.selector),o&&Mt.find.matchesSelector(oe,o),n.guid||(n.guid=Mt.guid++),(l=m.events)||(l=m.events={}),(a=m.handle)||(a=m.handle=function(e){return void 0!==Mt&&Mt.event.triggered!==e.type?Mt.event.dispatch.apply(t,arguments):void 0}),e=(e||"").match(At)||[""],u=e.length;u--;)s=se.exec(e[u])||[],f=g=s[1],p=(s[2]||"").split(".").sort(),f&&(c=Mt.event.special[f]||{},f=(o?c.delegateType:c.bindType)||f,c=Mt.event.special[f]||{},d=Mt.extend({type:f,origType:g,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&Mt.expr.match.needsContext.test(o),namespace:p.join(".")},r),(h=l[f])||(h=l[f]=[],h.delegateCount=0,c.setup&&!1!==c.setup.call(t,i,p,a)||t.addEventListener&&t.addEventListener(f,a)),c.add&&(c.add.call(t,d),d.handler.guid||(d.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,d):h.push(d),Mt.event.global[f]=!0)},remove:function(t,e,n,i,o){var r,a,s,l,u,d,c,h,f,p,g,m=Wt.hasData(t)&&Wt.get(t);if(m&&(l=m.events)){for(e=(e||"").match(At)||[""],u=e.length;u--;)if(s=se.exec(e[u])||[],f=g=s[1],p=(s[2]||"").split(".").sort(),f){for(c=Mt.event.special[f]||{},f=(i?c.delegateType:c.bindType)||f,h=l[f]||[],s=s[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=r=h.length;r--;)d=h[r],!o&&g!==d.origType||n&&n.guid!==d.guid||s&&!s.test(d.namespace)||i&&i!==d.selector&&("**"!==i||!d.selector)||(h.splice(r,1),d.selector&&h.delegateCount--,c.remove&&c.remove.call(t,d));a&&!h.length&&(c.teardown&&!1!==c.teardown.call(t,p,m.handle)||Mt.removeEvent(t,f,m.handle),delete l[f])}else for(f in l)Mt.event.remove(t,f+e[u],n,i,!0);Mt.isEmptyObject(l)&&Wt.remove(t,"handle events")}},dispatch:function(t){var e,n,i,o,r,a,s=Mt.event.fix(t),l=new Array(arguments.length),u=(Wt.get(this,"events")||{})[s.type]||[],d=Mt.event.special[s.type]||{};for(l[0]=s,e=1;e=1))for(;u!==this;u=u.parentNode||this)if(1===u.nodeType&&("click"!==t.type||!0!==u.disabled)){for(r=[],a={},n=0;n-1:Mt.find(o,this,null,[u]).length),a[o]&&r.push(i);r.length&&s.push({elem:u,handlers:r})}return u=this,l\x20\t\r\n\f]*)[^>]*)\/>/gi,ue=/\s*$/g;Mt.extend({htmlPrefilter:function(t){return t.replace(le,"<$1>")},clone:function(t,e,n){var i,o,r,a,s=t.cloneNode(!0),l=Mt.contains(t.ownerDocument,t);if(!(xt.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||Mt.isXMLDoc(t)))for(a=M(s),r=M(t),i=0,o=r.length;i0&&k(a,!l&&M(t,"script")),s},cleanData:function(t){for(var e,n,i,o=Mt.event.special,r=0;void 0!==(n=t[r]);r++)if(Bt(n)){if(e=n[Wt.expando]){if(e.events)for(i in e.events)o[i]?Mt.event.remove(n,i):Mt.removeEvent(n,i,e.handle);n[Wt.expando]=void 0}n[$t.expando]&&(n[$t.expando]=void 0)}}}),Mt.fn.extend({detach:function(t){return N(this,t,!0)},remove:function(t){return N(this,t)},text:function(t){return Yt(this,function(t){return void 0===t?Mt.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)})},null,t,arguments.length)},append:function(){return F(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){R(this,t).appendChild(t)}})},prepend:function(){return F(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=R(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return F(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return F(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(Mt.cleanData(M(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return Mt.clone(this,t,e)})},html:function(t){return Yt(this,function(t){var e=this[0]||{},n=0,i=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!ue.test(t)&&!ne[(te.exec(t)||["",""])[1].toLowerCase()]){t=Mt.htmlPrefilter(t);try{for(;n1)}}),Mt.Tween=U,U.prototype={constructor:U,init:function(t,e,n,i,o,r){this.elem=t,this.prop=n,this.easing=o||Mt.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=i,this.unit=r||(Mt.cssNumber[n]?"":"px")},cur:function(){var t=U.propHooks[this.prop];return t&&t.get?t.get(this):U.propHooks._default.get(this)},run:function(t){var e,n=U.propHooks[this.prop];return this.options.duration?this.pos=e=Mt.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):U.propHooks._default.set(this),this}},U.prototype.init.prototype=U.prototype,U.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=Mt.css(t.elem,t.prop,""),e&&"auto"!==e?e:0)},set:function(t){Mt.fx.step[t.prop]?Mt.fx.step[t.prop](t):1!==t.elem.nodeType||null==t.elem.style[Mt.cssProps[t.prop]]&&!Mt.cssHooks[t.prop]?t.elem[t.prop]=t.now:Mt.style(t.elem,t.prop,t.now+t.unit)}}},U.propHooks.scrollTop=U.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},Mt.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},Mt.fx=U.prototype.init,Mt.fx.step={};var xe,De,_e=/^(?:toggle|show|hide)$/,Se=/queueHooks$/;Mt.Animation=Mt.extend(K,{tweeners:{"*":[function(t,e){var n=this.createTween(t,e);return D(n.elem,t,Gt.exec(e),n),n}]},tweener:function(t,e){Dt(t)?(e=t,t=["*"]):t=t.match(At);for(var n,i=0,o=t.length;i1)},removeAttr:function(t){return this.each(function(){Mt.removeAttr(this,t)})}}),Mt.extend({attr:function(t,e,n){var i,o,r=t.nodeType;if(3!==r&&8!==r&&2!==r)return void 0===t.getAttribute?Mt.prop(t,e,n):(1===r&&Mt.isXMLDoc(t)||(o=Mt.attrHooks[e.toLowerCase()]||(Mt.expr.match.bool.test(e)?Me:void 0)),void 0!==n?null===n?void Mt.removeAttr(t,e):o&&"set"in o&&void 0!==(i=o.set(t,n,e))?i:(t.setAttribute(e,n+""),n):o&&"get"in o&&null!==(i=o.get(t,e))?i:(i=Mt.find.attr(t,e),null==i?void 0:i))},attrHooks:{type:{set:function(t,e){if(!xt.radioValue&&"radio"===e&&u(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}},removeAttr:function(t,e){var n,i=0,o=e&&e.match(At);if(o&&1===t.nodeType)for(;n=o[i++];)t.removeAttribute(n)}}),Me={set:function(t,e,n){return!1===e?Mt.removeAttr(t,n):t.setAttribute(n,n),n}},Mt.each(Mt.expr.match.bool.source.match(/\w+/g),function(t,e){var n=ke[e]||Mt.find.attr;ke[e]=function(t,e,i){var o,r,a=e.toLowerCase();return i||(r=ke[a],ke[a]=o,o=null!=n(t,e,i)?a:null,ke[a]=r),o}});var Ce=/^(?:input|select|textarea|button)$/i,Te=/^(?:a|area)$/i;Mt.fn.extend({prop:function(t,e){return Yt(this,Mt.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each(function(){delete this[Mt.propFix[t]||t]})}}),Mt.extend({prop:function(t,e,n){var i,o,r=t.nodeType;if(3!==r&&8!==r&&2!==r)return 1===r&&Mt.isXMLDoc(t)||(e=Mt.propFix[e]||e,o=Mt.propHooks[e]),void 0!==n?o&&"set"in o&&void 0!==(i=o.set(t,n,e))?i:t[e]=n:o&&"get"in o&&null!==(i=o.get(t,e))?i:t[e]},propHooks:{tabIndex:{get:function(t){var e=Mt.find.attr(t,"tabindex");return e?parseInt(e,10):Ce.test(t.nodeName)||Te.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),xt.optSelected||(Mt.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),Mt.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){Mt.propFix[this.toLowerCase()]=this}),Mt.fn.extend({addClass:function(t){var e,n,i,o,r,a,s,l=0;if(Dt(t))return this.each(function(e){Mt(this).addClass(t.call(this,e,et(this)))});if(e=nt(t),e.length)for(;n=this[l++];)if(o=et(n),i=1===n.nodeType&&" "+tt(o)+" "){for(a=0;r=e[a++];)i.indexOf(" "+r+" ")<0&&(i+=r+" ");s=tt(i),o!==s&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,i,o,r,a,s,l=0;if(Dt(t))return this.each(function(e){Mt(this).removeClass(t.call(this,e,et(this)))});if(!arguments.length)return this.attr("class","");if(e=nt(t),e.length)for(;n=this[l++];)if(o=et(n),i=1===n.nodeType&&" "+tt(o)+" "){for(a=0;r=e[a++];)for(;i.indexOf(" "+r+" ")>-1;)i=i.replace(" "+r+" "," ");s=tt(i),o!==s&&n.setAttribute("class",s)}return this},toggleClass:function(t,e){var n=typeof t,i="string"===n||Array.isArray(t);return"boolean"==typeof e&&i?e?this.addClass(t):this.removeClass(t):Dt(t)?this.each(function(n){Mt(this).toggleClass(t.call(this,n,et(this),e),e)}):this.each(function(){var e,o,r,a;if(i)for(o=0,r=Mt(this),a=nt(t);e=a[o++];)r.hasClass(e)?r.removeClass(e):r.addClass(e);else void 0!==t&&"boolean"!==n||(e=et(this),e&&Wt.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===t?"":Wt.get(this,"__className__")||""))})},hasClass:function(t){var e,n,i=0;for(e=" "+t+" ";n=this[i++];)if(1===n.nodeType&&(" "+tt(et(n))+" ").indexOf(e)>-1)return!0;return!1}});var Ee=/\r/g;Mt.fn.extend({val:function(t){var e,n,i,o=this[0];{if(arguments.length)return i=Dt(t),this.each(function(n){var o;1===this.nodeType&&(o=i?t.call(this,n,Mt(this).val()):t,null==o?o="":"number"==typeof o?o+="":Array.isArray(o)&&(o=Mt.map(o,function(t){return null==t?"":t+""})),(e=Mt.valHooks[this.type]||Mt.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,o,"value")||(this.value=o))});if(o)return(e=Mt.valHooks[o.type]||Mt.valHooks[o.nodeName.toLowerCase()])&&"get"in e&&void 0!==(n=e.get(o,"value"))?n:(n=o.value,"string"==typeof n?n.replace(Ee,""):null==n?"":n)}}}),Mt.extend({valHooks:{option:{get:function(t){var e=Mt.find.attr(t,"value");return null!=e?e:tt(Mt.text(t))}},select:{get:function(t){var e,n,i,o=t.options,r=t.selectedIndex,a="select-one"===t.type,s=a?null:[],l=a?r+1:o.length;for(i=r<0?l:a?r:0;i-1)&&(n=!0);return n||(t.selectedIndex=-1),r}}}}),Mt.each(["radio","checkbox"],function(){Mt.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=Mt.inArray(Mt(t).val(),e)>-1}},xt.checkOn||(Mt.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})}),xt.focusin="onfocusin"in n;var Ie=/^(?:focusinfocus|focusoutblur)$/,Pe=function(t){t.stopPropagation()};Mt.extend(Mt.event,{trigger:function(t,e,i,o){var r,a,s,l,u,d,c,h,f=[i||dt],p=yt.call(t,"type")?t.type:t,g=yt.call(t,"namespace")?t.namespace.split("."):[];if(a=h=s=i=i||dt,3!==i.nodeType&&8!==i.nodeType&&!Ie.test(p+Mt.event.triggered)&&(p.indexOf(".")>-1&&(g=p.split("."),p=g.shift(),g.sort()),u=p.indexOf(":")<0&&"on"+p,t=t[Mt.expando]?t:new Mt.Event(p,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=g.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),e=null==e?[t]:Mt.makeArray(e,[t]),c=Mt.event.special[p]||{},o||!c.trigger||!1!==c.trigger.apply(i,e))){if(!o&&!c.noBubble&&!_t(i)){for(l=c.delegateType||p,Ie.test(l+p)||(a=a.parentNode);a;a=a.parentNode)f.push(a),s=a;s===(i.ownerDocument||dt)&&f.push(s.defaultView||s.parentWindow||n)}for(r=0;(a=f[r++])&&!t.isPropagationStopped();)h=a,t.type=r>1?l:c.bindType||p,d=(Wt.get(a,"events")||{})[t.type]&&Wt.get(a,"handle"),d&&d.apply(a,e),(d=u&&a[u])&&d.apply&&Bt(a)&&(t.result=d.apply(a,e),!1===t.result&&t.preventDefault());return t.type=p,o||t.isDefaultPrevented()||c._default&&!1!==c._default.apply(f.pop(),e)||!Bt(i)||u&&Dt(i[p])&&!_t(i)&&(s=i[u],s&&(i[u]=null),Mt.event.triggered=p,t.isPropagationStopped()&&h.addEventListener(p,Pe),i[p](),t.isPropagationStopped()&&h.removeEventListener(p,Pe),Mt.event.triggered=void 0,s&&(i[u]=s)),t.result}},simulate:function(t,e,n){var i=Mt.extend(new Mt.Event,n,{type:t,isSimulated:!0});Mt.event.trigger(i,null,e)}}),Mt.fn.extend({trigger:function(t,e){return this.each(function(){Mt.event.trigger(t,e,this)})},triggerHandler:function(t,e){var n=this[0];if(n)return Mt.event.trigger(t,e,n,!0)}}),xt.focusin||Mt.each({focus:"focusin",blur:"focusout"},function(t,e){var n=function(t){Mt.event.simulate(e,t.target,Mt.event.fix(t))};Mt.event.special[e]={setup:function(){var i=this.ownerDocument||this,o=Wt.access(i,e);o||i.addEventListener(t,n,!0),Wt.access(i,e,(o||0)+1)},teardown:function(){var i=this.ownerDocument||this,o=Wt.access(i,e)-1;o?Wt.access(i,e,o):(i.removeEventListener(t,n,!0),Wt.remove(i,e))}}});var Re=n.location,Le=Date.now(),Oe=/\?/;Mt.parseXML=function(t){var e;if(!t||"string"!=typeof t)return null;try{e=(new n.DOMParser).parseFromString(t,"text/xml")}catch(t){e=void 0}return e&&!e.getElementsByTagName("parsererror").length||Mt.error("Invalid XML: "+t),e};var He=/\[\]$/,Ae=/\r?\n/g,Fe=/^(?:submit|button|image|reset|file)$/i,Ne=/^(?:input|select|textarea|keygen)/i;Mt.param=function(t,e){var n,i=[],o=function(t,e){var n=Dt(e)?e():e;i[i.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(t)||t.jquery&&!Mt.isPlainObject(t))Mt.each(t,function(){o(this.name,this.value)});else for(n in t)it(n,t[n],e,o);return i.join("&")},Mt.fn.extend({serialize:function(){return Mt.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=Mt.prop(this,"elements");return t?Mt.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!Mt(this).is(":disabled")&&Ne.test(this.nodeName)&&!Fe.test(t)&&(this.checked||!Kt.test(t))}).map(function(t,e){var n=Mt(this).val();return null==n?null:Array.isArray(n)?Mt.map(n,function(t){return{name:e.name,value:t.replace(Ae,"\r\n")}}):{name:e.name,value:n.replace(Ae,"\r\n")}}).get()}});var Ye=/%20/g,ze=/#.*$/,je=/([?&])_=[^&]*/,Be=/^(.*?):[ \t]*([^\r\n]*)$/gm,We=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,$e=/^(?:GET|HEAD)$/,Ve=/^\/\//,Ue={},qe={},Ge="*/".concat("*"),Je=dt.createElement("a");Je.href=Re.href,Mt.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Re.href,type:"GET",isLocal:We.test(Re.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Ge,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":Mt.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?at(at(t,Mt.ajaxSettings),e):at(Mt.ajaxSettings,t)},ajaxPrefilter:ot(Ue),ajaxTransport:ot(qe),ajax:function(t,e){function i(t,e,i,s){var u,h,f,w,x,D=e;d||(d=!0,l&&n.clearTimeout(l),o=void 0,a=s||"",_.readyState=t>0?4:0,u=t>=200&&t<300||304===t,i&&(w=st(p,_,i)),w=lt(p,w,_,u),u?(p.ifModified&&(x=_.getResponseHeader("Last-Modified"),x&&(Mt.lastModified[r]=x),(x=_.getResponseHeader("etag"))&&(Mt.etag[r]=x)),204===t||"HEAD"===p.type?D="nocontent":304===t?D="notmodified":(D=w.state,h=w.data,f=w.error,u=!f)):(f=D,!t&&D||(D="error",t<0&&(t=0))),_.status=t,_.statusText=(e||D)+"",u?v.resolveWith(g,[h,D,_]):v.rejectWith(g,[_,D,f]),_.statusCode(b),b=void 0,c&&m.trigger(u?"ajaxSuccess":"ajaxError",[_,p,u?h:f]),y.fireWith(g,[_,D]),c&&(m.trigger("ajaxComplete",[_,p]),--Mt.active||Mt.event.trigger("ajaxStop")))}"object"==typeof t&&(e=t,t=void 0),e=e||{};var o,r,a,s,l,u,d,c,h,f,p=Mt.ajaxSetup({},e),g=p.context||p,m=p.context&&(g.nodeType||g.jquery)?Mt(g):Mt.event,v=Mt.Deferred(),y=Mt.Callbacks("once memory"),b=p.statusCode||{},w={},x={},D="canceled",_={readyState:0,getResponseHeader:function(t){var e;if(d){if(!s)for(s={};e=Be.exec(a);)s[e[1].toLowerCase()]=e[2];e=s[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return d?a:null},setRequestHeader:function(t,e){return null==d&&(t=x[t.toLowerCase()]=x[t.toLowerCase()]||t,w[t]=e),this},overrideMimeType:function(t){return null==d&&(p.mimeType=t),this},statusCode:function(t){var e;if(t)if(d)_.always(t[_.status]);else for(e in t)b[e]=[b[e],t[e]];return this},abort:function(t){var e=t||D;return o&&o.abort(e),i(0,e),this}};if(v.promise(_),p.url=((t||p.url||Re.href)+"").replace(Ve,Re.protocol+"//"),p.type=e.method||e.type||p.method||p.type,p.dataTypes=(p.dataType||"*").toLowerCase().match(At)||[""],null==p.crossDomain){u=dt.createElement("a");try{u.href=p.url,u.href=u.href,p.crossDomain=Je.protocol+"//"+Je.host!=u.protocol+"//"+u.host}catch(t){p.crossDomain=!0}}if(p.data&&p.processData&&"string"!=typeof p.data&&(p.data=Mt.param(p.data,p.traditional)),rt(Ue,p,e,_),d)return _;c=Mt.event&&p.global,c&&0==Mt.active++&&Mt.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!$e.test(p.type),r=p.url.replace(ze,""),p.hasContent?p.data&&p.processData&&0===(p.contentType||"").indexOf("application/x-www-form-urlencoded")&&(p.data=p.data.replace(Ye,"+")):(f=p.url.slice(r.length),p.data&&(p.processData||"string"==typeof p.data)&&(r+=(Oe.test(r)?"&":"?")+p.data,delete p.data),!1===p.cache&&(r=r.replace(je,"$1"),f=(Oe.test(r)?"&":"?")+"_="+Le+++f),p.url=r+f),p.ifModified&&(Mt.lastModified[r]&&_.setRequestHeader("If-Modified-Since",Mt.lastModified[r]),Mt.etag[r]&&_.setRequestHeader("If-None-Match",Mt.etag[r])),(p.data&&p.hasContent&&!1!==p.contentType||e.contentType)&&_.setRequestHeader("Content-Type",p.contentType),_.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Ge+"; q=0.01":""):p.accepts["*"]);for(h in p.headers)_.setRequestHeader(h,p.headers[h]);if(p.beforeSend&&(!1===p.beforeSend.call(g,_,p)||d))return _.abort();if(D="abort",y.add(p.complete),_.done(p.success),_.fail(p.error),o=rt(qe,p,e,_)){if(_.readyState=1,c&&m.trigger("ajaxSend",[_,p]),d)return _;p.async&&p.timeout>0&&(l=n.setTimeout(function(){_.abort("timeout")},p.timeout));try{d=!1,o.send(w,i)}catch(t){if(d)throw t;i(-1,t)}}else i(-1,"No Transport");return _},getJSON:function(t,e,n){return Mt.get(t,e,n,"json")},getScript:function(t,e){return Mt.get(t,void 0,e,"script")}}),Mt.each(["get","post"],function(t,e){Mt[e]=function(t,n,i,o){return Dt(n)&&(o=o||i,i=n,n=void 0),Mt.ajax(Mt.extend({url:t,type:e,dataType:o,data:n,success:i},Mt.isPlainObject(t)&&t))}}),Mt._evalUrl=function(t){return Mt.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},Mt.fn.extend({wrapAll:function(t){var e;return this[0]&&(Dt(t)&&(t=t.call(this[0])),e=Mt(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t}).append(this)),this},wrapInner:function(t){return Dt(t)?this.each(function(e){Mt(this).wrapInner(t.call(this,e))}):this.each(function(){var e=Mt(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)})},wrap:function(t){var e=Dt(t);return this.each(function(n){Mt(this).wrapAll(e?t.call(this,n):t)})},unwrap:function(t){return this.parent(t).not("body").each(function(){Mt(this).replaceWith(this.childNodes)}),this}}),Mt.expr.pseudos.hidden=function(t){return!Mt.expr.pseudos.visible(t)},Mt.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},Mt.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(t){}};var Ze={0:200,1223:204},Xe=Mt.ajaxSettings.xhr();xt.cors=!!Xe&&"withCredentials"in Xe,xt.ajax=Xe=!!Xe,Mt.ajaxTransport(function(t){var e,i;if(xt.cors||Xe&&!t.crossDomain)return{send:function(o,r){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||o["X-Requested-With"]||(o["X-Requested-With"]="XMLHttpRequest");for(a in o)s.setRequestHeader(a,o[a]);e=function(t){return function(){e&&(e=i=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===t?s.abort():"error"===t?"number"!=typeof s.status?r(0,"error"):r(s.status,s.statusText):r(Ze[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=e(),i=s.onerror=s.ontimeout=e("error"),void 0!==s.onabort?s.onabort=i:s.onreadystatechange=function(){4===s.readyState&&n.setTimeout(function(){e&&i()})},e=e("abort");try{s.send(t.hasContent&&t.data||null)}catch(t){if(e)throw t}},abort:function(){e&&e()}}}),Mt.ajaxPrefilter(function(t){t.crossDomain&&(t.contents.script=!1)}),Mt.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return Mt.globalEval(t),t}}}),Mt.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")}),Mt.ajaxTransport("script",function(t){if(t.crossDomain){var e,n;return{send:function(i,o){e=Mt(" {% set event = trigger(constant('App\\Event\\ThemeEvent::JAVASCRIPT')) %} diff --git a/templates/macros/datatables.html.twig b/templates/macros/datatables.html.twig index 075e5000..4941e43a 100644 --- a/templates/macros/datatables.html.twig +++ b/templates/macros/datatables.html.twig @@ -1,6 +1,6 @@ {% macro data_table_column_modal(name, columns) %} -