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' %> +
<%= form_with model: assignment do |f| %> @@ -10,15 +12,33 @@ id="assignment-title-error">
-
- <%= f.text_field :deadline, - class: 'form-control', - autocomplete: 'off', - id: "assignment_deadline_#{assignment.id}" %> -
+ + <%# Datetimepicker for assignment deadline %> +
+
+ <%= f.text_field :deadline, + class: 'form-control td-input', + autocomplete: 'off', + 'data-td-target': '#assignment-picker' %> + + + +
+
+ +
<%= f.select :medium_id, options_for_select(Medium.where(teachable: assignment.lecture, @@ -48,12 +68,13 @@ id="assignment-deletion-date-error">
-
+
<%= f.submit t('buttons.save'), class: 'btn btn-sm btn-primary ms-2' %> <%= link_to t('buttons.cancel'), cancel_editing_assignment_path(assignment), class: 'btn btn-sm btn-secondary', + role: 'button', remote: true %>
diff --git a/app/views/assignments/_row.html.erb b/app/views/assignments/_row.html.erb index e38e1dc53..d1fbf07d2 100644 --- a/app/views/assignments/_row.html.erb +++ b/app/views/assignments/_row.html.erb @@ -5,7 +5,7 @@
<%= assignment.title %>
-
+
<% if assignment.deadline %> <%= I18n.l(assignment.deadline, format: :long) %> <% end %> @@ -26,7 +26,7 @@ <%= I18n.l(assignment.deletion_date, format: :long) %> <% end %>
-
+
<% if assignment.persisted? %> <%= link_to edit_assignment_path(assignment), class: 'text-dark', diff --git a/app/views/assignments/create.coffee b/app/views/assignments/create.coffee index 3aab03b90..9e938c3f0 100644 --- a/app/views/assignments/create.coffee +++ b/app/views/assignments/create.coffee @@ -1,9 +1,9 @@ # clean up from previous error messages -$('#assignment_title').removeClass('is-invalid') +$('#assignment_title_').removeClass('is-invalid') $('#assignment-title-error').empty() $('#assignment_deadline').removeClass('is-invalid') $('#assignment-deadline-error').empty() -$('#assignment_deletion_date').removeClass('is-invalid') +$('#assignment_deletion_date_').removeClass('is-invalid') $('#assignment-deletion-date-error').empty() # display error message @@ -11,7 +11,7 @@ $('#assignment-deletion-date-error').empty() <% if @errors[:title].present? %> $('#assignment-title-error') .append('<%= @errors[:title].join(" ") %>').show() -$('#assignment_title').addClass('is-invalid') +$('#assignment_title_').addClass('is-invalid') <% end %> <% if @errors[:deadline].present? %> $('#assignment-deadline-error') @@ -21,7 +21,7 @@ $('#assignment_deadline').addClass('is-invalid') <% if @errors[:deletion_date].present? %> $('#assignment-deletion-date-error') .append('<%= @errors[:deletion_date].join(" ") %>').show() -$('#assignment_deletion_date').addClass('is-invalid') +$('#assignment_deletion_date_').addClass('is-invalid') <% end %> <% else %> $('.assignmentRow[data-id="0"') diff --git a/app/views/assignments/edit.coffee b/app/views/assignments/edit.coffee index 60d65a9ff..68dab3107 100644 --- a/app/views/assignments/edit.coffee +++ b/app/views/assignments/edit.coffee @@ -16,10 +16,3 @@ new TomSelect('#assignment_medium_id_<%= @assignment.id %>', # make sure that no medium is preselected $('#assignment_medium_id_<%= @assignment.id %>').val(null).trigger('change') <% end %> - -# using moment to format the date right -d = moment($("#assignment_deadline_<%= @assignment.id %>").val(),"YYYY-MM-DD hh:mm:ss z") -$("#assignment_deadline_<%= @assignment.id %>").val (d.format("DD.MM.Y H:mm")) -$("#assignment_deadline_<%= @assignment.id %>").datetimepicker - format:'d.m.Y H:i' - inline:false diff --git a/app/views/assignments/new.js.erb b/app/views/assignments/new.js.erb index df9a0beee..c7872b491 100644 --- a/app/views/assignments/new.js.erb +++ b/app/views/assignments/new.js.erb @@ -16,8 +16,3 @@ new TomSelect('#assignment_medium_id_', { }); $('#assignment_medium_id_').val(null).trigger('change'); - -$("#assignment_deadline_").datetimepicker({ - format: 'd.m.Y H:i', - inline: false -}); \ No newline at end of file diff --git a/app/views/assignments/update.coffee b/app/views/assignments/update.coffee index 55c4573c4..0378a3237 100644 --- a/app/views/assignments/update.coffee +++ b/app/views/assignments/update.coffee @@ -2,6 +2,8 @@ # clean up from previous error messages $('#assignment_title').removeClass('is-invalid') $('#assignment-title-error').empty() +$('#assignment_deadline').removeClass('is-invalid') +$('#assignment-deadline-error').empty() # display error message <% if @errors[:title].present? %> diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index 64336b5d0..884f3f77c 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -34,6 +34,18 @@ integrity="sha256-rADF2WO6GbkNb5O9gqHdjQ/XhrfnXDtPdHdRe5KQmek=" crossorigin="anonymous"> + + + + + + + + +