From 3545893c3d778f995bff76238cd55183311147a3 Mon Sep 17 00:00:00 2001 From: Mantas Date: Thu, 7 Jun 2018 15:04:47 +0300 Subject: [PATCH] Fixes #1693 pending till date issue --- .../controllers/_ui_element/basedate.coffee | 156 +++++++++++++++ .../app/controllers/_ui_element/date.coffee | 166 +--------------- .../controllers/_ui_element/datetime.coffee | 181 +++--------------- .../javascripts/app/lib/app_post/i18n.coffee | 18 ++ public/assets/tests/ui.js | 31 +++ 5 files changed, 239 insertions(+), 313 deletions(-) create mode 100644 app/assets/javascripts/app/controllers/_ui_element/basedate.coffee diff --git a/app/assets/javascripts/app/controllers/_ui_element/basedate.coffee b/app/assets/javascripts/app/controllers/_ui_element/basedate.coffee new file mode 100644 index 000000000..2603e6a10 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/basedate.coffee @@ -0,0 +1,156 @@ +# coffeelint: disable=camel_case_classes +# Base class for providing date picker. Must be extended +class App.UiElement.basedate + @templateName: -> + throw 'Must override in a subclass' + + @render: (attributeOrig) -> + attribute = _.clone(attributeOrig) + attribute.nameRaw = attribute.name + attribute.name = "{#{@templateName()}}#{attribute.name}" + + item = $( App.view("generic/#{@templateName()}")( + attribute: attribute + ) ) + + # set our custom template + $.fn.datepicker.defaults.template = App.view('generic/datepicker')() + + # apply date widgets + $.fn.datepicker.dates['custom'] = @buildCustomDates() + + @applyPickers(item, attribute) + @bindEvents(item, attribute) + + item + + @log: (name, args...) -> + App.Log.debug "Ui.element.#{@templateName()}.#{name}", args... + + @applyPickers: (item, attribute) -> + item.find('.js-datepicker').datepicker( + weekStart: 1 + autoclose: true + todayBtn: 'linked' + todayHighlight: true + format: App.i18n.timeFormat().date + rtl: App.i18n.dir() is 'rtl' + container: item + language: 'custom' + ) + + @setNewTimeInitial(item, attribute) + + # observer changes / update needed to forece rerender to get correct today shown + @bindEvents: (item, attribute) -> + item + .find('input') + .bind('focus', (e) -> + item.find('.js-datepicker').datepicker('rerender') + ).bind('keyup blur change', (e) => + @setNewTime(item, attribute, 0) + @validation(item, attribute, true) + ) + + item.bind('validate', (e) => + @validation(item, attribute) + ) + + @setNewTime: (item, attribute, tolerant = false) -> + currentInput = @currentInput(item, attribute) + return if !currentInput + + if !@validateInput(currentInput) + item.find("[name=\"#{attribute.name}\"]").val('') + return + + item.find("[name=\"#{attribute.name}\"]").val(@buildTimestamp(currentInput)) + + # returns array with date or false if cannot get date + @currentInput: (item, attribute) -> + datetime = item.find('.js-datepicker').datepicker('getDate') + if !datetime || datetime.toString() is 'Invalid Date' + item.find("[name=\"#{attribute.name}\"]").val('') + return false + + @log 'setNewTime', datetime + + year = datetime.getFullYear() + month = datetime.getMonth() + 1 + day = datetime.getDate() + date = "#{App.Utils.formatTime(year)}-#{App.Utils.formatTime(month,2)}-#{App.Utils.formatTime(day,2)}" + [date] + + @validateInput: (currentInput) -> + currentInput[0] isnt '' + + @buildTimestamp: (currentInput) -> + throw 'Must override in a subclass' + + @dateSetter: -> + throw 'Must override in a subclass' + + @setNewTimeInitial: (item, attribute) -> + timestamp = item.find("[name=\"#{attribute.name}\"]").val() + @log 'setNewTimeInitial', timestamp + if !timestamp + @setNoTimestamp(item) + return + + timeObject = new Date( Date.parse( timestamp ) ) + + @log 'setNewTimeInitial', timestamp, timeObject + @setTimestamp(item, timeObject) + item.find('.js-datepicker').datepicker('update') + + @setNoTimestamp: (item) -> + return + + @setTimestamp: (item, timeObject) -> + item.find('.js-datepicker').datepicker(@dateSetter(), timeObject) + + @validation: (item, attribute, runtime) -> + # remove old validation + if attribute.validationContainer is 'self' + item.find('.js-datepicker').removeClass('has-error') + else + item.closest('.form-group').removeClass('has-error') + item.find('.has-error').removeClass('has-error') + item.find('.help-inline').html('') + item.closest('.form-group').find('.help-inline').html('') + + timestamp = item.find("[name=\"#{attribute.name}\"]").val() + + # check required attributes + errors = {} + if !timestamp + if !attribute.null + errors[attribute.name] = 'missing' + else + timeObject = new Date( Date.parse( timestamp ) ) + + + @log 'validation', errors + return if _.isEmpty(errors) + + # show invalid options + if attribute.validationContainer is 'self' + item.find('.js-datepicker').addClass('has-error') + else + formGroup = item.closest('.form-group') + for key, value of errors + formGroup.addClass('has-error') + + @buildCustomDates: -> + data = { + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + daysMin: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + months: ['January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December'], + monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + today: 'today', + clear: 'clear' + } + + App.i18n.translateDeep(data) diff --git a/app/assets/javascripts/app/controllers/_ui_element/date.coffee b/app/assets/javascripts/app/controllers/_ui_element/date.coffee index e70a6d834..0e48d3041 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/date.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/date.coffee @@ -1,161 +1,11 @@ # coffeelint: disable=camel_case_classes -class App.UiElement.date - @render: (attributeOrig) -> +# Provides date-only picker +class App.UiElement.date extends App.UiElement.basedate + @templateName: -> + 'date' - attribute = _.clone(attributeOrig) - attribute.nameRaw = attribute.name - attribute.name = "{date}#{attribute.name}" + @buildTimestamp: (currentInput) -> + currentInput[0] - item = $( App.view('generic/date')( - attribute: attribute - ) ) - - # set our custom template - $.fn.datepicker.defaults.template = App.view('generic/datepicker')() - - # apply date widgets - $.fn.datepicker.dates['custom'] = - days: [ - App.i18n.translateInline('Sunday'), - App.i18n.translateInline('Monday'), - App.i18n.translateInline('Tuesday'), - App.i18n.translateInline('Wednesday'), - App.i18n.translateInline('Thursday'), - App.i18n.translateInline('Friday'), - App.i18n.translateInline('Saturday'), - App.i18n.translateInline('Sunday'), - ], - daysMin: [ - App.i18n.translateInline('Sun'), - App.i18n.translateInline('Mon'), - App.i18n.translateInline('Tue'), - App.i18n.translateInline('Wed'), - App.i18n.translateInline('Thu'), - App.i18n.translateInline('Fri'), - App.i18n.translateInline('Sat'), - App.i18n.translateInline('Sun') - ], - daysShort: [ - App.i18n.translateInline('Sun'), - App.i18n.translateInline('Mon'), - App.i18n.translateInline('Tue'), - App.i18n.translateInline('Wed'), - App.i18n.translateInline('Thu'), - App.i18n.translateInline('Fri'), - App.i18n.translateInline('Sat'), - App.i18n.translateInline('Sun') - ], - months: [ - App.i18n.translateInline('January'), - App.i18n.translateInline('February'), - App.i18n.translateInline('March'), - App.i18n.translateInline('April'), - App.i18n.translateInline('May'), - App.i18n.translateInline('June'), - App.i18n.translateInline('July'), - App.i18n.translateInline('August'), - App.i18n.translateInline('September'), - App.i18n.translateInline('October'), - App.i18n.translateInline('November'), - App.i18n.translateInline('December'), - ], - monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - today: App.i18n.translateInline('today'), - clear: App.i18n.translateInline('clear') - currentDate = undefined - - item.find('.js-datepicker').datepicker( - weekStart: 1 - autoclose: true - todayBtn: 'linked' - todayHighlight: true - format: App.i18n.timeFormat().date - rtl: App.i18n.dir() is 'rtl' - container: item - language: 'custom' - ) - - # set initial date time - @setNewTimeInitial(item, attribute) - - # observer changes / update needed to forece rerender to get correct today shown - item.find('input').bind('focus', (e) -> - item.find('.js-datepicker').datepicker('rerender') - ) - item.find('input').bind('keyup blur change', (e) => - @setNewTime(item, attribute, 0) - @validation(item, attribute, true) - ) - item.bind('validate', (e) => - @validation(item, attribute) - ) - - item - - @setNewTime: (item, attribute, tolerant = false) -> - - datetime = item.find('.js-datepicker').datepicker('getDate') - if !datetime || datetime.toString() is 'Invalid Date' - App.Log.debug 'UiElement.date.setNewTime', datetime - item.find("[name=\"#{attribute.name}\"]").val('') - return - - App.Log.debug 'UiElement.date.setNewTime', datetime - year = datetime.getFullYear() - month = datetime.getMonth() + 1 - day = datetime.getDate() - date = "#{App.Utils.formatTime(year)}-#{App.Utils.formatTime(month,2)}-#{App.Utils.formatTime(day,2)}" - - if date is '' - item.find("[name=\"#{attribute.name}\"]").val('') - return - - App.Log.debug 'UiElement.date.setNewTime', date - item.find("[name=\"#{attribute.name}\"]").val(date) - - @setNewTimeInitial: (item, attribute) -> - App.Log.debug 'UiElement.date.setNewTimeInitial', timestamp - timestamp = item.find("[name=\"#{attribute.name}\"]").val() - return if !timestamp - - timeObject = new Date( Date.parse( timestamp ) ) - - hour = timeObject.getHours() - minute = timeObject.getMinutes() - - App.Log.debug 'UiElement.date.setNewTimeInitial', timestamp, timeObject - item.find('.js-datepicker').datepicker('setUTCDate', timeObject) - item.find('.js-datepicker').datepicker('update') - - @validation: (item, attribute, runtime) -> - - # remove old validation - if attribute.validationContainer is 'self' - item.find('.js-datepicker').removeClass('has-error') - else - item.closest('.form-group').removeClass('has-error') - item.find('.has-error').removeClass('has-error') - item.find('.help-inline').html('') - item.closest('.form-group').find('.help-inline').html('') - - timestamp = item.find("[name=\"#{attribute.name}\"]").val() - - # check required attributes - errors = {} - if !timestamp - if !attribute.null - errors[attribute.name] = 'missing' - else - timeObject = new Date( Date.parse( timestamp ) ) - - - App.Log.debug 'UiElement.date.validation', errors - return if _.isEmpty(errors) - - # show invalid options - if attribute.validationContainer is 'self' - item.find('.js-datepicker').addClass('has-error') - else - formGroup = item.closest('.form-group') - for key, value of errors - formGroup.addClass('has-error') + @dateSetter: -> + 'setUTCDate' diff --git a/app/assets/javascripts/app/controllers/_ui_element/datetime.coffee b/app/assets/javascripts/app/controllers/_ui_element/datetime.coffee index 6e8397572..648aa9981 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/datetime.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/datetime.coffee @@ -1,174 +1,45 @@ # coffeelint: disable=camel_case_classes -class App.UiElement.datetime - @render: (attributeOrig) -> +# Provides full date and time picker +class App.UiElement.datetime extends App.UiElement.basedate + @templateName: -> + 'datetime' - attribute = _.clone(attributeOrig) - attribute.nameRaw = attribute.name - attribute.name = "{datetime}#{attribute.name}" + @applyPickers: (item, attribute) -> + super(item, attribute) - item = $( App.view('generic/datetime')( - attribute: attribute - ) ) - - # set our custom template - $.fn.datepicker.defaults.template = App.view('generic/datepicker')() - - # apply date widgets - $.fn.datepicker.dates['custom'] = - days: [ - App.i18n.translateInline('Sunday'), - App.i18n.translateInline('Monday'), - App.i18n.translateInline('Tuesday'), - App.i18n.translateInline('Wednesday'), - App.i18n.translateInline('Thursday'), - App.i18n.translateInline('Friday'), - App.i18n.translateInline('Saturday'), - App.i18n.translateInline('Sunday'), - ], - daysMin: [ - App.i18n.translateInline('Sun'), - App.i18n.translateInline('Mon'), - App.i18n.translateInline('Tue'), - App.i18n.translateInline('Wed'), - App.i18n.translateInline('Thu'), - App.i18n.translateInline('Fri'), - App.i18n.translateInline('Sat'), - App.i18n.translateInline('Sun') - ], - daysShort: [ - App.i18n.translateInline('Sun'), - App.i18n.translateInline('Mon'), - App.i18n.translateInline('Tue'), - App.i18n.translateInline('Wed'), - App.i18n.translateInline('Thu'), - App.i18n.translateInline('Fri'), - App.i18n.translateInline('Sat'), - App.i18n.translateInline('Sun') - ], - months: [ - App.i18n.translateInline('January'), - App.i18n.translateInline('February'), - App.i18n.translateInline('March'), - App.i18n.translateInline('April'), - App.i18n.translateInline('May'), - App.i18n.translateInline('June'), - App.i18n.translateInline('July'), - App.i18n.translateInline('August'), - App.i18n.translateInline('September'), - App.i18n.translateInline('October'), - App.i18n.translateInline('November'), - App.i18n.translateInline('December'), - ], - monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - today: App.i18n.translateInline('today'), - clear: App.i18n.translateInline('clear') - currentDate = undefined - - item.find('.js-datepicker').datepicker( - weekStart: 1 - autoclose: true - todayBtn: 'linked' - todayHighlight: true - format: App.i18n.timeFormat().date - rtl: App.i18n.dir() is 'rtl' - container: item - language: 'custom' - ) - - # set initial date time - @setNewTimeInitial(item, attribute) - - # apply time widgets item.find('.js-timepicker').timepicker() - # observer changes / update needed to forece rerender to get correct today shown - item.find('input').bind('focus', (e) -> - item.find('.js-datepicker').datepicker('rerender') - ) - item.find('input').bind('keyup blur change', (e) => - @setNewTime(item, attribute, 0) - @validation(item, attribute, true) - ) - item.bind('validate', (e) => - @validation(item, attribute) - ) - item + # returns array with date and time or false if cannot get date + @currentInput: (item, attribute) -> + result = super(item, attribute) - @setNewTime: (item, attribute, tolerant = false) -> + if _.isArray(result) + result.push item.find('.js-timepicker').val() - datetime = item.find('.js-datepicker').datepicker('getDate') - if !datetime || datetime.toString() is 'Invalid Date' - App.Log.debug 'UiElement.datetime.setNewTime', datetime - item.find("[name=\"#{attribute.name}\"]").val('') - return + result - App.Log.debug 'UiElement.datetime.setNewTime', datetime - year = datetime.getFullYear() - month = datetime.getMonth() + 1 - day = datetime.getDate() - date = "#{App.Utils.formatTime(year)}-#{App.Utils.formatTime(month,2)}-#{App.Utils.formatTime(day,2)}" - time = item.find('.js-timepicker').val() + @validateInput: (currentInput) -> + currentInput[0] isnt '' || currentInput[1] isnt '' - if date is '' || time is '' - item.find("[name=\"#{attribute.name}\"]").val('') - return + @setNoTimestamp: (item) -> + item.find('.js-timepicker').val('08:00') - timestamp = "#{date}T#{time}:00.000Z" - time = new Date( Date.parse(timestamp) ) - time.setMinutes( time.getMinutes() + time.getTimezoneOffset() ) - App.Log.debug 'UiElement.datetime.setNewTime', time.toString() - timestamp = time.toISOString().replace(/\d\d\.\d\d\dZ$/, '00.000Z') - item.find("[name=\"#{attribute.name}\"]").val(timestamp) - - @setNewTimeInitial: (item, attribute) -> - App.Log.debug 'UiElement.datetime.setNewTimeInitial', timestamp - timestamp = item.find("[name=\"#{attribute.name}\"]").val() - if !timestamp - item.find('.js-timepicker').val('08:00') - return - - timeObject = new Date( Date.parse( timestamp ) ) + @setTimestamp: (item, timeObject) -> + super(item, timeObject) hour = timeObject.getHours() minute = timeObject.getMinutes() time = "#{App.Utils.formatTime(hour,2)}:#{App.Utils.formatTime(minute,2)}" - App.Log.debug 'UiElement.datetime.setNewTimeInitial', timestamp, timeObject - item.find('.js-datepicker').datepicker('setUTCDate', timeObject) item.find('.js-timepicker').val(time) - item.find('.js-datepicker').datepicker('update') - - @validation: (item, attribute, runtime) -> - - # remove old validation - if attribute.validationContainer is 'self' - item.find('.js-datepicker').removeClass('has-error') - else - item.closest('.form-group').removeClass('has-error') - item.find('.has-error').removeClass('has-error') - item.find('.help-inline').html('') - item.closest('.form-group').find('.help-inline').html('') - - timestamp = item.find("[name=\"#{attribute.name}\"]").val() - - # check required attributes - errors = {} - if !timestamp - if !attribute.null - errors[attribute.name] = 'missing' - else - timeObject = new Date( Date.parse( timestamp ) ) - - App.Log.debug 'UiElement.datetime.validation', errors - return if _.isEmpty(errors) - - # show invalid options - if attribute.validationContainer is 'self' - item.find('.js-datepicker').addClass('has-error') - else - formGroup = item.closest('.form-group') - for key, value of errors - formGroup.addClass('has-error') + @buildTimestamp: (currentInput) -> + timestamp = "#{currentInput[0]}T#{currentInput[1]}:00.000Z" + time = new Date( Date.parse(timestamp) ) + time.setMinutes( time.getMinutes() + time.getTimezoneOffset() ) + @log 'setNewTime', time.toString() + time.toISOString().replace(/\d\d\.\d\d\dZ$/, '00.000Z') + @dateSetter: -> + 'setDate' diff --git a/app/assets/javascripts/app/lib/app_post/i18n.coffee b/app/assets/javascripts/app/lib/app_post/i18n.coffee index dbfbb72c1..b8c385409 100644 --- a/app/assets/javascripts/app/lib/app_post/i18n.coffee +++ b/app/assets/javascripts/app/lib/app_post/i18n.coffee @@ -5,6 +5,11 @@ class App.i18n @init: (args) -> _instance ?= new _i18nSingleton(args) + @translateDeep: (input, args...) -> + if _instance == undefined + _instance ?= new _i18nSingleton() + _instance.translateDeep(input, args) + @translateContent: (string, args...) -> if _instance == undefined _instance ?= new _i18nSingleton() @@ -203,6 +208,19 @@ class _i18nSingleton extends Spine.Module return string if !string @translate(string, args, true) + translateDeep: (input, args) => + if _.isArray(input) + _.map input, (item) => + @translateDeep(item, args) + else if _.isObject(input) + _.reduce _.keys(input), (memo, item) => + memo[item] = @translateDeep(input[item]) + memo + , {} + else + @translateInline(input, args) + + translateContent: (string, args) => return string if !string diff --git a/public/assets/tests/ui.js b/public/assets/tests/ui.js index 93eb6d21a..614cb496a 100644 --- a/public/assets/tests/ui.js +++ b/public/assets/tests/ui.js @@ -1,3 +1,34 @@ +// date picker timezone conversion for display +test("date picker", function() { + Date.prototype.getTimezoneOffset2 = Date.prototype.getTimezoneOffset + Date.prototype.getTimezoneOffset = function() { return -360 } + + obj_date_time = { + name: 'test', + value: '2018-04-06T20:45:00.000Z' + } + + el_date_time = App.UiElement.datetime.render(obj_date_time) + + date_time_parsed = new Date(Date.parse(obj_date_time.value)) + date_time_input = el_date_time.find('.js-datepicker').datepicker('getDate') + equal(date_time_parsed.getDate(), date_time_input.getDate(), 'datetime matching day') + + obj_date = { + name: 'test', + value: '2018-06-06' + } + + el_date = App.UiElement.date.render(obj_date) + + date_parsed = new Date(Date.parse(obj_date.value)) + date_input = el_date.find('.js-datepicker').datepicker('getUTCDate') + equal(date_parsed.getDate(), date_input.getDate(), 'date matching day') + + Date.prototype.getTimezoneOffset = Date.prototype.getTimezoneOffset2 + Date.prototype.getTimezoneOffset2 = undefined +}) + // pretty date test("check pretty date", function() { var current = new Date()