5

Here's the situation: I have a great big text input box for a URL here: https://asafaweb.com

This doesn't need to adhere to the strict definition of a URL though; I allow addresses without a scheme then default to HTTP for usability purposes. For example, "stackoverflow.com" would be considered a valid URL. But there are also URLs which I don't want to allow for various reasons (i.e. they've been blacklisted or they're internal IP address ranges).

I want to set the type of the input to "url" rather than the default "text" so users on mobile devices get a keyboard designed for the URL context (i.e. iOS gives you a ".com" button), The problem is that as soon as I do this, the default unobtrusive jQuery validation bundled with ASP.NET MVC expects a URL with a scheme thus breaking my schemeless URL support.

This is an MVC4 site and I have an HTML helper like so:

@Html.TextBoxFor(m => m.ScanUrl, new { type = "url" })

The ScanUrl attribute then has a custom ValidationAttribute which does all the bespoke checks to make sure that URL can be scanned.

How can I keep the existing validation pattern without jQuery validation interjecting and wanting to make sure a URL is, well, a strict URL?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Troy Hunt
  • 20,345
  • 13
  • 96
  • 151
  • How about adding a data attrib like data-validationtype="schemeless" and modding the validate url routine to check for it and if present calling a method to do a schemeless url regex check? – Luke Baughan Sep 01 '12 at 15:20
  • Further to the above you could override the validation method for urll check www.bennadel.com/blog/1624-Ask-Ben-Overriding-Core-jQuery-Methods.htm in a separate include then you dont even need to mod the core code – Luke Baughan Sep 01 '12 at 15:56
  • Troy, are you sure it's the MVC validation that's failing and not the built in browser validation on the actual input control? Where do you get the actual popup telling you that you can enter the URL you've typed in, and does it look different in different browsers? – shawty Sep 02 '12 at 22:34
  • Following up on that last comment, you can put a 'novalidate' attribute on the form holding the html input tag (Assuming it is the browser validation and not JQ) , there's also a few more ideas here : http://stackoverflow.com/questions/3090369/disable-validation-of-html5-form-elements – shawty Sep 02 '12 at 22:47
  • Definitely jQuery validation mate, it's using the unobtrusive validation that MVC3/4 implements. – Troy Hunt Sep 03 '12 at 22:02

3 Answers3

8

If you don't need the strict URL validation anywhere in this site, modifying how jQuery Validation validates URLs might be the easiest way to handle that. You'll find that on line 1034 of jquery.validate.js if you're using the version that ships with MVC 4.

You could tweak the regex right there in the plugin script, or you could patch the url validation method after jQuery Validation is loaded:

<script src="/Scripts/jquery.validate.js"></script>
<script>
  // To no-op url validation completely.
  jQuery.validator.methods.url = function(value, element) {
    return this.optional(element) || true;
  };

  // Or, continue validating everything else as previously, 
  //  but not require the protocol (double check my change to the regex...).
  jQuery.validator.methods.url = function(value, element) {
    return this.optional(element) || /^(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
  };    
</script>

The main advantage to patching it afterward being that you aren't tied to your tweaked version of the script and could update jQuery Validation itself later without losing your customized url validator. That's probably obvious to you, but may be helpful to others finding this answer later.

iurisilvio
  • 4,868
  • 1
  • 30
  • 36
Dave Ward
  • 59,815
  • 13
  • 117
  • 134
0

You could use javascript to automatically prepend the schema to the URL in the input if it is not there on focus out / change.

Matt Tew
  • 1,581
  • 1
  • 9
  • 15
  • Not really desirable as I don't want to go changing the string that was entered. If the server side validation fails and they stay on the page, I'd prefer they see the same string they originally entered. – Troy Hunt Sep 01 '12 at 08:11
  • You could turn client validation off altogether, and just validate on the server: `@Html.TextBoxFor(m => m.ScanUrl, new Dictionary { { "type", "url"}, {"data-val", "false" } })` – Matt Tew Sep 01 '12 at 08:17
  • No joy, even with the attribute rendered to the input it still validates. Besides, I'd prefer not to lose the mandatory field validation on the client, preference is still to have the url type behave like a text type. – Troy Hunt Sep 02 '12 at 22:26
0

You can also tell the jQuery Validation plugin to selectively ignore input fields based on a selector you specify, which could be a class you add to all elements to ignore, like class="ignorejQueryValidate", or in your case, all inputs with type="url".

See the section on the ignore option in the jQuery docs on the Validation plug-in's validate() method.

@using (Html.BeginForm("SomeAction", "SomeController", FormMethod.Post, new { id = "myForm"}))
{
    @Html.TextBoxFor(m => m.ScanUrl, new { type = "url", @class = "ignorejQueryValidate" })
}

$("#myForm").validate({
    ignore: ".ignorejQueryValidate"
});

or

@using (Html.BeginForm("SomeAction", "SomeController", FormMethod.Post, new { id = "myForm" }))
{
    @Html.TextBoxFor(m => m.ScanUrl, new { type = "url" })
}

$("#myForm").validate({
    ignore: "input[type=url]"
});
nekno
  • 19,177
  • 5
  • 42
  • 47
  • I don't know if that would work right in the context of ASP.NET MVC, because MVC's "unobtrusive validation" script automatically calls its own `.validate()` on the form, based on data- attributes on the inputs. If a second `.validate()` call was necessary to set the ignore selector, I think one would supersede the other. Also, ignoring the input would also disable the "required" validation that Troy still wanted to retain. – Dave Ward Sep 04 '12 at 21:14