aboutsummaryrefslogtreecommitdiff
path: root/resources/assets/javascripts/studip-ui
diff options
context:
space:
mode:
Diffstat (limited to 'resources/assets/javascripts/studip-ui')
-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
4 files changed, 423 insertions, 153 deletions
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;