This commit is contained in:
mmrbnjd
2024-05-16 23:40:32 +03:30
parent 354316abba
commit 3ca7f9deb0
25 changed files with 7727 additions and 101 deletions

View File

@@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFrameworks>net5.0</TargetFrameworks>
<Version>3.3.0</Version>
<LangVersion>latest</LangVersion>
<NoWarn>$(NoWarn);NU1701;1702;1591;NU1602;CS8609;CS8610;CS8619;CS8632</NoWarn>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<Authors>Farshad Davoudi</Authors>
<Title>Blazor.PersianDatePicker</Title>
<Company>Bit</Company>
<Description>
A free Jalali (Persian) and Gregorian (Miladi) dual datepicker library for Blazor applications
</Description>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<!-- Add README.md to nuget package -->
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryURL>https://github.com/farshaddavoudi/Blazor.PersianDatePicker</RepositoryURL>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb;.xml</AllowedOutputExtensionsInPackageBuildOutputFolder>
<EmbedAllSources>True</EmbedAllSources>
<DebugType>portable</DebugType>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
<!-- Add README.md to nuget package -->
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.0" />
<PackageReference Include="Delegate.SassBuilder" Version="1.4.0" />
<PackageReference Include="SourceLink.Embed.AllSourceFiles" Version="2.8.3" PrivateAssets="all" />
<PackageReference Include="SourceLink.Copy.PdbFiles" Version="2.8.3" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,10 @@
// ReSharper disable CheckNamespace
namespace Blazor.PersianDatePicker
{
public enum Align
{
Right,
Left
}
}

View File

@@ -0,0 +1,27 @@
// ReSharper disable once CheckNamespace
namespace Blazor.PersianDatePicker
{
public enum Calendar
{
/// <summary>
/// Both Jalali (Persian) and Miladi (Gregorian) calendars, with default set to Jalali
/// </summary>
DualModeJalaliDefault,
/// <summary>
/// Both Jalali (Persian) and Miladi (Gregorian) calendars, with default set to Miladi
/// </summary>
DualModeMiladiDefault,
/// <summary>
/// Only Jalali (Persian) calendar
/// </summary>
SingleModeJalali,
/// <summary>
/// Only Miladi (Gregorian) calendar
/// </summary>
SingleModeMiladi
}
}

View File

@@ -0,0 +1,48 @@
using System.ComponentModel.DataAnnotations;
// ReSharper disable InconsistentNaming
// ReSharper disable once CheckNamespace
namespace Blazor.PersianDatePicker
{
public enum DateFormat
{
/// <summary>
/// e.g. 1400/01/01
/// </summary>
[Display(Name = "YYYY/MM/DD")]
yyyy_slash_MM_slash_dd,
/// <summary>
/// e.g. 1400-01-01
/// </summary>
[Display(Name = "YYYY-MM-DD")]
yyyy_dash_MM_dash_dd
}
public static class DateFormatExtensions
{
public static string GetCSharpFormat(this DateFormat format)
{
if (format == DateFormat.yyyy_slash_MM_slash_dd)
return "yyyy/MM/dd";
if (format == DateFormat.yyyy_dash_MM_dash_dd)
return "yyyy-MM-dd";
return null;
}
public static string GetSeparator(this DateFormat format)
{
if (format == DateFormat.yyyy_slash_MM_slash_dd)
return "/";
if (format == DateFormat.yyyy_dash_MM_dash_dd)
return "-";
return null;
}
}
}

View File

@@ -0,0 +1,22 @@
// ReSharper disable CheckNamespace
namespace Blazor.PersianDatePicker
{
public enum DigitType
{
/// <summary>
/// For dual calendar mode, will change based on current calendar
/// </summary>
BasedOnCalendar,
/// <summary>
/// e.g. ۱۴۰۰/۰۱/۱۱
/// </summary>
Persian,
/// <summary>
/// e.g. 1400/01/11
/// </summary>
English
}
}

View File

@@ -0,0 +1,11 @@
// ReSharper disable CheckNamespace
namespace Blazor.PersianDatePicker
{
public enum IconPosition
{
BasedOnAlign,
Right,
Left
}
}

View File

