Browse Source

Enhance date picker functionality to support date range selection and timezone normalization

pull/22236/head
maliming 2 years ago
parent
commit
aa6e83076c
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 111
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs
  2. 24
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs
  3. 34
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs
  4. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js
  5. 27
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js
  6. 46
      npm/packs/core/src/abp.js

111
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs

@ -16,63 +16,28 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
using Volo.Abp.Json;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker;
public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelperService<TTagHelper>
where TTagHelper : AbpDatePickerBaseTagHelper<TTagHelper>
{
protected readonly Dictionary<Type, Func<object, string>> SupportedInputTypes = new()
{
{
typeof(string), o =>
{
if(o is string s && DateTime.TryParse(s, out var dt))
{
return dt.ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTime), o =>
{
if(o is DateTime dt && dt != default)
{
return dt.ToString("O");
}
return string.Empty;
}
},
{typeof(DateTime?), o => ((DateTime?) o)?.ToString("O")!},
{
typeof(DateTimeOffset), o =>
{
if(o is DateTimeOffset dto && dto != default)
{
return dto.ToString("O");
}
return string.Empty;
}
},
{typeof(DateTimeOffset?), o => ((DateTimeOffset?) o)?.ToString("O")!}
};
protected readonly Dictionary<Type, Func<object, string>> SupportedInputTypes;
protected readonly IJsonSerializer JsonSerializer;
protected readonly IHtmlGenerator Generator;
protected readonly HtmlEncoder Encoder;
protected readonly IServiceProvider ServiceProvider;
protected readonly IAbpTagHelperLocalizer TagHelperLocalizer;
protected readonly IClock Clock;
protected virtual string TagName { get; set; } = "abp-date-picker";
protected IStringLocalizer<AbpUiResource> L { get; }
protected abstract TagHelperOutput TagHelperOutput { get; set; }
protected AbpDatePickerBaseTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator,
HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer<AbpUiResource> l,
IAbpTagHelperLocalizer tagHelperLocalizer)
IAbpTagHelperLocalizer tagHelperLocalizer, IClock clock)
{
JsonSerializer = jsonSerializer;
Generator = generator;
@ -80,6 +45,65 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
ServiceProvider = serviceProvider;
L = l;
TagHelperLocalizer = tagHelperLocalizer;
Clock = clock;
SupportedInputTypes = new Dictionary<Type, Func<object, string>>
{
{
typeof(string), x =>
{
if(x is string s && DateTime.TryParse(s, out var dt))
{
return Clock.Convert(dt).ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTime), x =>
{
if(x is DateTime dt && dt != default)
{
return Clock.Convert(dt).ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTime?), x =>
{
if(x is DateTime dt && dt != default)
{
return Clock.Convert(dt).ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTimeOffset), x =>
{
if(x is DateTimeOffset dto && dto != default)
{
return Clock.Convert(dto).ToString("O");
}
return string.Empty;
}
},
{
typeof(DateTimeOffset?), x =>
{
if(x is DateTimeOffset dto && dto != default)
{
return Clock.Convert(dto).ToString("O");
}
return string.Empty;
}
}
};
}
protected virtual T? GetAttribute<T>() where T : Attribute
@ -233,6 +257,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
}
protected abstract int GetOrder();
protected abstract void AddBaseTagAttributes(TagHelperAttributeList attributes);
protected virtual string GetExtraInputHtml(TagHelperContext context, TagHelperOutput output)
@ -375,7 +400,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
{
attrList.Add("data-visible-date-format", options.VisibleDateFormat);
}
if(!options.InputDateFormat.IsNullOrEmpty())
{
attrList.Add("data-input-date-format", options.InputDateFormat);
@ -754,7 +779,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
{
return Task.FromResult(string.Empty);
}
return GetValidationAsHtmlByInputAsync(context, output, @for);
}
@ -766,7 +791,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
new ValidationMessageTagHelper(Generator) { For = @for, ViewContext = TagHelper.ViewContext };
var attributeList = new TagHelperAttributeList { { "class", "text-danger" } };
if(!output.Attributes.TryGetAttribute("name", out var nameAttribute) || nameAttribute == null || nameAttribute.Value == null)
{
if (nameAttribute != null)
@ -776,7 +801,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
nameAttribute = new TagHelperAttribute("name", "date_" + Guid.NewGuid().ToString("N"));
output.Attributes.Add(nameAttribute);
}
attributeList.Add("data-valmsg-for", nameAttribute.Value);
return await validationMessageTagHelper.RenderAsync(attributeList, context, Encoder, "span",

24
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs

@ -8,14 +8,22 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
using Volo.Abp.Json;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker;
public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService<AbpDatePickerTagHelper>
{
public AbpDatePickerTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer<AbpUiResource> l, IAbpTagHelperLocalizer tagHelperLocalizer) : base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer)
public AbpDatePickerTagHelperService(
IJsonSerializer jsonSerializer,
IHtmlGenerator generator,
HtmlEncoder encoder,
IServiceProvider serviceProvider,
IStringLocalizer<AbpUiResource> l,
IAbpTagHelperLocalizer tagHelperLocalizer,
IClock clock)
: base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer, clock)
{
}
protected override TagHelperOutput TagHelperOutput { get; set; } = default!;
@ -42,10 +50,18 @@ public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService<A
{
InputTypeName = "hidden",
ViewContext = TagHelper.ViewContext,
For = TagHelper.AspFor,
For = TagHelper.AspFor
};
var attributes = new TagHelperAttributeList { { "data-date", "true" }, { "type", "hidden" } };
if (Clock.SupportsMultipleTimezone && TagHelper.AspFor.Model is DateTime dateTime)
{
DateTagHelper.Format = "{0:O}";
DateTagHelper.Value = Clock.Convert(dateTime).ToString("O");
attributes.Add("value", DateTagHelper.Value);
}
DateTagHelperOutput = await DateTagHelper.ProcessAndGetOutputAsync(attributes, context, "input");
}
@ -80,4 +96,4 @@ public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService<A
{
return DateTagHelperOutput?.Render(Encoder)!;
}
}
}

