Create Blazor custom date picker component with nullable and non-nullable date value

Viewed 1931

I want to create custom date picker component in Blazor. My code is like that

<div class="form-group">
<label>@Title</label>
<input type="date" class="form-control" @bind="Value" />
</div>

@code{
        [Parameter]
        public string Title { get;set; }
        private DateTime? _value;
        [Parameter]
        public DateTime? Value
        {
            get
            {
                return _value;
            }
            set
            {
                if (Value == _value)
                {
                    return;
                }
                _value = value;
                ValueChanged.InvokeAsync(value);

            }
        }

        [Parameter]
        public EventCallback<DateTime?> ValueChanged { get; set; }

}

the problem is that component only works if the value is nullable DateTime (DateTime?), and if value is DateTime it throws error

cannot convert from 'Microsoft.AspNetCore.Components.EventCallback<System.DateTime>' to 'Microsoft.AspNetCore.Components.EventCallback'

I decided to make the bind value accepts nullable because I thought it will accept both types. It works like that with int, double ...etc. So I want to make it accept DateTime and DateTime? Any ideas to make it ?

Update: I tried to make the component accepts generic type but it throws error Cannot convert T to DateTime

2 Answers

Here your component with generics:

@using System.Globalization
@using System

@typeparam TValue

<div class="form-group">
<label>@Title</label>
<input 
    type="date"
    class="form-control" 
    value="@FormatValueAsString(Value)"
    @onchange="@OnChange"
/>
</div>

@code {
    private const string DateFormat = "yyyy-MM-dd";
    CultureInfo provider = CultureInfo.InvariantCulture;
    private TValue _value;
    [Parameter] public string Title {get; set;}
    [Parameter] public TValue Value
    {
        get
        {
            return _value;
        }
        set
        {
            if (EqualityComparer<TValue>.Default.Equals(value , _value))
            {
                return;
            }
            _value = value;
            ValueChanged.InvokeAsync(value);

        }
    }

    [Parameter] public EventCallback<TValue> ValueChanged { get; set; }
    private void OnChange( ChangeEventArgs args)
    {
        try
        {
            Value = 
                (TValue)(object)
                DateTime
                .ParseExact(args.Value.ToString(),DateFormat, provider);
        }
        catch {
            Value = default(TValue); // not sure you want this
        }        
    }
    protected string FormatValueAsString(TValue? value)
    {
        switch (value)
        {
            case DateTime dateTimeValue:
                var r = BindConverter.FormatValue(dateTimeValue, DateFormat, provider);
                return r;
            default:
                return string.Empty; 
        }
    }    
}

But ... I suggest to you to inherit from InputDate.

Take a look to "Option 2 (recomended): Through inheritance from InputBase "

To check i out at https://blazorrepl.com/repl/QlYPQBFj03U9ITNe13

Much simpler way would be to inherit form Microsoft's InputDate component. I have done the same to implement Bootstrap4 validation styling in Blazor, see the following snippets:

@inherits InputDate<TValue>

@typeparam TValue

<input @attributes="AdditionalAttributes"
        type="date"
        class="@Bs4InputHelpers.FixClassNames(CssClass)"
        value="@BindConverter.FormatValue(CurrentValueAsString)"
        @oninput="EventCallback.Factory.CreateBinder<string>(
    this, value => CurrentValueAsString = value, CurrentValueAsString)" />

@code {

}

And for the Helper:

public static class Bs4InputHelpers
    {
        public static string FixClassNames(string inputClassNames)
        {
            //NOTE: Notice the space in front of the class name, this is to ensure we get
            // the suffix to our existing form-control class set from the mark up and NOT
            // half of an invalid tag.  We could use a reg-ex but that might be a bit
            // too slow for the UI rendering to stay smooth.

            // The invalid string shall always be fixed up, as we can never get it until the
            // element has checked at least once by an attempted submit.
            string result = inputClassNames.Replace(" invalid", " is-invalid");

            // The valid tag is on by default, and to keep consistency with BS4 we only want
            // it to appear either when our field is modified, or we've tried a submit
            if (inputClassNames.Contains("modified"))
            {
                result = result.Replace(" valid", " is-valid");
            }

            return result;
        }


    }
Related