@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
// ReSharper disable CheckNamespace
namespace Blazor.PersianDatePicker
{
public enum PickerTheme
{
[Display(Name = "default-theme")]
Default,
[Display(Name = "dark-theme")]
Dark,
[Display(Name = "blue-theme")]
Blue,
[Display(Name = "cheerup-theme")]
Cheerup,
[Display(Name = "redblack-theme")]
RedBlack
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
namespace Blazor.PersianDatePicker.Extensions
{
public static class EnumExtensions
{
public static string? ToEnumDisplayName(this Enum value, bool showEnumStringIfNoDisplayName = true)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
var displayName = value.GetType()
.GetMember(value.ToString())
.First()
?.GetCustomAttributes<DisplayAttribute>()
.FirstOrDefault()
?.GetName();
return displayName ?? (showEnumStringIfNoDisplayName ? value.ToString() : null);
}
}
}

View File

@@ -0,0 +1,44 @@
namespace Blazor.PersianDatePicker.Extensions
{
public static class StringExtensions
{
public static string EnglishToPersianDigits(this string str)
{
return str.Replace("0", "۰")
.Replace("1", "۱")
.Replace("2", "۲")
.Replace("3", "۳")
.Replace("4", "۴")
.Replace("5", "۵")
.Replace("6", "۶")
.Replace("7", "۷")
.Replace("8", "۸")
.Replace("9", "۹");
}
public static string PersianToEnglishDigits(this string str)
{
return str.Replace("۰", "0")
.Replace("۱", "1")
.Replace("۲", "2")
.Replace("۳", "3")
.Replace("۴", "4")
.Replace("۵", "5")
.Replace("۶", "6")
.Replace("۷", "7")
.Replace("۸", "8")
.Replace("۹", "9")
//iphone numeric
.Replace("٠", "0")
.Replace("١", "1")
.Replace("٢", "2")
.Replace("٣", "3")
.Replace("٤", "4")
.Replace("٥", "5")
.Replace("٦", "6")
.Replace("٧", "7")
.Replace("٨", "8")
.Replace("٩", "9");
}
}
}

View File

@@ -0,0 +1,402 @@
@using System.ComponentModel.DataAnnotations
@if (Visible)
{
<div class="datepicker_wrapp" style="@Style">
<input id="@Id"
name="@Name"
readonly="@ReadOnly"
disabled="@Disabled"
class="@_internalCssClass @_iconCssClass @CssClass"
placeholder="@Placeholder"
autocomplete="off"
maxlength="10"
value="@Value"
init-value="@Value"
@onchange="@Change" />
<span class="@_clearBtnCssClass" @onclick="@Clear">×</span>
</div>
}
@code
{
#nullable enable
private string? _internalCssClass;
private string? _iconCssClass;
private string? _clearBtnCssClass;
public FieldIdentifier FieldIdentifier { get; private set; }
private string? _value;
private bool _isJalaliCurrentCalendarType;
[CascadingParameter]
public EditContext? EditContext { get; set; }
[Parameter]
public virtual string? Value
{
get => _value;
set
{
if (!EqualityComparer<string>.Default.Equals(value, _value))
{
_value = value;
}
}
}
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
/// <summary>
/// Html input element id attribute (Required)
/// </summary>
[Parameter, Required]
public string? Id { get; set; }
/// <summary>
/// Html input element name attribute (Optional)
/// </summary>
[Parameter]
public string? Name { get; set; }
/// <summary>
/// Control visibility of input
/// </summary>
[Parameter]
public bool Visible { get; set; } = true;
/// <summary>
/// Disabled make input disabled. Meaning only showing value and picker popup won't open
/// </summary>
[Parameter]
public bool Disabled { get; set; }
/// <summary>
/// Picker align relative to input
/// </summary>
[Parameter]
public Align PickerAlign { get; set; } = Align.Right;
/// <summary>
/// Show calendar icon on input
/// </summary>
[Parameter]
public bool ShowCalendarIcon { get; set; } = true;
/// <summary>
/// Calendar icon position relative to input
/// </summary>
[Parameter]
public IconPosition CalendarIconPosition { get; set; } = IconPosition.BasedOnAlign;
/// <summary>
/// Can be used in changing vertical position of picker popup relative to input
/// </summary>
[Parameter]
public double PickerOffsetTopPositionInPixels { get; set; } = 2;
/// <summary>
/// Initial value for input set on today
/// </summary>
[Parameter]
public bool InitialValueSetOnToday { get; set; }
/// <summary>
/// Calendar type for date picker including Dual, Single, etc
/// </summary>
[Parameter]
public Calendar CalendarType { get; set; } = Calendar.DualModeJalaliDefault;
/// <summary>
/// Control the digit type showing in input after selecting by picker
/// </summary>
[Parameter]
public DigitType DigitType { get; set; } = DigitType.BasedOnCalendar;
/// <summary>
/// Format of date to show in input after selecting by picker
/// </summary>
[Parameter]
public DateFormat DateFormat { get; set; } = DateFormat.yyyy_slash_MM_slash_dd;
/// <summary>
/// Prevent user select date before today
/// </summary>
[Parameter]
public bool MinDateSetOnToday { get; set; } = true;
[Parameter]
public string? Placeholder { get; set; }
/// <summary>
/// Set datepicker readonly
/// </summary>
[Parameter]
public bool ReadOnly { get; set; } = true;
/// <summary>
/// CSS class for input element
/// </summary>
[Parameter]
public string? CssClass { get; set; }
/// <summary>
/// Inline styles for input element
/// </summary>
[Parameter]
public string? Style { get; set; }
/// <summary>
/// Choose a theme for changing look and feel of picker
/// </summary>
[Parameter]
public PickerTheme Theme { get; set; } = PickerTheme.Default;
[Parameter]
public Expression<Func<string>>? ValueExpression { get; set; }
[Parameter]
public EventCallback<string> OnChange { get; set; }
[Parameter]
public EventCallback OnClear { get; set; }
[Inject] private IJSRuntime? JsRuntime { get; set; }
protected override async Task OnInitializedAsync()
{
if (string.IsNullOrEmpty(Id))
Id = "id" + new Random().Next(1000, 9999);
if (CalendarType == Calendar.DualModeJalaliDefault || CalendarType == Calendar.SingleModeJalali)
_isJalaliCurrentCalendarType = true;
else
_isJalaliCurrentCalendarType = false;
if (!ShowCalendarIcon)
{
_iconCssClass = "";
_clearBtnCssClass = "clear-btn right";
}
else
{
if (CalendarIconPosition == IconPosition.Left)
{
_iconCssClass = "persian-datepicker-left-icon";
_clearBtnCssClass = "clear-btn right";
}
else if (CalendarIconPosition == IconPosition.Right)
{
_iconCssClass = "persian-datepicker-right-icon";
_clearBtnCssClass = "clear-btn left";
}
else
{
if (PickerAlign == Align.Right)
{
_iconCssClass = "persian-datepicker-left-icon";
_clearBtnCssClass = "clear-btn right";
}
else
{
_iconCssClass = "persian-datepicker-right-icon";
_clearBtnCssClass = "clear-btn left";
}
}
}
if (InitialValueSetOnToday)
{
var dt = DateTime.Now;
_value = _isJalaliCurrentCalendarType
? GetPersianDateFromGregorianDate(dt)
: dt.ToString(DateFormat.GetCSharpFormat());
ModifyValueDigit();
await OnValueChanged();
}
_internalCssClass = Disabled ? "datepicker-disabled" : "blazor-datepicker";
await base.OnInitializedAsync();
}
public override Task SetParametersAsync(ParameterView parameters)
{
var result = base.SetParametersAsync(parameters);
if (EditContext != null && ValueExpression != null && FieldIdentifier.Model != EditContext.Model)
{
FieldIdentifier = FieldIdentifier.Create(ValueExpression);
EditContext.OnValidationStateChanged += ValidationStateChanged;
}
return result;
}
private void ValidationStateChanged(object? sender, ValidationStateChangedEventArgs e)
{
StateHasChanged();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var calendarType = CalendarType == Calendar.SingleModeJalali || CalendarType == Calendar.DualModeJalaliDefault
? "persian"
: "gregorian";
bool enableSwitchCalendar = CalendarType == Calendar.DualModeJalaliDefault || CalendarType == Calendar.DualModeMiladiDefault;
bool? hasExplicitDigitType = DigitType != DigitType.BasedOnCalendar;
string digitType = "float";
if (DigitType == DigitType.Persian)
digitType = "fa";
else if (DigitType == DigitType.English)
digitType = "en";
var minDate = MinDateSetOnToday ? "today" : null;
await JsRuntime!.InvokeVoidAsync("blazorReady",
DotNetObjectReference.Create(this),
Id!,
calendarType,
enableSwitchCalendar,
hasExplicitDigitType,
digitType,
InitialValueSetOnToday,
DateFormat.ToEnumDisplayName()!,
minDate ?? string.Empty,
PickerAlign.ToString().ToLower(),
PickerOffsetTopPositionInPixels,
Theme.ToEnumDisplayName()!);
await base.OnAfterRenderAsync(true);
}
await base.OnAfterRenderAsync(false);
}
protected async Task Change(ChangeEventArgs args)
{
Value = $"{args.Value}";
await ValueChanged.InvokeAsync(Value);
EditContext?.NotifyFieldChanged(FieldIdentifier);
await OnChange.InvokeAsync(Value);
}
[JSInvokable]
public async Task SetDate(long? timestamp, string elementId, bool switchingCalendar = false)
{
if (elementId != Id)
return;
if (timestamp != null)
{
var dt = new DateTime(1970, 1, 1, 0, 0, 0, 0)
.AddSeconds(Math.Round((long)timestamp / 1000d)).ToLocalTime();
_value = _isJalaliCurrentCalendarType
? GetPersianDateFromGregorianDate(dt)
: dt.ToString(DateFormat.GetCSharpFormat());
}
if (switchingCalendar)
{
_isJalaliCurrentCalendarType = !_isJalaliCurrentCalendarType;
if (_isJalaliCurrentCalendarType)
{
// Change from en to fa: 2021/04/03 => 1400/05/02
var dt = Convert.ToDateTime(_value);
_value = GetPersianDateFromGregorianDate(dt);
}
else
{
// Change from fa to en: 1400/05/05 => 2021/01/05
// Convert to Miladi
if (_value != null)
{
DateTime dt = DateTime.Parse(_value, new CultureInfo("fa-IR"));
// Get Date
_value = dt.ToString(DateFormat.GetCSharpFormat());
}
}
}
ModifyValueDigit();
await OnValueChanged();
}
[JSInvokable]
public async Task SetToday(string elementId)
{
var dt = DateTime.Today;
_value = _isJalaliCurrentCalendarType
? GetPersianDateFromGregorianDate(dt)
: dt.ToString(DateFormat.GetCSharpFormat());
ModifyValueDigit();
await OnValueChanged();
}
private void ModifyValueDigit()
{
if (DigitType == DigitType.Persian)
_value = _value?.EnglishToPersianDigits();
else if (DigitType == DigitType.English)
_value = _value?.PersianToEnglishDigits();
else if (DigitType == DigitType.BasedOnCalendar)
_value = _isJalaliCurrentCalendarType ? _value?.EnglishToPersianDigits() : _value?.PersianToEnglishDigits();
}
private async Task OnValueChanged()
{
await ValueChanged.InvokeAsync(_value);
EditContext?.NotifyFieldChanged(FieldIdentifier);
await OnChange.InvokeAsync(Value);
}
private string GetPersianDateFromGregorianDate(DateTime dt)
{
PersianCalendar pc = new PersianCalendar();
return $"{pc.GetYear(dt):D2}{DateFormat.GetSeparator()}{pc.GetMonth(dt):D2}{DateFormat.GetSeparator()}{pc.GetDayOfMonth(dt):D2}";
}
private async Task Clear()
{
await ValueChanged.InvokeAsync(string.Empty);
EditContext?.NotifyFieldChanged(FieldIdentifier);
await OnClear.InvokeAsync();
}
}

View File

@@ -0,0 +1,7 @@
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Components.Forms
@using System.Linq.Expressions
@using System.Globalization
@using Blazor.PersianDatePicker.Extensions

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff