0

Is it possible to compare dates with Knockout observables and jQuery Validation? I have a StartDate and an EndDate, both observables. I want to validate that the EndDate is always greater than the StartDate.

I've tried the addMethod feature but I can't seem to pass in self.StartDate. It doesn't seem that the addMethod accepts KO observables, so I have to pass in the string ID, "#StartDate".

$.validator.addMethod("dateGreater", function(value, element, params){
       var endDate = new Date(value); // this returns incorrect date.
       var startDate = new Date($(params).val()); 

       return this.optional(element) || startDate < endDate;
   }, "The End Date must be greater thatn the Start Date.");

The problem with this code is that when the user selects a date with the DatePicker, the EndDate returns the previous date, not the currently selected date, and since the StartDate is not an observable being validated, if the user changes it, the validation doesn't fire.

I guess my question really is, can this be done with jQuery Validation, or should I be using Knockout-validation?

I created a simple jsfiddle that shows what I'm trying to accomplish. http://jsfiddle.net/kahanu/htqfa1qw/6/

King Wilder
  • 629
  • 1
  • 7
  • 19
  • I use jquery-validation without knockout-validation so I'm not sure you need to change your approach rightaway - should be able to figure it out! Couple of thoughts: if the user can edit the start date, can you bind it to an observable to help the validation process? And what datetime widget do you use - sometimes you need to poke these widgets with a change event, to tell KO to update the observable. – sifriday Jul 27 '15 at 18:06
  • The StartDate and EndDate's are both observables already, sorry, I'll edit my post, I should have included that. I'm using the bootstrap-datepicker.js widget. But this addMethod doesn't accept an observable as the params, I've tried that. That's why I needed to resort to the element ID approach, which doesn't work. – King Wilder Jul 27 '15 at 18:23
  • so broadly speaking I found it works well to keep jquery-validation outside of KO. In this case I'd compare the inputs with fairly normal jquery-validation methods. You don't lose any magic here, because jquery-validation takes care of its own change event tracking. Then there's also usually a fix needed to push a datetime widget into your viewmodels. I'm just doing a fiddle to show this approach. Is it this version of bootstrap-datetime? http://eonasdan.github.io/bootstrap-datetimepicker/ – sifriday Jul 27 '15 at 19:18
  • Ah actually your fiddle below is nice, I have it working in that... will post the answer. – sifriday Jul 27 '15 at 19:27

1 Answers1

2

This answer follows the approach of keeping jquery-validation outside of KO. I think this is OK, because you don't lose any magic; jquery-validation has its own framework for tracking changes and it keeps KO and jquery-validation separate, which is simple.

Here is your fiddle, with this approach applied:

http://jsfiddle.net/htqfa1qw/7/

The fixes were:

  1. jquery-validation requires names in the input elements

  2. These names then correspond with the keys in the rules object.

  3. Param passing is weird and the docs don't explain it well!

So your rules object now looks like this:

    EndDate: {
        required: true,
        dateGreater: {
            param: "StartDate"
        }
    }

which is saying we want the <input name="EndDate"> to be greater than the <input name="StartDate">

We then modify the rule so that it gets the value directly from the DOM without needing to go via KO:

var start = new Date($("[name="+param+"]").val());

If you did want to do it via KO by passing in the observable as you describe, then you could do this:

http://jsfiddle.net/htqfa1qw/9/

Instantiate your data model before applying it:

var dm = new DataModel();
ko.applyBindings(dm);

Then alter the validation rule to lookup the method by the param, and call it:

$.validator.addMethod("dateGreater", function (endDate, element, param) {
    var end= new Date(endDate);
    var start = new Date(dm[param].call());
    return this.optional(element) || start < end;
}, "Start Date must be less than End Date.");

(With ref to the comments) Once it is working with the bootstrap-datetime widget, you need to be careful about how the widget changes the input value, and how it's method of changing the value ineracts with both the jquery-validation framework and also the KO framework, both of which rely on change events.

In your fiddle the problem seems to be that jquery-validation gets triggered with the old date value before the widget successfully loads the new value into the input. Hence you see validation happening on the old value... The quick fix seems to be to call jquery-validation's valid() method, to trigger a manual validation, in the event handler you already have:

    $el.datepicker(options).on("changeDate", function (ev) {
        var observable = valueAccessor();
        observable(ev.date);
        $(this).datepicker('hide');

        // Poke jquery validation to validate this new value.
        $("#date-form").valid();
    });

Demo here:

http://jsfiddle.net/htqfa1qw/12/

In this fiddle there's some console.logs and you can track the sequence:

  1. validation is called with the old value (the "Start date..." log statement)
  2. your event handler kicks in with the new value (the "changeDate" log statement)
  3. We manually call valid() and validation is called with the new value (the 2nd "Start date..." log statement)
sifriday
  • 4,342
  • 1
  • 13
  • 24
  • Fantastic! Sorry it took me a while to respond. Putting out fires. I like both methods. I really like the dm[param]call(), and I wasn't quite sure I had the param passing correct. I see I didn't. You saved me. – King Wilder Jul 27 '15 at 22:40
  • One more thing, is the "required" attribute on the HTML input element needed if it's used in the jQuery Validation rule? – King Wilder Jul 27 '15 at 22:41
  • OK, while this works in jsfiddle, it doesn't work with the Bootstrap DatePicker. I updated the fiddle to use the date picker and it's not working. Again I'm sure I'm unaware of a configuration issue. Any suggestions? http://jsfiddle.net/kahanu/htqfa1qw/11/. Notice how the console.log shows an incorrect date for the end date when you change the date with the date picker. – King Wilder Jul 28 '15 at 01:35
  • yep, you can remove the required attribute. There are multiple ways to apply it, but you do only need one - here's a good low-down: http://stackoverflow.com/questions/14375707/should-i-implement-my-validation-using-the-built-in-class-or-the-rule/14380401#14380401 – sifriday Jul 28 '15 at 08:47
  • Glad everything went well for you. So the problem with the widget is probably that the widget interfers with KO's data-bind ... often when a widget sets a value the change event gets squashed and so the view-model doesn't get updated. You often have to resort to some manual code to push the value back into KO, or explore the world of custom bindings. I use the bootstrap datepicker with jquery-validation with KO and have some of this manual code already written so I'll have a look at your fiddle and see if I can get it working... – sifriday Jul 28 '15 at 08:50
  • You've done a custom binding already - looking nice! I got it working with a tiny tweak, which I'll add to the answer now. – sifriday Jul 28 '15 at 09:01