1

I have a custom validation class to validate a date. It works server side but not client side. I can't trigger the jquery method to do the check. I think I might have something wired up incorrectly with the adaptor or validator. P.S. I can get non custom client side validation to work (ex. [required]), just not my custom validation

View Model

public class HomePageViewModel
{
    [Required]
    public string SearchQuery { get; set; }

    [DisplayName("Date")]
    [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
    [ClassDate(ErrorMessage = "Class date must be today or later.")]
    public DateTime ClassDate { get; set; }
}

Validation class.

public class ClassDateAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        if (value == null) // ok, just use todays date day
        {
            return true;
        }

        DateTime toValidate = (DateTime) value;

        if (toValidate.Day < DateTime.Now.Day) // if they are looking for classes in the past
        {
            return false;
        }
        return true;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
        ControllerContext context)
    {
        var classDateRule = new ModelClientValidationRule();
        classDateRule.ErrorMessage = "test error message";
        classDateRule.ValidationType = "classdate"; 

        yield return classDateRule;
    }
}

Javascript code.

$(function () {
$(".datefield").datepicker();
var now = new Date();

var day = ("0" + now.getDate()).slice(-2);
var month = ("0" + (now.getMonth() + 1)).slice(-2);

var today = (month) + "/" + (day) + "/" + now.getFullYear();

$('.datefield').val(today);


$.validator.unobtrusive.adapters.addSingleVal("classdate");

$.validator.addMethod("classdate", function(value) {
    if (value) {
        //do something to test date here
        //if date is today or later
        return true;
    }
    return false;
});
});

This is the element from my view.

 @Html.TextBoxFor(model => model.ClassDate, new { @class = "datefield form-control", @id = "ClassDate", @type = "date", name = "ClassDate" })
 @Html.ValidationMessageFor(model => model.ClassDate, "", new { @class = "text-danger" })
2
  • Since your using a datapicker (assume this is jquery ui) then why not just set the minDate: 0,? (and why do you have @id = "ClassDate", @type = "date", name = "ClassDate" - all which are are bit pointless since thats what the helper renders anyway) Commented Feb 24, 2015 at 0:31
  • I guess I'll set the minDate to today with the current time. I don't want anyone to select a date/time in the past. But for the sake of just getting this hooked up, can you see any issues? I know I'll need custom client side validation in other areas of my project so knowing whats going on here will help out. Commented Feb 24, 2015 at 0:34

2 Answers 2

2

The main issue why you are not getting client side validation is that you use $.validator.unobtrusive.adapters.addSingleVal("classdate");. addSingleVal() is for validating against another property in your model. It needs to be $.validator.unobtrusive.adapters.addBool("classdate");

Since you are using the jQueryUI datepicker, you could prevent selecting a date less than today's date using

$(.datefield).datepicker({
  minDate: 0
});

A lot of you code here makes no sense (see side notes below) and should be something like

Property definition

[DisplayName("Date")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
[NotLessThanToday]
public DateTime ClassDate { get; set; }

Validation attribute

public class NotLessThanTodayAttribute : ValidationAttribute, IClientValidatable
{
  private const string _DefaultErrorMessage = "The property {0} cannot be less than todays date";

  public NotLessThanTodayAttribute() : base(_DefaultErrorMessage)
  {
  }

  public override string FormatErrorMessage(string name)
  {
    return string.Format(ErrorMessageString, name);
  }

  protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  {
    if (value == null)
    {
      return ValidationResult.Success; // ??
    }
    DateTime date = DateTime.Today;
    if (DateTime.TryParse((string)value, out date))
    {
      if (date < DateTime.Today)
      {
        return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
      }
    }
    else
    {
      // not a valid date
    }
    return base.IsValid(value, validationContext);
  }

  public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
  {
    var clientValidationRule = new ModelClientValidationRule()
    {
      ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
      ValidationType = "notlessthantoday"
    };
    return new[] { clientValidationRule };
  }
}

Script

$.validator.addMethod("notlessthantoday", function (value, element, params) {
  if (!this.optional(element)) {
    var date = new Date(value);
    var today = new Date();
    return date > today;
  }
  return true; // ??
});

$.validator.unobtrusive.adapters.addBool("notlessthantoday");

Side notes

  1. ApplyFormatInEditMode = true is used in conjunction with [DataType(DataType.Date)] and @Html.EditorFor() to render the browsers HTML5 datepicker. It's not necessary here, and if you did use it, the format string needs to be "{0:yyyy-MM-dd}" (ISO format)
  2. Your current validation attribute is checking only if the dates day is less that todays day. if (toValidate.Day < DateTime.Now.Day) meaning 1/1/2016 is valid (1 is less than 24).
  3. The script var now = new Date(); ..... $('.datefield').val(today); makes no sense and overrides the value in the model (a user enters a date, you post and return the view and your code resets the date back to today's value!). If you want to display todays date, then set the value in the model before you pass it to the view (model.ClassDate = Datetime.Today;)
  4. You set (or try to) the following attributes in the helper: @id="ClassDate", @type="date", name="ClassDate". The html helper sets the id attribute to id="ClassDate" anyway. The type="date" is not required (that's for rending the browsers HTML5 datepicker) and setting the name attribute as your doing does nothing (fortunately it does not work because it you tried to give it a name other that "ClassDate", binding would fail)
Sign up to request clarification or add additional context in comments.

3 Comments

I believe your code is correct. I copy and pasted directly into my project. But when I submit my form and set a breakpoint inside the javascript it never gets called and the breakpoint never gets hit. The IsValid method inside NotLessThanTodayAttribute gets called instead as if client side validation is disabled. I think all my libraries are installed correctly and being called in the correct order and I have the properties in web.config set to true for client side validation. But I could be wrong!
First check that the html generated includes data-val-notlessthantoday="The field ClassDate cannot be less than todays date". Also make sure also that you have not wrapped the scripts in $(document).ready
ah! I put them into $(document).ready. This has caused me some confusion. What's allowed and what isn't allowed inside this event...
-1

document.ready...

1) Executes after the whole web page is loaded.

2) Generally, is used to initialize events on dom elements (like .click, .change, .select). This makes sense, since you can't usually attach events to dom elements that aren't loaded yet.

3) Generally, functions do not go into document.ready. This makes sense, since functions generally get called by events.

Of course, there are exceptions to everything, but these make pretty good rules of thumb.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.