Wednesday, July 25, 2012

Patching jQuery Validation for the iOS Date Picker

If you’re trying to use native datepickers with <input type="date"/> in your app and the jQuery Validate plugin for validation, here's something you probably need to know.

I discovered, when testing my app on an iPhone, that the jQuery Validate plugin wasn’t working on my date inputs. It would always mark them invalid. Huh.

I dug into it and found that this is how it determines if a date is valid:

// http://docs.jquery.com/Plugins/Validation/Methods/date
date: function(value, element) {
    return this.optional(element) || !/Invalid|NaN/.test(new Date(value));
}

That is, it just passes the string to be tested to the Javascript “Date()” constructor and checks to see if something back comes back.

OK…what’s going on then? I logged the value of the input and confirmed that it’s in the sensible ISO format I thought it’d be in:

2012-07-17 

After an embarassingly large amount of hunting in the wrong places, I eventually uncovered that validation code above and simple ran it:

2012-07-18 fiddle.jshell.net:23
Tue Jul 17 2012 20:00:00 GMT-0400 (Eastern Daylight Time) 

OK, so that makes sense in Chrome—the validation works in Chrome. So I ran that in iOS and…it failed! Here’s my commit message after I figured this out:

// patch the validate "date" method to accomodate iOS-style ISO dates
// because some browsers (including Chrome 19+ and iOS) support HTML5 date
// inputs, but some of those same browsers' Date() implementation
// doesn't parse them... WUT?! I'm looking at you iOS

This is madness. iOS 5’s Date() can’t parse what has got to be the easiest to parse date string ever.

Enough grumbling…what do we do? My first solution involved using the other date parse rule in the Validate plugin:

// http://docs.jquery.com/Plugins/Validation/Methods/dateISO
dateISO: function(value, element) {
    return this.optional(element) || /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value);
},

That worked, since it just matches a simple yyyy-MM-dd pattern. I (of course) do server side validation, too, so I’m not worried about a user entering a syntactically correct, but practically incorrect date like 2012-02-30.

Rather than change all my inputs to use this alternative rule, or mess with my validation routine (I’m using the unobtrusive flavor)—I’d really like to just leave all that be, I decided to patch the first validator like so:

if ($.validator) {
    var originalDateValidator1 = $.validator.methods.date;
    var originalDateValidator2 = $.validator.methods.dateISO;

    $.validator.methods.date = function (value, element) {
        var isValidDate =
            originalDateValidator1.apply(this, arguments) ||
            originalDateValidator2.apply(this, arguments);

        return isValidDate;
    };
}

Quite simply, this just runs both of the date checkers and returns true if either of them pass.

In addition to not having to actually change any of my HTML, this code sits outside the library itself (it’s in my global.js file) so I can still update my plugins or load them from CDNs without fear. Oh, and if a user enters a date in the usual MM/dd/yyyy format in a browser that doesn’t support the native datepicker, the validation will still pass.

Hopefully Apple fixes this issue…

6 comments:

Kikoanis said...

Thank you, you saved my day

Kikoanis said...
This comment has been removed by the author.
Anonymous said...

Hey buddy,

you definitly made my day!!!!

Thanks a lot!

Cheers
John

Mike Causer said...

Brilliant, thanks

Unknown said...

I've been having issue where the validation plugin won't validate a date input type that is set to required, on iPad only. Even after the date is selected from the native iOS datepicker, the plugin still marks the input with an error and says it is required.

Brian Watts said...

This saved my bacon. (Along with a tip from stackoverflow to disable my jquery UI timepicker on iOS by using a line of modernizr code)

Thanks, Man!