aboutsummaryrefslogtreecommitdiff
path: root/resources
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+github@gmail.com>2022-12-14 09:07:17 +0100
committerJan-Hendrik Willms <tleilax+github@gmail.com>2022-12-21 15:25:27 +0100
commitc6a7f35341cbbca3b5e7c04ad40962b47aac8ccc (patch)
treeaeb9b072e135632fdc38f93d71268f10becec63c /resources
parentcbe25c6bd513babe7abcd236208ec7f25b61c2b5 (diff)
work in progressbiest-1874
Diffstat (limited to 'resources')
-rw-r--r--resources/assets/javascripts/bootstrap/application.js31
-rw-r--r--resources/assets/javascripts/studip-ui.js252
-rw-r--r--resources/assets/javascripts/studip-ui/abstract-picker.js64
-rw-r--r--resources/assets/javascripts/studip-ui/date-picker.js166
-rw-r--r--resources/assets/javascripts/studip-ui/datetime-picker.js40
-rw-r--r--resources/assets/javascripts/studip-ui/time-picker.js306
-rw-r--r--resources/assets/stylesheets/studip-jquery-ui.less1
7 files changed, 552 insertions, 308 deletions
diff --git a/resources/assets/javascripts/bootstrap/application.js b/resources/assets/javascripts/bootstrap/application.js
index cafd07e..05335bc 100644
--- a/resources/assets/javascripts/bootstrap/application.js
+++ b/resources/assets/javascripts/bootstrap/application.js
@@ -202,37 +202,6 @@ jQuery.extend(jQuery.ui.dialog.prototype.options, {
* jQuery timepicker
* ------------------------------------------------------------------------ */
-/* German translation for the jQuery Timepicker Addon */
-/* Written by Marvin */
-(function ($) {
- $.timepicker.regional.de = {
- timeOnlyTitle: 'Zeit wählen',
- timeText: 'Zeit',
- hourText: 'Stunde',
- minuteText: 'Minute',
- secondText: 'Sekunde',
- millisecText: 'Millisekunde',
- microsecText: 'Mikrosekunde',
- timezoneText: 'Zeitzone',
- currentText: 'Jetzt',
- closeText: 'Fertig',
- timeFormat: "HH:mm",
- amNames: ['vorm.', 'AM', 'A'],
- pmNames: ['nachm.', 'PM', 'P'],
- isRTL: false,
- showTimezone: false
- };
- $.timepicker.setDefaults($.timepicker.regional.de);
-
- $(document).on('focus', '.has-time-picker', function () {
- $(this).removeClass('has-time-picker').timepicker();
- });
- $(document).on('focus', '.has-time-picker-select', function () {
- $(this).removeClass('has-time-picker-select').timepicker({controlType: 'select'});
- });
-}(jQuery));
-
-
(function ($) {
$(document).on('focusout', '.studip-timepicker', function () {
var time = $(this).val();
diff --git a/resources/assets/javascripts/studip-ui.js b/resources/assets/javascripts/studip-ui.js
index 897604a..cc9f61e 100644
--- a/resources/assets/javascripts/studip-ui.js
+++ b/resources/assets/javascripts/studip-ui.js
@@ -45,140 +45,146 @@ import Timepicker from './studip-ui/time-picker.js';
'use strict';
// Exit if datepicker is undefined (which it should never be)
- if ($.datepicker === undefined) {
+ if (!Datepicker.supportsNativeInput && $.datepicker === undefined) {
return;
}
- // Setup defaults and default locales
- var defaults = {},
- locale = {
- closeText: $gettext('Schließen'),
- prevText: $gettext('Zurück'),
- nextText: $gettext('Vor'),
- currentText: $gettext('Jetzt'),
- monthNames: [
- $gettext('Januar'),
- $gettext('Februar'),
- $gettext('März'),
- $gettext('April'),
- $gettext('Mai'),
- $gettext('Juni'),
- $gettext('Juli'),
- $gettext('August'),
- $gettext('September'),
- $gettext('Oktober'),
- $gettext('November'),
- $gettext('Dezember')
- ],
- monthNamesShort: [
- $gettext('Jan'),
- $gettext('Feb'),
- $gettext('Mär'),
- $gettext('Apr'),
- $gettext('Mai'),
- $gettext('Jun'),
- $gettext('Jul'),
- $gettext('Aug'),
- $gettext('Sep'),
- $gettext('Okt'),
- $gettext('Nov'),
- $gettext('Dez')
- ],
- dayNames: [
- $gettext('Sonntag'),
- $gettext('Montag'),
- $gettext('Dienstag'),
- $gettext('Mittwoch'),
- $gettext('Donnerstag'),
- $gettext('Freitag'),
- $gettext('Samstag')
- ],
- dayNamesShort: [
- $gettext('So'),
- $gettext('Mo'),
- $gettext('Di'),
- $gettext('Mi'),
- $gettext('Do'),
- $gettext('Fr'),
- $gettext('Sa')
- ],
- weekHeader: $gettext('Wo'),
- dateFormat: 'dd.mm.yy',
- firstDay: 1,
- isRTL: false,
- showMonthAfterYear: false,
- yearSuffix: '',
- changeMonth: true,
- changeYear: true,
- };
- // Set dayNamesMin to dayNamesShort since they are equal
- locale.dayNamesMin = locale.dayNamesShort;
-
-
- // Apply defaults including date picker handlers
- defaults = Object.assign({}, locale, {
- beforeShow (input) {
- DatePicker.refresh();
-
- if ($(input).parents('.ui-dialog').length > 0) {
- return;
+ if (!Datepicker.supportsNativeInput) {
+ // Setup defaults and default locales
+ var defaults = {},
+ locale = {
+ closeText: $gettext('Schließen'),
+ prevText: $gettext('Zurück'),
+ nextText: $gettext('Vor'),
+ currentText: $gettext('Jetzt'),
+ monthNames: [
+ $gettext('Januar'),
+ $gettext('Februar'),
+ $gettext('März'),
+ $gettext('April'),
+ $gettext('Mai'),
+ $gettext('Juni'),
+ $gettext('Juli'),
+ $gettext('August'),
+ $gettext('September'),
+ $gettext('Oktober'),
+ $gettext('November'),
+ $gettext('Dezember')
+ ],
+ monthNamesShort: [
+ $gettext('Jan'),
+ $gettext('Feb'),
+ $gettext('Mär'),
+ $gettext('Apr'),
+ $gettext('Mai'),
+ $gettext('Jun'),
+ $gettext('Jul'),
+ $gettext('Aug'),
+ $gettext('Sep'),
+ $gettext('Okt'),
+ $gettext('Nov'),
+ $gettext('Dez')
+ ],
+ dayNames: [
+ $gettext('Sonntag'),
+ $gettext('Montag'),
+ $gettext('Dienstag'),
+ $gettext('Mittwoch'),
+ $gettext('Donnerstag'),
+ $gettext('Freitag'),
+ $gettext('Samstag')
+ ],
+ dayNamesShort: [
+ $gettext('So'),
+ $gettext('Mo'),
+ $gettext('Di'),
+ $gettext('Mi'),
+ $gettext('Do'),
+ $gettext('Fr'),
+ $gettext('Sa')
+ ],
+ weekHeader: $gettext('Wo'),
+ dateFormat: 'dd.mm.yy',
+ firstDay: 1,
+ isRTL: false,
+ showMonthAfterYear: false,
+ yearSuffix: '',
+ changeMonth: true,
+ changeYear: true,
+ };
+ // Set dayNamesMin to dayNamesShort since they are equal
+ locale.dayNamesMin = locale.dayNamesShort;
+
+
+ // Apply defaults including date picker handlers
+ defaults = Object.assign({}, locale, {
+ beforeShow (input) {
+ Datepicker.refresh();
+
+ if ($(input).parents('.ui-dialog').length > 0) {
+ return;
+ }
+
+ $(input).css({
+ 'position': 'relative',
+ 'z-index': 1002
+ });
+ },
+ onSelect: function (value, instance) {
+ if (value !== instance.lastVal) {
+ $(this).change();
+ }
}
-
- $(input).css({
- 'position': 'relative',
- 'z-index': 1002
- });
- },
- onSelect: function (value, instance) {
- if (value !== instance.lastVal) {
- $(this).change();
+ });
+
+ $.datepicker.setDefaults(Object.assign({}, defaults, {
+ beforeShow (input) {
+ // Don't lose original behaviour
+ defaults.beforeShow(input);
+
+ if ($(input).parents('.ui-dialog').length > 0) {
+ $('.ui-dialog-content').bind('scroll.datepicker-scroll', _.debounce($.proxy(DpHideOnScroll, null, input), 100, {leading:true, trailing:false}));
+ }
+ $(window).bind('scroll.datepicker-scroll', _.debounce($.proxy(DpHideOnScroll, null, input), 100, {leading:true, trailing:false}));
+
+ if ($(input).closest('#sidebar').length === 0) {
+ return;
+ }
+
+ const button = input.nextElementSibling;
+ if (button && button.matches('input[type="submit"]')) {
+ button.style.position = 'relative';
+ button.style.zIndex = input.style.zIndex;
+ }
+ },
+ onClose (date, inst) {
+ $(this).one('click.picker', function () {
+ $(this).datepicker('show');
+ }).on('blur', function () {
+ $(this).off('click.picker');
+ });
+
+ if ($(this).parents('.ui-dialog').length > 0) {
+ $('.ui-dialog-content').unbind('scroll.datepicker-scroll');
+ } else {
+ $(window).unbind('scroll.datepicker-scroll');
+ }
}
- }
- });
+ }));
- $.datepicker.setDefaults(Object.assign({}, defaults, {
- beforeShow (input) {
- // Don't lose original behaviour
- defaults.beforeShow(input);
-
- if ($(input).parents('.ui-dialog').length > 0) {
- $('.ui-dialog-content').bind('scroll.datepicker-scroll', _.debounce($.proxy(DpHideOnScroll, null, input), 100, {leading:true, trailing:false}));
- }
- $(window).bind('scroll.datepicker-scroll', _.debounce($.proxy(DpHideOnScroll, null, input), 100, {leading:true, trailing:false}));
-
- if ($(input).closest('#sidebar').length === 0) {
- return;
- }
-
- const button = input.nextElementSibling;
- if (button && button.matches('input[type="submit"]')) {
- button.style.position = 'relative';
- button.style.zIndex = input.style.zIndex;
- }
- },
- onClose (date, inst) {
- $(this).one('click.picker', function () {
- $(this).datepicker('show');
- }).on('blur', function () {
- $(this).off('click.picker');
- });
-
- if ($(this).parents('.ui-dialog').length > 0) {
- $('.ui-dialog-content').unbind('scroll.datepicker-scroll');
- } else {
- $(window).unbind('scroll.datepicker-scroll');
- }
+ var DpHideOnScroll = function () {
+ var input = arguments[0];
+ $(input).blur();
+ $(input).datepicker('hide');
}
- }));
-
- var DpHideOnScroll = function () {
- var input = arguments[0];
- $(input).blur();
- $(input).datepicker('hide');
}
// Attach global focus handler on date picker elements
$(document).on('focus', Datepicker.selector, () => {
+ if (!$(event.target).is('input[type="date"]')) {
+ $(event.target).attr('type', 'date');
+ }
+
Datepicker.init();
});
diff --git a/resources/assets/javascripts/studip-ui/abstract-picker.js b/resources/assets/javascripts/studip-ui/abstract-picker.js
new file mode 100644
index 0000000..b046d9d
--- /dev/null
+++ b/resources/assets/javascripts/studip-ui/abstract-picker.js
@@ -0,0 +1,64 @@
+class AbstractPicker
+{
+ static dataHandlers = {};
+
+ static supportsNativeInput() {
+ return true;
+ }
+
+ static init() {
+ const initIndex = this.name.toLowerCase();
+
+ Array.from(document.querySelectorAll(this.selector)).filter(node => {
+ return node.dataset[initIndex] === undefined;
+ }).forEach(element => {
+ element.dataset[initIndex] = true;
+
+ const picker = new this(element);
+ });
+
+ }
+
+ node;
+
+ constructor(node) {
+ if (new.target === AbstractPicker) {
+ throw new TypeError('Cannot construct an abstract picker');
+ }
+
+ if (this.constructor.selector === undefined) {
+ throw new Error('No selector getter defined');
+ }
+
+ if (this.constructor.datasetIndex === undefined) {
+ throw new Error('No datasetIndex getter defined');
+ }
+
+ this.node = node;
+
+ this.setup();
+
+ this.refresh();
+ node.addEventListener('change', () => this.refresh());
+ }
+
+ setup() {
+ }
+
+ refresh() {
+ const options = this.node.dataset[this.constructor.datasetIndex] !== undefined
+ ? JSON.parse(this.node.dataset[this.constructor.datasetIndex])
+ : {};
+
+ console.log('in refresh', options);
+
+ Object.entries(options).forEach(([key, value]) => {
+ if (this.constructor.dataHandlers[key] !== undefined) {
+ console.log('match', key, value);
+ this.constructor.dataHandlers[key](this.node, value);
+ }
+ });
+ }
+}
+
+export default AbstractPicker;
diff --git a/resources/assets/javascripts/studip-ui/date-picker.js b/resources/assets/javascripts/studip-ui/date-picker.js
index a9fc870..0393aac 100644
--- a/resources/assets/javascripts/studip-ui/date-picker.js
+++ b/resources/assets/javascripts/studip-ui/date-picker.js
@@ -1,26 +1,90 @@
// Setup Stud.IP's own datepicker extensions
-export default {
- selector: '.has-date-picker,[data-date-picker]',
+function getValue(element) {
+ if (Datepicker.supportsNativeInput) {
+ return element.value;
+ }
+
+ return $(element).datepicker('getDate');
+}
+
+function setValue(element, value) {
+ if (Datepicker.supportsNativeInput) {
+ element.value = value;
+ return;
+ }
+
+ $(element).datepicker('setDate', value);
+}
+
+function getOption(element, option) {
+ if (Datepicker.supportsNativeInput) {
+ return element.getAttribute(option) ?? null;
+ }
+
+ const mapping = {
+ min: 'minDate',
+ max: 'maxDate',
+ };
+
+ return $(element).datepicker('option', mapping[option] ?? option);
+}
+
+function setOption(element, option, value) {
+ if (Datepicker.supportsNativeInput) {
+ element.setAttribute(option, value);
+ return;
+ }
+
+ const mapping = {
+ min: 'minDate',
+ max: 'maxDate',
+ };
+
+ $(element).datepicker('option', mapping[option] ?? option, value);
+}
+
+const Datepicker = {
+ supportsNativeInput: (function () {
+ let input = document.createElement('input');
+ input.setAttribute('type', 'date');
+
+ const invalid = 'not-a-valid-date';
+ input.setAttribute('value', invalid);
+
+ return input.value !== invalid;
+ })(),
+ selector: [
+ '.has-date-picker',
+ '[data-date-picker]',
+ 'input[type="date"]',
+ ].join(','),
// Initialize all datepickers that not yet been initialized (e.g. in dialogs)
- init: function () {
- $(this.selector).filter(function () {
- return $(this).data('date-picker-init') === undefined;
- }).each(function () {
- $(this).data('date-picker-init', true).datepicker();
+ init() {
+ Array.from(document.querySelectorAll(this.selector)).filter(node => {
+ return node.dataset.datePickerInit === undefined;
+ }).forEach(element => {
+ element.dataset.datePickerInit = true;
+
+ if (!this.supportsNativeInput) {
+ $(element).datepicker();
+ }
+
+ this.refresh(element);
+
+ element.addEventListener('change', event => {
+ this.refresh(event.target);
+ });
});
},
// Apply registered handlers. Take care: This happens upon before a
// picker is shown as well as after a date has been selected.
- refresh: function () {
- $(this.selector).each(function () {
- var element = this,
- options = $(element).data().datePicker;
- if (options) {
- $.each(options, function (key, value) {
- if (this.dataHandlers[key] !== undefined) {
- this.dataHandlers[key].call(element, value);
- }
- });
+ refresh(node) {
+ const options = node.dataset.datePicker !== undefined
+ ? JSON.parse(node.dataset.datePicker)
+ : {};
+ Object.entries(options).forEach(([key, value]) => {
+ if (this.dataHandlers[key] !== undefined) {
+ this.dataHandlers[key](node, value);
}
});
},
@@ -30,16 +94,11 @@ export default {
// the maximum allowed date the other date.
// This will also set this date to the maximum allowed date if it
// currently later than the allowed maximum date.
- '<='(selector, offset) {
- var this_date = $(this).datepicker('getDate'),
- max_date = null,
- temp,
- adjustment = 0;
-
- if ($(this).data().datePicker.offset) {
- temp = $(this).data().datePicker.offset;
- adjustment = parseInt($(temp).val(), 10);
- }
+ '<='(node, selector, offset = null) {
+ let this_date = getValue(node);
+ let max_date = null;
+
+ offset = offset ?? node.dataset.datePicker?.offset ?? 0;
// Get max date by either actual dates or maxDate options on
// all matching elements
@@ -47,7 +106,7 @@ export default {
max_date = new Date();
} else {
$(selector).each(function () {
- var date = $(this).datepicker('getDate') || $(this).datepicker('option', 'maxDate');
+ var date = getValue(this) ?? getOption(this, 'max');
if (date && (!max_date || date < max_date)) {
max_date = new Date(date);
}
@@ -56,49 +115,41 @@ export default {
// Set max date and adjust current date if neccessary
if (max_date) {
- max_date.setTime(max_date.getTime() - (offset || 0) * 24 * 60 * 60 * 1000);
-
- temp = new Date(max_date);
- temp.setDate(temp.getDate() - adjustment);
+ max_date.setTime(max_date.getTime() - offset * 24 * 60 * 60 * 1000);
if (this_date && this_date > max_date) {
- $(this).datepicker('setDate', temp);
+ setValue(node, max_date);
}
- $(this).datepicker('option', 'maxDate', max_date);
+ setOption(node, 'max', max_date);
} else {
- $(this).datepicker('option', 'maxDate', null);
+ setOption(node, 'max', null);
}
},
// Ensure this date is earlier (<) than another date by setting the
// maximum allowed date to the other date - 1 day.
// This will also set this date to the maximum allowed date - 1 day
// if it is currently later than the allowed maximum date.
- '<'(selector) {
- this['<='].call(this, selector, 1);
+ '<'(node, selector) {
+ this['<='](node, selector, 1);
},
// Ensure this date is not earlier (>=) than another date by setting
// the minimum allowed date to the other date.
// This will also set this date to the minimum allowed date if it is
// currently earlier than the allowed minimum date.
- '>='(selector, offset) {
- var this_date = $(this).datepicker('getDate'),
- min_date = null,
- temp,
- adjustment = 0;
-
- if ($(this).data().datePicker.offset) {
- temp = $(this).data().datePicker.offset;
- adjustment = parseInt($(temp).val(), 10);
- }
+ '>='(node, selector, offset = null) {
+ let this_date = getValue(node);
+ let min_date;
+
+ offset = offset ?? node.dataset.datePicker?.offset ?? 0;
// Get min date by either actual dates or minDate options on
// all matching elements
if (selector === 'today') {
min_date = new Date();
} else {
- $(selector).each(function () {
- var date = $(this).datepicker('getDate') || $(this).datepicker('option', 'minDate');
+ document.querySelectorAll(selector).forEach(n => {
+ var date = getValue(n) ?? getOption(n, 'min');
if (date && (!min_date || date > min_date)) {
min_date = new Date(date);
}
@@ -107,26 +158,25 @@ export default {
// Set min date and adjust current date if neccessary
if (min_date) {
- min_date.setTime(min_date.getTime() + (offset || 0) * 24 * 60 * 60 * 1000);
-
- temp = new Date(min_date);
- temp.setDate(temp.getDate() + adjustment);
+ min_date.setTime(min_date.getTime() + offset * 24 * 60 * 60 * 1000);
if (this_date && this_date < min_date) {
- $(this).datepicker('setDate', temp);
+ setValue(node, min_date);
}
- $(this).datepicker('option', 'minDate', min_date);
+ setOption(node, 'min', min_date);
} else {
- $(this).datepicker('option', 'minDate', null);
+ setOption(node, 'min', null);
}
},
// Ensure this date is later (>) than another date by setting the
// minimum allowed date to the other date + 1 day.
// This will also set this date to the minimum allowed date + 1 day
// if it is currently earlier than the allowed minimum date.
- '>'(selector) {
- this['>='].call(this, selector, 1);
+ '>'(node, selector) {
+ this['>='](node, selector, 1);
}
}
};
+
+export default Datepicker;
diff --git a/resources/assets/javascripts/studip-ui/datetime-picker.js b/resources/assets/javascripts/studip-ui/datetime-picker.js
index 0e8868a..cb05be0 100644
--- a/resources/assets/javascripts/studip-ui/datetime-picker.js
+++ b/resources/assets/javascripts/studip-ui/datetime-picker.js
@@ -15,15 +15,10 @@ export default {
].join(','),
// Initialize all datetimepickers that not yet been initialized (e.g. in dialogs)
init() {
- const elements = Array.from(document.querySelectorAll(this.selector)).filter(node => {
+ Array.from(document.querySelectorAll(this.selector)).filter(node => {
return node.dataset.datetimePickerInit === undefined;
- });
-
- elements.forEach(element => {
+ }).forEach(element => {
element.dataset.datetimePickerInit = true;
- element.addEventListener('change', event => {
- this.refresh(event.target);
- });
// Load and apply polyfill if necessary
if (!this.supportsNativeInput) {
@@ -32,26 +27,29 @@ export default {
element.classList.add('hasTimepicker');
});
}
- });
- $(this.selector).filter(function () {
- return $(this).data('datetime-picker-init') === undefined;
- }).each(function () {
- $(this).data('datetime-picker-init', true).datetimepicker();
+ this.refresh(element);
+
+ element.addEventListener('change', event => {
+ this.refresh(event.target);
+ });
});
+ //
+ // $(this.selector).filter(function () {
+ // return $(this).data('datetime-picker-init') === undefined;
+ // }).each(function () {
+ // $(this).data('datetime-picker-init', true).datetimepicker();
+ // });
},
// Apply registered handlers. Take care: This happens upon before a
// picker is shown as well as after a date has been selected.
refresh(node) {
- $(this.selector).each(function () {
- var element = this,
- options = $(element).data().datetimePicker;
- if (options) {
- $.each(options, function (key, value) {
- if (this.dataHandlers[key] !== undefined) {
- this.dataHandlers[key].call(element, value);
- }
- });
+ const options = node.dataset.datetimePicker !== undefined
+ ? JSON.parse(node.dataset.datetimePicker)
+ : {};
+ Object.entries(options).forEach(([key, value]) => {
+ if (this.dataHandlers[key] !== undefined) {
+ this.dataHandlers[key](node, value);
}
});
},
diff --git a/resources/assets/javascripts/studip-ui/time-picker.js b/resources/assets/javascripts/studip-ui/time-picker.js
index 4f331c8..88515e2 100644
--- a/resources/assets/javascripts/studip-ui/time-picker.js
+++ b/resources/assets/javascripts/studip-ui/time-picker.js
@@ -1,85 +1,43 @@
-export default {
- supportsNativeInput: (function () {
- let input = document.createElement('input');
- input.setAttribute('type', 'time');
-
- const invalid = 'not-a-time';
- input.setAttribute('value', invalid);
-
- return input.value !== invalid;
- })(),
- selector: [
- '.has-time-picker',
- '[data-time-picker]',
- 'input[type="time"]',
- ].join(','),
- // Initialize all datetimepickers that not yet been initialized (e.g. in dialogs)
- init() {
- const elements = Array.from(document.querySelectorAll(this.selector)).filter(node => {
- return node.dataset.timePickerInit === undefined;
- });
-
- elements.forEach(element => {
- element.dataset.timePickerInit = true;
- element.addEventListener('change', event => {
- this.refresh(event.target);
- });
+import AbstractPicker from './abstract-picker.js';
- // Load and apply polyfill if necessary
- if (!this.supportsNativeInput) {
- import('time-input-polyfill').then(({default: TimePolyfill}) => {
- new TimePolyfill(element);
- element.classList.add('hasTimepicker');
- });
- }
- });
- },
- // Apply registered handlers. Take care: This happens upon before a
- // picker is shown as well as after a date has been selected.
- refresh(node) {
- const options = node.dataset.timePicker !== undefined
- ? JSON.parse(node.dataset.timePicker)
- : {};
- Object.entries(options).forEach(([key, value]) => {
- if (this.dataHandlers[key] !== undefined) {
- this.dataHandlers[key](node, value);
- }
- });
- },
- parseTime(time) {
- const split = time.split(':');
- return {
- hour: parseInt(split[0], 10),
- minute: parseInt(split[1], 10)
- };
- },
- createTime(hours, minutes, minute_offset = 0) {
- // Adjust minutes if offset is given
- minutes = minutes + minute_offset;
- if (minutes >= 60) {
- hours += 1;
- minutes -= 60;
- } else if (minutes < 0) {
- hours -= 1;
- minutes += 60;
- }
+const nativeSupport = (() => {
+ let input = document.createElement('input');
+ input.setAttribute('type', 'time');
- // Sanitize hours
- hours = Math.min(23, Math.max(0, hours));
+ const invalid = 'not-a-time';
+ input.setAttribute('value', invalid);
- return ('0' + hours).slice(-2) + ':' + ('0' + minutes).slice(-2);
- },
- // Define handlers for any data-time-picker option
- dataHandlers: {
+ return input.value !== invalid;
+})();
+
+class Timepicker extends AbstractPicker
+{
+ static supportsNativeInput() {
+ return nativeSupport;
+ }
+
+ static get selector() {
+ return [
+ '.has-time-picker',
+ '[data-time-picker]',
+ 'input[type="time"]',
+ ].join(',');
+ }
+
+ static get datasetIndex() {
+ return 'timePicker';
+ }
+
+ static dataHandlers = {
// Ensure this time is not later (<=) than another time by setting
// the maximum allowed time on the other time.
// This will also set this time to the maximum allowed time if it is
// currently later than the allowed maximum time.
- '<='(node, selector, offset) {
+ '<='(node, selector, offset = null) {
const this_time = node.value;
let max_time = null;
- offset = offset ?? node.dataset.offset ?? 0;
+ offset = offset ?? node.dataset.timepicker?.offset ?? 0;
document.querySelectorAll(selector).forEach(n => {
const time = n.value;
@@ -117,7 +75,7 @@ export default {
// the minimum allowed date to the other date.
// This will also set this date to the minimum allowed date if it is
// currently earlier than the allowed minimum date.
- '>='(node, selector, offset) {
+ '>='(node, selector, offset = null) {
const this_time = node.value;
let min_time = null;
@@ -156,4 +114,204 @@ export default {
this['>='](node, selector, 1);
}
}
-};
+
+ static parseTime(time) {
+ const split = time.split(':');
+ return {
+ hour: parseInt(split[0], 10),
+ minute: parseInt(split[1], 10)
+ };
+ }
+
+ static createTime(hours, minutes, minute_offset = 0) {
+ // Adjust minutes if offset is given
+ minutes = minutes + minute_offset;
+ if (minutes >= 60) {
+ hours += 1;
+ minutes -= 60;
+ } else if (minutes < 0) {
+ hours -= 1;
+ minutes += 60;
+ }
+
+ // Sanitize hours
+ hours = Math.min(23, Math.max(0, hours));
+
+ return ('0' + hours).slice(-2) + ':' + ('0' + minutes).slice(-2);
+ }
+
+ setup() {
+ super.setup();
+
+ // Load and apply polyfill if necessary
+ if (!Timepicker.supportsNativeInput) {
+ import('time-input-polyfill').then(({default: TimePolyfill}) => {
+ new TimePolyfill(this.node);
+ this.node.classList.add('has-time-picker');
+ });
+ }
+ }
+}
+
+// const Timepicker = {
+// supportsNativeInput: (function () {
+// let input = document.createElement('input');
+// input.setAttribute('type', 'time');
+//
+// const invalid = 'not-a-time';
+// input.setAttribute('value', invalid);
+//
+// return input.value !== invalid;
+// })(),
+// selector: [
+// '.has-time-picker',
+// '[data-time-picker]',
+// 'input[type="time"]',
+// ].join(','),
+// // Initialize all datetimepickers that not yet been initialized (e.g. in dialogs)
+// init() {
+// Array.from(document.querySelectorAll(this.selector)).filter(node => {
+// return node.dataset.timePickerInit === undefined;
+// }).forEach(element => {
+// element.dataset.timePickerInit = true;
+//
+// // Load and apply polyfill if necessary
+// if (!this.supportsNativeInput) {
+// import('time-input-polyfill').then(({default: TimePolyfill}) => {
+// new TimePolyfill(element);
+// element.classList.add('hasTimepicker');
+// });
+// }
+//
+// this.refresh(element);
+//
+// element.addEventListener('change', event => {
+// this.refresh(event.target);
+// });
+// });
+// },
+// // Apply registered handlers. Take care: This happens upon before a
+// // picker is shown as well as after a date has been selected.
+// refresh(node) {
+// const options = node.dataset.timePicker !== undefined
+// ? JSON.parse(node.dataset.timePicker)
+// : {};
+// Object.entries(options).forEach(([key, value]) => {
+// if (this.dataHandlers[key] !== undefined) {
+// this.dataHandlers[key](node, value);
+// }
+// });
+// },
+// parseTime(time) {
+// const split = time.split(':');
+// return {
+// hour: parseInt(split[0], 10),
+// minute: parseInt(split[1], 10)
+// };
+// },
+// createTime(hours, minutes, minute_offset = 0) {
+// // Adjust minutes if offset is given
+// minutes = minutes + minute_offset;
+// if (minutes >= 60) {
+// hours += 1;
+// minutes -= 60;
+// } else if (minutes < 0) {
+// hours -= 1;
+// minutes += 60;
+// }
+//
+// // Sanitize hours
+// hours = Math.min(23, Math.max(0, hours));
+//
+// return ('0' + hours).slice(-2) + ':' + ('0' + minutes).slice(-2);
+// },
+// // Define handlers for any data-time-picker option
+// dataHandlers: {
+// // Ensure this time is not later (<=) than another time by setting
+// // the maximum allowed time on the other time.
+// // This will also set this time to the maximum allowed time if it is
+// // currently later than the allowed maximum time.
+// '<='(node, selector, offset = null) {
+// const this_time = node.value;
+// let max_time = null;
+//
+// offset = offset ?? node.dataset.timepicker?.offset ?? 0;
+//
+// document.querySelectorAll(selector).forEach(n => {
+// const time = n.value;
+// if (time && (!max_time || time < max_time)) {
+// max_time = time;
+// }
+// });
+//
+// // Set max time and adjust current time if neccessary
+// if (max_time) {
+// const parsed = Timepicker.parseTime(max_time);
+// max_time = Timepicker.createTime(
+// parsed.hour,
+// parsed.minute,
+// -offset
+// );
+//
+// if (this_time && this_time > max_time) {
+// node.value = max_time;
+// }
+//
+// node.setAttribute('max', max_time);
+// } else {
+// node.removeAttribute('max');
+// }
+// },
+// // Ensure this date is earlier (<) than another date by setting the
+// // maximum allowed date to the other date - 1 day.
+// // This will also set this date to the maximum allowed date - 1 day
+// // if it is currently later than the allowed maximum date.
+// '<'(node, selector) {
+// this['<='](node, selector, 1);
+// },
+// // Ensure this date is not earlier (>=) than another date by setting
+// // the minimum allowed date to the other date.
+// // This will also set this date to the minimum allowed date if it is
+// // currently earlier than the allowed minimum date.
+// '>='(node, selector, offset = null) {
+// const this_time = node.value;
+// let min_time = null;
+//
+// offset = offset ?? node.dataset.offset ?? 0;
+//
+// document.querySelectorAll(selector).forEach(n => {
+// const time = n.value;
+// if (time && (!min_time || time < min_time)) {
+// min_time = time;
+// }
+// });
+//
+// // Set min time and adjust current time if neccessary
+// if (min_time) {
+// const parsed = Timepicker.parseTime(min_time);
+// min_time = Timepicker.createTime(
+// parsed.hour,
+// parsed.minute,
+// offset
+// );
+//
+// if (this_time && this_time < min_time) {
+// node.value = min_time;
+// }
+//
+// node.setAttribute('min', min_time);
+// } else {
+// node.removeAttribute('min');
+// }
+// },
+// // Ensure this date is later (>) than another date by setting the
+// // minimum allowed date to the other date + 1 day.
+// // This will also set this date to the minimum allowed date + 1 day
+// // if it is currently earlier than the allowed minimum date.
+// '>'(node, selector) {
+// this['>='](node, selector, 1);
+// }
+// }
+// };
+
+export default Timepicker;
diff --git a/resources/assets/stylesheets/studip-jquery-ui.less b/resources/assets/stylesheets/studip-jquery-ui.less
index c9fb6de..38af845 100644
--- a/resources/assets/stylesheets/studip-jquery-ui.less
+++ b/resources/assets/stylesheets/studip-jquery-ui.less
@@ -5,7 +5,6 @@
@import (less) "jquery-ui.structure.css";
@import "less/jquery-ui/custom.less";
@import "less/jquery-ui/studip.less";
-@import "~jquery-ui-timepicker-addon/dist/jquery-ui-timepicker-addon.css";
@import "~multiselect/css/multi-select.css";
// Tweaks/adjustments for multi-select