0

I have 3 input fields: year, month and day. I want to validate that the entered date is valid. For example, if someone enters "2016-02-31", an error should be returned.

My problem is that i can't use 1 input field for a DateTime, because the end-user needs to enter a lot of dates, and with 3 inputs, he enters them a lot faster, because he can navigate to the next input field using the TAB key on his keyboard faster.

Or if someone has a better solution, i would be happy to hear it.

EDIT: I need client and server side validation.

Jo Smo
  • 6,923
  • 9
  • 47
  • 67
  • Are you wanting client side validation as well as server side validation? –  Jun 05 '16 at 02:29
  • Yes i need both. Will add to my question. Thanks. – Jo Smo Jun 05 '16 at 02:32
  • You could create a `ValidationAttribute` that implements `IClientValidatable` and include scripts to add the rules to the `$.validator` although it may be simpler to just write you own script (that creates a javascript date from the values) and display a message, and in the server, construct a `DateTime` from the 3 values and test if its valid). I'm not sure why you think that having 3 controls and pressing the tab key is easier than one control and pressing the `-` key to create a valid date –  Jun 05 '16 at 02:46
  • @StephenMuecke i said the same thing to the end-user, but he said that he's much faster this way. That the minus key is harder to press when fast-typing. I tried to convince him, trust me. :D – Jo Smo Jun 05 '16 at 02:55
  • @StephenMuecke i will take a look at the things you mentioned, thank you. – Jo Smo Jun 05 '16 at 02:56
  • The best way to handle this depends on a few other factors. Do you have a view model with properties `int Year`,`int Month` and `int Day` as well as another property for `DateTime Date`. And how are you generating the controls in the view (using a `for` loop for a collection of that model?) –  Jun 05 '16 at 02:58

2 Answers2

1

You will first need to start with a view model to represent your dates

public class DateVM
{
    public DateVM() { } // default constructor required by the DefaultModelBinder
    public DateVM(DateTime date) // useful if your editing existing dates
    {
        Date date.Date;
        Day = date.Day;
        Month = date.Month;
        Year = date.Year;
    }
    public int Day { get; set; }
    public int Month { get; set; }
    public int Year { get; set; }
    [Required(ErrorMessage = "The values for the date are not valid")]
    public DateTime Date { get; set; }
}

and then the main view model would contain a public List<DateVM> Dates {get; set; } property.

While it would be possible to create a custom ValidationAttribute that implements IClientValidatable, (refer The Complete Guide to Validation-in-ASP.NET-MVC-3 Part-2 for a good guide to implementing one) it presents a few design problems such as which property do you apply the attribute to?, what happens when one of the other properties is not edited? etc.

Instead, I recommend you just include your own scripts to handle the .change() events of each control and validate the date (and display an error message if appropriate), and also handle the forms .submit() event to cancel the post if invalid.

Your part of the view for editing the dates would need to be

for(int i = 0; i < Model.Dates.Count; i++)
{
    <div class="date-container">
        @Html.LabelFor(m => m.Dates[i].Year)
        @Html.TextBoxFor(m => m.Dates[i].Year, new { @class = "year" })
        @Html.ValidationMessageFor(m => m.Dates[i].Year)
        @Html.LabelFor(m => m.Dates[i].Month)
        @Html.TextBoxFor(m => m.Dates[i].Month, new { @class = "month" })
        @Html.ValidationMessageFor(m => m.Dates[i].Month)
        @Html.LabelFor(m => m.Dates[i].Day)
        @Html.TextBoxFor(m => m.Dates[i].Day, new { @class = "day" })
        @Html.ValidationMessageFor(m => m.Dates[i].Day)
        @Html.HiddenFor(m => m.Dates[i].Date, new { @class = "date" })
        @Html.ValidationMessageFor(m => m.Dates[i].Date, null, new { @class = "validation" })
    </div>
}

and then include the following scripts

$('.day, .month, .year').change(function () {
    var container = $(this).closest('.date-container');
    var day = Number(container.find('.day').val());
    var month = Number(container.find('.month').val());
    var year = Number(container.find('.year').val());
    if (isNaN(day) || isNaN(month) || isNaN(year) || day === 0 || month === 0 || year === 0) {
        container.find('.date').val('');
        return;
    }
    var date = new Date(year, month - 1, day);
    var isValid = date.getDate() === day && date.getMonth() === month - 1 && date.getFullYear() === year;
    if (isValid) {
        container.find('.date').val(year + '-' + month + '-' + day);
        container.find('.validation').text('').addClass('field-validation-valid').removeClass('field-validation-error');
    } else {
        container.find('.date').val('');
        container.find('.validation').text('The values for the date are not valid').addClass('field-validation-error').removeClass('field-validation-valid');
    }
});

$('form').submit(function () {
    $.each($('.date'), function () {
        if (!$(this).val()) {
            return false;
        }
    });
})
  • But what about the server side validation? – Jo Smo Jun 07 '16 at 21:00
  • It gives you server side validation on everything because your also posting back a DateTime (the hidden input), and if it cannot be parsed to a `DateTime`, a validation error will be shown –  Jun 07 '16 at 22:09
  • One thing you could do (in the rare case a user might have disabled javascript in their browser) is if the value of `Date` is `null`, then generate a new `DateTime` - `model.Date = new DateTime(model,Year, model.Month, model.Day);` - and if its valid, then remove the `ModelState` error –  Jun 07 '16 at 22:17
  • Thank you. I will try it. :) – Jo Smo Jun 07 '16 at 22:17
0

As far I know You must create your own custom validator.

Look google for more information about ValidationAttribute, IClientValidatable and - in your case - also IValidatableObject.

Few examples:


Look also this question


Other way - you can consider using date-picker control instant of three fields. Date-picker gives you easy way to date validation plus few interesting features.

Try this one: eternicode/bootstrap-datepicker (also available via bower).

Community
  • 1
  • 1
Lukasz Mk
  • 7,000
  • 2
  • 27
  • 41