diff --git a/app/assets/javascripts/datetimepicker.js b/app/assets/javascripts/datetimepicker.js new file mode 100644 index 000000000..07aaf97a6 --- /dev/null +++ b/app/assets/javascripts/datetimepicker.js @@ -0,0 +1,107 @@ +// Initialize on page load (when js file is dynamically loaded) +$(document).ready(startInitialization); + +// On page change (e.g. go back and forth in browser) +$(document).on('turbolinks:before-cache', () => { + // Remove stale datetimepickers + $('.tempus-dominus-widget').remove(); +}); + +function startInitialization() { + const pickerElements = $('.td-picker'); + if (pickerElements.length == 0) { + console.error('No datetimepicker element found on page, although requested.'); + return; + } + + pickerElements.each((i, element) => { + element = $(element); + const datetimePicker = initDatetimePicker(element); + registerErrorHandlers(datetimePicker, element); + registerFocusHandlers(datetimePicker, element); + }); +} + +function initDatetimePicker(element) { + // see https://getdatepicker.com + return new tempusDominus.TempusDominus( + element.get(0), + { + display: { + sideBySide: true, // clock to the right of the calendar + }, + localization: { + startOfTheWeek: 1, + // choose format to be compliant with backend time format + format: 'yyyy-MM-dd HH:mm', + hourCycle: 'h23', + } + } + ); +} + +function registerErrorHandlers(datetimePicker, element) { + // Catch Tempus Dominus error when user types in invalid date + // this is rather hacky at the moment, see this discussion: + // https://github.com/Eonasdan/tempus-dominus/discussions/2656 + datetimePicker.dates.oldParseInput = datetimePicker.dates.parseInput; + datetimePicker.dates.parseInput = (input) => { + try { + return datetimePicker.dates.oldParseInput(input); + } catch (err) { + const errorMsg = element.find('.td-error').data('td-invalid-date'); + element.find('.td-error').text(errorMsg).show(); + datetimePicker.dates.clear(); + } + }; + + datetimePicker.subscribe(tempusDominus.Namespace.events.change, (e) => { + // see https://getdatepicker.com/6/namespace/events.html#change + + // Clear error message + if (e.isValid && !e.isClear) { + element.find('.td-error').empty(); + } + + // If date was selected, close datetimepicker. + // However: leave the datetimepicker open if user only changed time + if (e.oldDate && e.date && !hasUserChangedDate(e.oldDate, e.date)) { + datetimePicker.hide(); + } + }); +} + +function hasUserChangedDate(oldDate, newDate) { + return oldDate.getHours() != newDate.getHours() + || oldDate.getMinutes() != newDate.getMinutes(); +} + +function registerFocusHandlers(datetimePicker, element) { + // Show datetimepicker when user clicks in text field next to button + // or when input field receives focus + var isButtonInvokingFocus = false; + + element.find('.td-input').on('click focusin', (e) => { + try { + if (!isButtonInvokingFocus) { + datetimePicker.show(); + } + } + finally { + isButtonInvokingFocus = false; + } + }); + + element.find('.td-picker-button').on('click', () => { + isButtonInvokingFocus = true; + element.find('.td-input').focus(); + }); + + // Hide datetimepicker when input field loses focus + element.find('.td-input').blur((e) => { + if (!e.relatedTarget) { + return; + } + datetimePicker.hide(); + }); +} diff --git a/app/assets/javascripts/media.coffee b/app/assets/javascripts/media.coffee index d6aaebb90..8869ee596 100644 --- a/app/assets/javascripts/media.coffee +++ b/app/assets/javascripts/media.coffee @@ -17,13 +17,6 @@ fancyTimeFormat = (time) -> output $(document).on 'turbolinks:load', -> - - # init datetimepicker - $('#release_date').datetimepicker - format: 'd.m.Y H:i' - inline: false - lang: 'en' - # disable/enable search field on the media search page, depending on # whether 'all tags'/'all editors'/... are selected $('[id^="search_all_"]').on 'change', -> @@ -395,14 +388,8 @@ $(document).on 'turbolinks:load', -> return $('#release_date').on 'focus', -> + # Select other option if user clicks on release date input field $('#medium_release_now_0').prop('checked', true) - $('#release_date').datetimepicker('toggle') - return - - $('#medium_assignment_deadline').on 'focus', -> - $(this).datetimepicker - format: 'd.m.Y H:i' - inline: false return $('#medium_create_assignment').on 'click', -> diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 82d42fcd6..5c9da133f 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -15,23 +15,10 @@ // const images = require.context('../images', true) // const imagePath = (name) => images(name, true) -// JQuery Datetimepicker fix -// import '...' does not work, random names for the import don't work either -// we use "css" and "myLib" imports although these strings do not show up anywhere -// in our codebase. This is just a hack to make the imports-loader work. Maybe it has -// to do with how webpacker resolves the imports or how jquery-datetimepicker exports -// its module. -import css from 'jquery-datetimepicker/build/jquery.datetimepicker.min.css' -import myLib from 'imports-loader?imports=default%20jquery%20$!./../../../node_modules/jquery-datetimepicker/build/jquery.datetimepicker.full.min.js' - -import moment from "moment"; // require -window.moment = moment; import { WidgetInstance } from "friendly-challenge"; var friendlyChallengeWidgetInstance = WidgetInstance -$.datetimepicker.setLocale('de'); - document.addEventListener("turbolinks:load", function () { var doneCallback, element, options, widget; diff --git a/app/javascript/packs/application.sass b/app/javascript/packs/application.sass index 0e08f8e6d..e69de29bb 100644 --- a/app/javascript/packs/application.sass +++ b/app/javascript/packs/application.sass @@ -1 +0,0 @@ -@import "jquery-datetimepicker/build/jquery.datetimepicker.min.css" \ No newline at end of file diff --git a/app/views/assignments/_form.html.erb b/app/views/assignments/_form.html.erb index 8377dc5f0..0eec8828a 100644 --- a/app/views/assignments/_form.html.erb +++ b/app/views/assignments/_form.html.erb @@ -1,3 +1,5 @@ +<%= javascript_include_tag 'datetimepicker' %> +