diff options
Diffstat (limited to 'resources/assets/javascripts/studip-ui')
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; |
