Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DateOnly support for calendars #6178

Open
1 of 2 tasks
sjd2021 opened this issue Jan 18, 2023 · 12 comments · May be fixed by #9016
Open
1 of 2 tasks

DateOnly support for calendars #6178

sjd2021 opened this issue Jan 18, 2023 · 12 comments · May be fixed by #9016
Labels
enhancement New feature or request

Comments

@sjd2021
Copy link

sjd2021 commented Jan 18, 2023

Feature request type

Enhance component

Component name

Date Picker

Is your feature request related to a problem?

We love Mud Blazor, but many of us .NET 6/7 users like to use DateOnly for our models that are binded to our form fields. The date pickers do not currently work with DateOnly even when we've specified that we are not interested in time. As a result, we have to write things in automapper which map to our database models. However, doing so seems to make the relationship too difficult for AutoMapper/EF to translate any queries that were written with Linq + ProjectTo. It's causing us to not be able to use the right tools for the right job and accurately reflect our db data types in our domain models.

Describe the solution you'd like

We'd like to be able to use DateOnly for calendars that simply need a date such as a birthday or closing date.

Have you seen this feature anywhere else?

Only in custom implementations

Describe alternatives you've considered

Automapper from a temporary model used only to support mudblazor - the issue with this is that we use ProjectTo to convert queries from our models to our domain db entities, and the mapping becomes too complex to be translated by EF.

Pull Request

  • I would like to do a Pull Request

Code of Conduct

  • I agree to follow this project's Code of Conduct
@sjd2021 sjd2021 added enhancement New feature or request triage labels Jan 18, 2023
@Luk164
Copy link

Luk164 commented Jan 27, 2023

For anyone who needs to get DateOnly working I made a small adapter to use in my project and thought I'd share:

<MudDatePicker Label="@Label" Class="d-inline-flex" Editable @bind-Date="_date" For="@(() => _date)" />

@*HACK: This component works as an adapter between MudDatePicker which only accepts DateTime? and any DateOnly object.
    Remove once issue https://github.com/MudBlazor/MudBlazor/issues/6178 is fixed*@
    
@code {
    [Parameter]
    [EditorRequired]
    public DateOnly Date { get; set; }
    
    [Parameter]
    public EventCallback<DateOnly> DateChanged { get; set; }

    [Parameter]
    [EditorRequired]
    public string? Label { get; set; }
    
    private DateTime? _date
    {
        get => Date.ToDateTime(TimeOnly.MinValue);
        set
        {
            if (value is not null)
            {
                Date = DateOnly.FromDateTime((DateTime)value);
                DateChanged.InvokeAsync(Date);
            }
        }
    }
}

@archigo
Copy link

archigo commented Dec 6, 2023

@Luk164
Have you found a way to support the validation parameter "For"?
I have done the same as you to support DateOnly., but converting this expression and still getting the correct parameter path does not seem possible.

@Luk164
Copy link

Luk164 commented Dec 6, 2023

@Luk164
Have you found a way to support the validation parameter "For"?
I have done the same as you to support DateOnly., but converting this expression and still getting the correct parameter path does not seem possible.

The adapter fully wraps the DateTime picker, so if you want to validate the easiest way would be to do so in the adaptor itself

@archigo
Copy link

archigo commented Dec 6, 2023

the For parameter is used to determine where to show a validation message, so it is not a question of just validating.
Mudblazor uses it this way:

MudFormComponent.cs

            var expression = (MemberExpression)For.Body;
            var propertyInfo = expression.Expression?.Type.GetProperty(expression.Member.Name);
            _validationAttrsFor = propertyInfo?.GetCustomAttributes(typeof(ValidationAttribute), true).Cast<ValidationAttribute>();

            _fieldIdentifier = FieldIdentifier.Create(For);
            _currentFor = For;

@Luk164
Copy link

Luk164 commented Dec 6, 2023

@archigo Sorry it has been almost a year since I made that, I will have to look deeper once I have some more time

@archigo
Copy link

archigo commented Dec 6, 2023

No problem, i was just curios, since i did not find a way to solve it myself.

Instead our DateOnlyPicker will have to use the Validation parameter, instead of using the model validation that we otherwise use.

@Luk164
Copy link

Luk164 commented Dec 6, 2023

No problem, i was just curios, since i did not find a way to solve it myself.

Instead our DateOnlyPicker will have to use the Validation parameter, instead of using the model validation that we otherwise use.

I do believe you can just invoke model validation manually using ValidationContext

https://stackoverflow.com/questions/17138749/how-to-manually-validate-a-model-with-attributes

@archigo
Copy link

archigo commented Dec 7, 2023

I think we are talking past each other. The issue is not invoking validation, but telling the MudComponent that a validation error belongs to it.
That is what the "For" parameter does.

@ar-schraml
Copy link

