Fixes #14722 - DateTime picker calendar does not get updated when custom date format (#14767)

* Seperate properties for input ng-model and datepicker.

Since flatpickr can't handle custom formatting.

* Set picker format to default.
This commit is contained in:
Johan Möller
2023-09-12 18:22:29 +02:00
committed by GitHub
parent 7f318e8993
commit 0bbda02931
2 changed files with 255 additions and 225 deletions

View File

@@ -1,209 +1,211 @@
function dateTimePickerController($scope, angularHelper, dateHelper, validationMessageService) {
let flatPickr = null;
let flatPickr = null;
function onInit() {
function onInit() {
$scope.hasDatetimePickerValue = $scope.model.value ? true : false;
$scope.model.datetimePickerValue = null;
$scope.serverTime = null;
$scope.serverTimeNeedsOffsetting = false;
$scope.hasDatetimePickerValue = $scope.model.value ? true : false;
$scope.model.datetimePickerValue = null;
$scope.serverTime = null;
$scope.serverTimeNeedsOffsetting = false;
// setup the default config
var config = {
pickTime: true,
useSeconds: true,
format: "YYYY-MM-DD HH:mm:ss",
icons: {
time: "icon-time",
date: "icon-calendar",
up: "icon-chevron-up",
down: "icon-chevron-down"
}
};
// map the user config
$scope.model.config = Utilities.extend(config, $scope.model.config);;
// ensure the format doesn't get overwritten with an empty string
if ($scope.model.config.format === "" || $scope.model.config.format === undefined || $scope.model.config.format === null) {
$scope.model.config.format = $scope.model.config.pickTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD";
}
// check whether a server time offset is needed
if (Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
// Will return something like 120
var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
// Will return something like -120
var localOffset = new Date().getTimezoneOffset();
// If these aren't equal then offsetting is needed
// note the minus in front of serverOffset needed
// because C# and javascript return the inverse offset
$scope.serverTimeNeedsOffsetting = (-serverOffset !== localOffset);
}
const dateFormat = $scope.model.config.pickTime ? "Y-m-d H:i:S" : "Y-m-d";
// date picker config
$scope.datePickerConfig = {
enableTime: $scope.model.config.pickTime,
dateFormat: dateFormat,
time_24hr: true,
clickOpens: !$scope.readonly
};
// Don't show calendar if date format has been set to only time
const timeFormat = $scope.model.config.format.toLowerCase();
const timeFormatPattern = /^h{1,2}:m{1,2}(:s{1,2})?\s?a?$/gmi;
if (timeFormat.match(timeFormatPattern)) {
$scope.datePickerConfig.enableTime = true;
$scope.datePickerConfig.noCalendar = true;
}
setDatePickerVal();
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
$scope.mandatoryMessage = value;
});
}
$scope.clearDate = function() {
$scope.hasDatetimePickerValue = false;
if($scope.model) {
$scope.model.datetimePickerValue = null;
$scope.model.value = null;
}
if($scope.datePickerForm && $scope.datePickerForm.datepicker) {
$scope.datePickerForm.datepicker.$setValidity("pickerError", true);
}
}
$scope.datePickerSetup = function(instance) {
flatPickr = instance;
// setup the default config
var config = {
pickTime: true,
useSeconds: true,
format: "YYYY-MM-DD HH:mm:ss",
icons: {
time: "icon-time",
date: "icon-calendar",
up: "icon-chevron-up",
down: "icon-chevron-down"
}
};
$scope.datePickerChange = function(date) {
const momentDate = moment(date);
// map the user config
$scope.model.config = Utilities.extend(config, $scope.model.config);;
// ensure the format doesn't get overwritten with an empty string
if ($scope.model.config.format === "" || $scope.model.config.format === undefined || $scope.model.config.format === null) {
$scope.model.config.format = $scope.model.config.pickTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD";
}
// check whether a server time offset is needed
if (Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
// Will return something like 120
var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
// Will return something like -120
var localOffset = new Date().getTimezoneOffset();
// If these aren't equal then offsetting is needed
// note the minus in front of serverOffset needed
// because C# and javascript return the inverse offset
$scope.serverTimeNeedsOffsetting = (-serverOffset !== localOffset);
}
const dateFormat = $scope.model.config.pickTime ? "Y-m-d H:i:S" : "Y-m-d";
// date picker config
$scope.datePickerConfig = {
enableTime: $scope.model.config.pickTime,
dateFormat: dateFormat,
time_24hr: true,
clickOpens: !$scope.readonly
};
// Don't show calendar if date format has been set to only time
const timeFormat = $scope.model.config.format.toLowerCase();
const timeFormatPattern = /^h{1,2}:m{1,2}(:s{1,2})?\s?a?$/gmi;
if (timeFormat.match(timeFormatPattern)) {
$scope.datePickerConfig.enableTime = true;
$scope.datePickerConfig.noCalendar = true;
}
setDatePickerVal();
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
$scope.mandatoryMessage = value;
});
}
$scope.clearDate = function () {
$scope.hasDatetimePickerValue = false;
if ($scope.model) {
$scope.model.datetimePickerValue = null;
$scope.model.value = null;
}
if ($scope.datePickerForm && $scope.datePickerForm.datepicker) {
$scope.datePickerForm.datepicker.$setValidity("pickerError", true);
}
}
$scope.datePickerSetup = function (instance) {
flatPickr = instance;
};
$scope.datePickerChange = function (date) {
const momentDate = moment(date);
setDate(momentDate);
setDatePickerVal();
};
$scope.inputChanged = function () {
if ($scope.model.datetimePickerValue === "" && $scope.hasDatetimePickerValue) {
// $scope.hasDatetimePickerValue indicates that we had a value before the input was changed,
// but now the input is empty.
$scope.clearDate();
} else if ($scope.model.datetimePickerValue) {
var momentDate = moment($scope.model.datetimePickerInputValue, $scope.model.config.format, true);
if (!momentDate || !momentDate.isValid()) {
momentDate = moment(new Date($scope.model.datetimePickerInputValue));
}
if (momentDate && momentDate.isValid()) {
setDate(momentDate);
setDatePickerVal();
};
$scope.inputChanged = function () {
if ($scope.model.datetimePickerValue === "" && $scope.hasDatetimePickerValue) {
// $scope.hasDatetimePickerValue indicates that we had a value before the input was changed,
// but now the input is empty.
$scope.clearDate();
} else if ($scope.model.datetimePickerValue) {
var momentDate = moment($scope.model.datetimePickerValue, $scope.model.config.format, true);
if (!momentDate || !momentDate.isValid()) {
momentDate = moment(new Date($scope.model.datetimePickerValue));
}
if (momentDate && momentDate.isValid()) {
setDate(momentDate);
}
setDatePickerVal();
flatPickr.setDate($scope.model.datetimePickerValue, false);
}
}
setDatePickerVal();
flatPickr.setDate($scope.model.datetimePickerValue, false);
}
//here we declare a special method which will be called whenever the value has changed from the server
//this is instead of doing a watch on the model.value = faster
$scope.model.onValueChanged = function (newVal, oldVal) {
if (newVal != oldVal) {
//check for c# System.DateTime.MinValue being passed as the clear indicator
var minDate = moment('0001-01-01');
var newDate = moment(newVal);
}
if (newDate.isAfter(minDate)) {
setDate(newDate);
} else {
$scope.clearDate();
}
}
};
//here we declare a special method which will be called whenever the value has changed from the server
//this is instead of doing a watch on the model.value = faster
$scope.model.onValueChanged = function (newVal, oldVal) {
if (newVal != oldVal) {
//check for c# System.DateTime.MinValue being passed as the clear indicator
var minDate = moment('0001-01-01');
var newDate = moment(newVal);
function setDate(momentDate) {
angularHelper.safeApply($scope, function() {
// when a date is changed, update the model
if (momentDate && momentDate.isValid()) {
$scope.datePickerForm.datepicker.$setValidity("pickerError", true);
$scope.hasDatetimePickerValue = true;
$scope.model.datetimePickerValue = momentDate.format($scope.model.config.format);
}
else {
$scope.hasDatetimePickerValue = false;
$scope.model.datetimePickerValue = null;
}
updateModelValue(momentDate);
});
if (newDate.isAfter(minDate)) {
setDate(newDate);
} else {
$scope.clearDate();
}
}
};
function updateModelValue(momentDate) {
var curMoment = moment($scope.model.value);
if ($scope.hasDatetimePickerValue) {
if ($scope.model.config.pickTime) {
//check if we are supposed to offset the time
if ($scope.model.value && Object.toBoolean($scope.model.config.offsetTime) && Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
$scope.model.value = dateHelper.convertToServerStringTime(momentDate, Umbraco.Sys.ServerVariables.application.serverTimeOffset);
$scope.serverTime = dateHelper.convertToServerStringTime(momentDate, Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z");
}
else {
$scope.model.value = momentDate.format("YYYY-MM-DD HH:mm:ss");
}
function setDate(momentDate) {
angularHelper.safeApply($scope, function () {
// when a date is changed, update the model
if (momentDate && momentDate.isValid()) {
$scope.datePickerForm.datepicker.$setValidity("pickerError", true);
$scope.hasDatetimePickerValue = true;
$scope.model.datetimePickerValue = momentDate.format($scope.model.config.format);
}
else {
$scope.hasDatetimePickerValue = false;
$scope.model.datetimePickerValue = null;
}
updateModelValue(momentDate);
});
}
function updateModelValue(momentDate) {
var curMoment = moment($scope.model.value);
if ($scope.hasDatetimePickerValue) {
if ($scope.model.config.pickTime) {
//check if we are supposed to offset the time
if ($scope.model.value && Object.toBoolean($scope.model.config.offsetTime) && Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
$scope.model.value = dateHelper.convertToServerStringTime(momentDate, Umbraco.Sys.ServerVariables.application.serverTimeOffset);
$scope.serverTime = dateHelper.convertToServerStringTime(momentDate, Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z");
}
else {
$scope.model.value = momentDate.format("YYYY-MM-DD");
$scope.model.value = momentDate.format("YYYY-MM-DD HH:mm:ss");
}
}
else {
$scope.model.value = null;
}
if (!curMoment.isSame(momentDate)) {
setDirty();
$scope.model.value = momentDate.format("YYYY-MM-DD");
}
}
function setDirty() {
if ($scope.datePickerForm) {
$scope.datePickerForm.datepicker.$setDirty();
}
else {
$scope.model.value = null;
}
/** Sets the value of the date picker control adn associated viewModel objects based on the model value */
function setDatePickerVal() {
if ($scope.model.value) {
var dateVal;
//check if we are supposed to offset the time
if ($scope.model.value && Object.toBoolean($scope.model.config.offsetTime) && $scope.serverTimeNeedsOffsetting) {
//get the local time offset from the server
dateVal = dateHelper.convertToLocalMomentTime($scope.model.value, Umbraco.Sys.ServerVariables.application.serverTimeOffset);
$scope.serverTime = dateHelper.convertToServerStringTime(dateVal, Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z");
}
else {
//create a normal moment , no offset required
dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment();
}
$scope.model.datetimePickerValue = dateVal.format($scope.model.config.format);
}
else {
$scope.clearDate();
}
if (!curMoment.isSame(momentDate)) {
setDirty();
}
}
$scope.$watch("model.value", function(newVal, oldVal) {
if (newVal !== oldVal) {
$scope.hasDatetimePickerValue = newVal ? true : false;
setDatePickerVal();
}
});
function setDirty() {
if ($scope.datePickerForm) {
$scope.datePickerForm.datepicker.$setDirty();
}
}
/** Sets the value of the date picker control adn associated viewModel objects based on the model value */
function setDatePickerVal() {
if ($scope.model.value) {
var dateVal;
//check if we are supposed to offset the time
if ($scope.model.value && Object.toBoolean($scope.model.config.offsetTime) && $scope.serverTimeNeedsOffsetting) {
//get the local time offset from the server
dateVal = dateHelper.convertToLocalMomentTime($scope.model.value, Umbraco.Sys.ServerVariables.application.serverTimeOffset);
$scope.serverTime = dateHelper.convertToServerStringTime(dateVal, Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z");
}
else {
//create a normal moment , no offset required
dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment();
}
$scope.model.datetimePickerValue = dateVal.format("YYYY-MM-DD HH:mm:ss");
$scope.model.datetimePickerInputValue = dateVal.format($scope.model.config.format);
}
else {
$scope.clearDate();
}
}
$scope.$watch("model.value", function (newVal, oldVal) {
if (newVal !== oldVal) {
$scope.hasDatetimePickerValue = newVal ? true : false;
setDatePickerVal();
}
});
onInit();
onInit();
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.DatepickerController", dateTimePickerController);

View File

@@ -1,48 +1,76 @@
<div class="umb-property-editor umb-datepicker" ng-controller="Umbraco.PropertyEditors.DatepickerController">
<ng-form name="datePickerForm">
<div id="datepicker{{model.alias}}">
<umb-date-time-picker
ng-model="model.datetimePickerValue"
options="datePickerConfig"
on-setup="datePickerSetup(fpItem)"
on-change="datePickerChange(dateStr)">
<div class="input-append">
<input type="text"
name="datepicker"
id="{{model.alias}}"
ng-model="model.datetimePickerValue"
ng-blur="inputChanged()"
ng-required="model.validation.mandatory"
val-server="value"
class="datepickerinput"
ng-readonly="readonly" />
<button ng-if="!readonly" type="button" class="btn-clear" ng-click="clearDate()" ng-show="hasDatetimePickerValue === true || datePickerForm.datepicker.$error.pickerError === true">
<umb-icon icon="icon-delete"></umb-icon>
<span class="sr-only"><localize key="content_removeDate">Clear date</localize></span>
</button>
<span ng-if="!readonly" class="add-on">
<umb-icon icon="icon-{{ datePickerConfig.noCalendar ? 'time' : 'calendar' }}"></umb-icon>
</span>
</div>
</umb-date-time-picker>
<div
class="umb-property-editor umb-datepicker"
ng-controller="Umbraco.PropertyEditors.DatepickerController"
>
<ng-form name="datePickerForm">
<div id="datepicker{{model.alias}}">
<umb-date-time-picker
ng-model="model.datetimePickerValue"
options="datePickerConfig"
on-setup="datePickerSetup(fpItem)"
on-change="datePickerChange(dateStr)"
>
<div class="input-append">
<input
type="text"
name="datepicker"
id="{{model.alias}}"
ng-model="model.datetimePickerInputValue"
ng-blur="inputChanged()"
ng-required="model.validation.mandatory"
val-server="value"
class="datepickerinput"
ng-readonly="readonly"
/>
<button
ng-if="!readonly"
type="button"
class="btn-clear"
ng-click="clearDate()"
ng-show="hasDatetimePickerValue === true || datePickerForm.datepicker.$error.pickerError === true"
>
<umb-icon icon="icon-delete"></umb-icon>
<span class="sr-only">
<localize key="content_removeDate">Clear date</localize>
</span>
</button>
<span ng-if="!readonly" class="add-on">
<umb-icon
icon="icon-{{ datePickerConfig.noCalendar ? 'time' : 'calendar' }}"
></umb-icon>
</span>
</div>
</umb-date-time-picker>
</div>
<div ng-messages="datePickerForm.datepicker.$error" show-validation-on-submit>
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
<p class="help-inline" ng-message="valServer">{{datePickerForm.datepicker.errorMsg}}</p>
<p class="help-inline" ng-message="pickerError"><localize key="validation_invalidDate">Invalid date</localize></p>
</div>
<div
ng-messages="datePickerForm.datepicker.$error"
show-validation-on-submit
>
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
<p class="help-inline" ng-message="valServer">
{{datePickerForm.datepicker.errorMsg}}
</p>
<p class="help-inline" ng-message="pickerError">
<localize key="validation_invalidDate">Invalid date</localize>
</p>
</div>
<p ng-if="model.config.offsetTime === '1' && serverTimeNeedsOffsetting && model.value" class="muted">
<small><localize key="content_scheduledPublishServerTime">This translates to the following time on the server:</localize> {{serverTime}}</small><br />
<small><localize key="content_scheduledPublishDocumentation">What does this mean?</localize></small>
</p>
</ng-form>
<p
ng-if="model.config.offsetTime === '1' && serverTimeNeedsOffsetting && model.value"
class="muted"
>
<small>
<localize key="content_scheduledPublishServerTime"
>This translates to the following time on the server:
</localize>
{{serverTime}} </small
><br />
<small>
<localize key="content_scheduledPublishDocumentation"
>What does this mean?</localize
>
</small>
</p>
</ng-form>
</div>