34
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs

@ -9,16 +9,20 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
using Volo.Abp.Json;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker;
public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperService<AbpDateRangePickerTagHelper>
{
public AbpDateRangePickerTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator,
HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer<AbpUiResource> l,
IAbpTagHelperLocalizer tagHelperLocalizer) :
base(jsonSerializer, generator, encoder, serviceProvider, l,
tagHelperLocalizer)
public AbpDateRangePickerTagHelperService(
IJsonSerializer jsonSerializer, IHtmlGenerator generator,
HtmlEncoder encoder,
IServiceProvider serviceProvider,
IStringLocalizer<AbpUiResource> l,
IAbpTagHelperLocalizer tagHelperLocalizer,
IClock clock) :
base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer, clock)
{
}
@ -42,6 +46,13 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
InputTypeName = "hidden"
};
if (Clock.SupportsMultipleTimezone && TagHelper.AspForStart.Model is DateTime dateTime)
{
StartDateTagHelper.Format = "{0:O}";
StartDateTagHelper.Value = Clock.Convert(dateTime).ToString("O");
startDateAttributes.Add("value", StartDateTagHelper.Value);
}
StartDateTagHelperOutput = await StartDateTagHelper.ProcessAndGetOutputAsync(startDateAttributes, context, "input");
}
@ -55,6 +66,13 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
InputTypeName = "hidden"
};
if (Clock.SupportsMultipleTimezone && TagHelper.AspForEnd.Model is DateTime dateTime)
{
EndDateTagHelper.Format = "{0:O}";
EndDateTagHelper.Value = Clock.Convert(dateTime).ToString("O");
endDateAttributes.Add("value", EndDateTagHelper.Value);
}
EndDateTagHelperOutput = await EndDateTagHelper.ProcessAndGetOutputAsync(endDateAttributes, context, "input");
}
@ -78,7 +96,7 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
protected override int GetOrder()
{
return TagHelper.Order;
return TagHelper.AspForStart?.Metadata.Order ?? 0;
}
protected override void AddBaseTagAttributes(TagHelperAttributeList attributes)
@ -92,7 +110,7 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
attributes.Add("data-start-date", convert);
}
}
if (TagHelper.AspForEnd?.Model != null &&
SupportedInputTypes.TryGetValue(TagHelper.AspForEnd.Metadata.ModelType, out var convertFuncEnd))
{
@ -113,4 +131,4 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ
{
return TagHelper.AspForStart ?? TagHelper.AspForEnd;
}
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js

@ -110,7 +110,7 @@ $.validator.defaults.ignore = ''; //TODO: Would be better if we can apply only f
_onOpenCallbacks.triggerAll(_publicApi);
if ($firstVisibleInput.data("datepicker")) {
if ($firstVisibleInput.data("datepicker") || $firstVisibleInput.data("daterangepicker")) {
return; //don't pop-up date pickers...
}

27
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js

@ -754,16 +754,14 @@
});
$.fn.handleDatepicker = function (datepickerSelector) {
var $this = $(this);
var $this = $(this);
var datepickers = $this.find(datepickerSelector);
$this.find('input[class="hidden-datepicker"]').remove();
$this.find('input[class~="hidden-datepicker"]').remove();
datepickers.each(function () {
var $this = $(this);
if($this.val() === ''){
var $this = $(this);
if ($this.val() === '') {
return;
}
var name = $this.attr('name') || $this.data('name');
$this.data('name', name).removeAttr('name');
var datepicker = $this.data('daterangepicker');
@ -772,13 +770,16 @@
var startDateInput = $('<input>').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker');
$this.after(startDateInput);
} else {
var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate());
var startDateInput = $('<input>').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker');
$this.after(startDateInput);
var endDate = abp.clock.normalizeToString(datepicker.endDate.toDate());
var endDateInput = $('<input>').attr('type', 'hidden').attr('name', name).val(endDate).addClass('hidden-datepicker');
$this.after(endDateInput);
if ($this.data('start-date')) {
var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate());
var startDateInput = $('<input>').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker');
$this.after(startDateInput);
}
if ($this.data('end-date')) {
var endDate = abp.clock.normalizeToString(datepicker.endDate.toDate());
var endDateInput = $('<input>').attr('type', 'hidden').attr('name', name).val(endDate).addClass('hidden-datepicker');
$this.after(endDateInput);
}
}
});

46
npm/packs/core/src/abp.js

@ -81,7 +81,7 @@ var abp = abp || {};
if (resource) {
return resource;
}
var legacySource = abp.localization.values[resourceName];
if (legacySource) {
return {
@ -89,11 +89,11 @@ var abp = abp || {};
baseResources: []
};
}
abp.log.warn('Could not find localization source: ' + resourceName);
abp.log.warn('Could not find localization source: ' + resourceName);
return null;
};
abp.localization.internal.localize = function (key, sourceName) {
var resource = abp.localization.internal.getResource(sourceName);
if (!resource){
@ -104,7 +104,7 @@ var abp = abp || {};
}
var value = resource.texts[key];
if (value === undefined) {
if (value === undefined) {
for (var i = 0; i < resource.baseResources.length; i++){
var basedArguments = Array.prototype.slice.call(arguments, 0);
basedArguments[1] = resource.baseResources[i];
@ -114,7 +114,7 @@ var abp = abp || {};
return result;
}
}
return {
value: key,
found: false
@ -135,7 +135,7 @@ var abp = abp || {};
if (sourceName === '_') { //A convention to suppress the localization
return key;
}
if (sourceName) {
return abp.localization.internal.localize.apply(this, arguments).value;
}
@ -767,28 +767,28 @@ var abp = abp || {};
return date;
}
if (abp.clock.supportsMultipleTimezone()) {
return dateObj.toISOString();
}
function padZero(num) {
return num < 10 ? '0' + num : num;
}
function padMilliseconds(num) {
if (num < 10) return '00' + num;
if (num < 100) return '0' + num;
return num;
var addZulu = false;
if (abp.clock.supportsMultipleTimezone()) {
var timeZone = abp.clock.timeZone();
var now = new Date();
var formattedDate = now.toLocaleString('en-US', { timeZone: timeZone, timeZoneName: 'short' });
var match = formattedDate.match(/GMT([+-]\d+)/);
var targetOffsetHours = match ? parseInt(match[1], 10) : 0;
var dateObj = new Date(dateObj.getTime() - (targetOffsetHours * 60 * 60 * 1000));
addZulu = true;
}
// yyyy-MM-ddTHH:mm:ss.SSS
// yyyy-MM-DDTHH:mm:ss
return dateObj.getFullYear() + '-' +
padZero(dateObj.getMonth() + 1) + '-' +
padZero(dateObj.getDate()) + 'T' +
padZero(dateObj.getHours()) + ':' +
padZero(dateObj.getMinutes()) + ':' +
padZero(dateObj.getSeconds()) + '.' +
padMilliseconds(dateObj.getMilliseconds());
padZero(dateObj.getMonth() + 1) + '-' +
padZero(dateObj.getDate()) + 'T' +
padZero(dateObj.getHours()) + ':' +
padZero(dateObj.getMinutes()) + ':' +
padZero(dateObj.getSeconds()) + (addZulu ? 'Z' : '');
};
// Default options for toLocaleString

Loading…
Cancel
Save