@archigo
I managed to work around having to use the For parameter with reflection. (Which isn't that nice, I know, but it works.)

So for an explanation:
The only thing For does, that's important for our use case (showing the appropriate validation message for the input), seems to be this line from the code you already posted above:

MudFormComponent.cs

            ...
            _fieldIdentifier = FieldIdentifier.Create(For);
            ...

So instead of setting the For parameter, which we can't do as it expects a DateTime? instead of a DateOnly?, we can set the _fieldIdentifier directly using reflection like this:

var fieldIdentifierField = typeof(MudFormComponent<DateTime?, string>).GetField("_fieldIdentifier", BindingFlags.Instance | BindingFlags.NonPublic);
if (fieldIdentifierField != null)
{
    fieldIdentifierField.SetValue(datePicker, editContext.Field("NameOfYourDateOnlyField"));
}

(datePicker is just a ref to the MudDatePicker. So this has to be done in OnAfterRender() for the ref to be set.)

Hope this helps!

@archigo
Copy link

archigo commented Apr 11, 2024

@ar-schraml
Thanks a lot!
Your solution worked great.
I only had one issue, which is when there are multiple levels to the form

Model<FormLevel1>:

class FormLevel1 {
    public DateOnly DateWorkByString;
    public FormLevel2 SubModel;
}

class FormLevel2 {
    public DateOnly DateDoesNitWorkByString;
}

I could not get the the field identifier to work by setting it based on a string. I think this is because the object reference of the field identifier is wrong.

I solved this by doing the same thing the MudBlazor does, which also has the advantage of mainting the for parameter in the same way that we are used to from normal MudBlazor components:

[CascadingParameter]
public EditContext? EditContext { get; set; }

[Parameter]
public Expression<Func<DateOnly?>>? For { get; set; }

private MudDatePicker? _mudDatePicker;

protected override void OnAfterRender(bool firstRender)
{
    if (firstRender && For != null)
    {
        if (EditContext == null)
            throw new Exception("Using 'For' without an 'EditContext' is not supported. Are you missing an 'EditForm'?");

        // get the private fieldidentifier by reflection.
        var fieldIdentifierField = typeof(MudFormComponent<DateTime?, string>).GetField("_fieldIdentifier", BindingFlags.Instance | BindingFlags.NonPublic);
        if (fieldIdentifierField != null)
        {
            // set the field identifier with our DateOnly expression, avoiding the type issue between DateOnly vs DateTime
            fieldIdentifierField.SetValue(_mudDatePicker, FieldIdentifier.Create(For));
        }
    }
}

@FelixCCWork
Copy link

FelixCCWork commented Apr 15, 2024

Thanks to your help I finally could implement my DateOnlyPicker.

Just for convenience because we all like copy and pasta here the full component.

@using System.Linq.Expressions
@using System.Reflection

<MudDatePicker @ref="datePickerRef" Label="@Label" Class="d-inline-flex" ReadOnly="@ReadOnly" HelperText="@HelperText" @bind-Date="dateBindTarget" />

@*HACK: This component works as an adapter between MudDatePicker which only accepts DateTime? and any DateOnly? object.
Remove once issue https://github.com/MudBlazor/MudBlazor/issues/6178 is fixed*@

@code
{
	[CascadingParameter]
	public EditContext? EditContext { get; set; }

	[Parameter, EditorRequired]
	public DateOnly? Date { get; set; }

	[Parameter]
	public EventCallback<DateOnly?> DateChanged { get; set; }

	[Parameter, EditorRequired]
	public string? Label { get; set; }

	[Parameter]
	public Expression<Func<DateOnly?>>? For { get; set; }

	[Parameter]
	public bool ReadOnly { get; set; }

	[Parameter]
	public string? HelperText { get; set; }

	private MudDatePicker? datePickerRef;

	private DateTime? dateBindTarget
	{
		get => Date?.ToDateTime(TimeOnly.MinValue);
		set
		{
			if (value is not null)
			{
				Date = DateOnly.FromDateTime((DateTime)value);
				DateChanged.InvokeAsync(Date);
			}
		}
	}

	protected override void OnAfterRender(bool firstRender)
	{
		if (!firstRender || For is null)
			return;

		if (EditContext is null)
			throw new Exception("Using 'For' without an 'EditContext' is not supported. Are you missing an 'EditForm'?");

		// Get the private field _fieldidentifier by reflection.
		FieldInfo? fieldIdentifierField = typeof(MudFormComponent<DateTime?, string>).GetField("_fieldIdentifier", BindingFlags.Instance | BindingFlags.NonPublic);

		if (fieldIdentifierField is not null)
		{
			// Set the field identifier with our DateOnly? expression, avoiding the type issue between DateOnly vs DateTime
			fieldIdentifierField.SetValue(datePickerRef, FieldIdentifier.Create(For));
		}
	}
}

codemonkey85 added a commit to codemonkey85/PKMDS-Blazor that referenced this issue Apr 18, 2024
@henon henon removed the triage label May 7, 2024
@crosscuttech
Copy link

crosscuttech commented May 19, 2024

So how does the For work when the parent is a MudForm? To be more specific, I'm getting the following error when I've got the DateOnlyPicker inside a MudForm:

System.AggregateException: One or more errors occurred. (Using 'For' without an 'EditContext' is not supported. Are you missing an 'EditForm'?)
       ---> System.Exception: Using 'For' without an 'EditContext' is not supported. Are you missing an 'EditForm'?

@ArieGato ArieGato linked a pull request May 19, 2024 that will close this issue
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants