Unobtrusive jQuery Validation for Knockout in ASP.NET MVC

This article is from our ASP.NET MVC 101 Tutorial Series

In this article, we will explore how we can use the jQuery Validator in an ASP.NET MVC application that uses Knockout Templating. Traditionally, jQuery validator works out of the box with KO ViewModels that are statically bound.

I would recommend reading two articles before you read this one Simple Databinding and Templating using Knockout and ASP.NET Web API & Change Tracking using KnockoutJS and ASP.NET Web API

But there are a couple of additional things that you’ve to do when templates are involved and the DOM is manipulated on the fly.

The Harness

Today we use a slightly different harness from the first two parts because we need validations for the dynamic controls added by the template.

We’ll create an MVC4 Application using the Internet template.

The first thing to do after the project is setup is to update jQuery, jQuery.UI.Common, jQuery.Validation and Knockout packages from Nuget.

PM> update-package jquery
PM> update-package jQuery.UI.Combined
PM> update-package jquery.Validation
PM> update-package knockoutjs

Note: The jQuery updates are critical for the scope of this article, because jQuery Validation 1.10 has a bug that renders it useless in IE10. You have to update the code to fix the issue before you can proceed. 1.11 fixes the issue.

Setting up the View Model

Once the harness is ready, we add a JavaScript file to the project under the Scripts folder called jqtemplatesample.js.

Next we set up a simple View Model.

var viewModel = {
    collect: ko.observableArray([
    {
        position: "position0",
        url: ko.observable("#"),
        description: ko.observable("Some Description")
    }]),
}


It essentially consists of an observable collection of objects with three properties, position (key), url and description. The description field is bound two-ways and is a ko-observable.

Setting up the View

We clean the Index.cshtml and start fresh. The view is marked up as follows.

@{
    ViewBag.Title = "Home Page";
}
<button id="addData">Add Data</button>

<form action="submitForm">
<div>
  Unbound
  <input id="requiredField" name="requiredField" data-val="true" data-val- required="Required"
type="text" />
   <span class="field-validation-valid" data-valmsg-for="requiredField" data-valmsg-replace="true"></span>
  </div>
  <div id='dataDiv'>
   <table>
    <thead>
     <tr>
      <th>Url</th>
      <th>Description</th>
     </tr>
    </thead>
    <tbody data-bind="foreach: collect">
     <tr>
      <td>
       <a class="launchDialog" data-bind="attr: { url: url() }">Url</a>
      </td>
     <td>
      <input data-bind="value: description, attr: { 'id': position, 'name': position }"
                            data-val="true" type="text"/>
      <span class="field-validation-valid" data-bind="attr: { 'data-valmsg-for': position }" data-valmsg-replace="true"></span>
     </td>
    </tr>
   </tbody>
  </table>
</div>
<button id="submitButton">Submit</button>
</form>

It has an addData button to add data into the view model.

Next it has an un-bound input box to verify that the jQuery validators are firing correctly. Note the data-val=”true” setting and data-val-required=”Required” setting in the Input element. The span tag links to the input via the data-val-msg-for attribute which maps to the id of the input element.

The ‘dataDiv’

This div serves as the container for the data that is bound to the ViewModel. We have used a table here, but you could easily use an ul or ol.

The tbody is bound to the collection (named collect) in the ViewModel; Binding is achieved using the html5 data- attribute (data-bind as recognized by KO).

An anchor tag is bound to the url property of the view model object in the collection.

The Input element’s ‘value’ property is bound to the description whereas the ‘id’ and the ‘name’ are bound to the ‘position’ property in the viewmodel object.

To show the Error message (if any) we’ve added a span tag and data-bind attribute binds the ‘data-valmsg-for’ attribute to the ID of the input tag (i.e. the position property in the view model).
To round off we have a submitButton that posts back to the Server.

Subscribing to Events and Adding Data to View Model

Now let’s get back to the jtemplatesample.js and wire up the events to add data, post to server and finally initialize ko bindings.

$(document).ready(function ()
{
ko.applyBindings(viewModel);
$(document).delegate("#addData", "click", null, function ()
{
  viewModel.collect.push(
  {
   position: "position" + viewModel.collect().length,
   url: ko.observable("#"),
   description: ko.observable("Some Description" + viewModel.collect().length)
  });
  return false;
});
$(document).delegate("#submitButton", "click", null, function ()
{
  $.ajax({
    url: "Home/Index",
    contentType: "application/json",
    dataType: "json",
    data: {}
  });
});
});

In the document’s ready event, we apply the ko bindings. Next, we assign a delegate for the click event of the addData button and push a new object into the viewModel’s collection (named collect). Finally we assign a delegate to submitButton that does an Ajax Post to the server.

Ready, Set, Go… err… umm! (Nearly)

We can run the application now and check that clicking on Add Data should add a row in the UI because of the KO binding to the ViewModel that got updated. Now remove some of the default description to make them empty, keep the unbound field empty and hit Submit.

unobtrusive-validation-failed

Daaang!!! Only the Unbound field is showing the Validation error, the dynamically added fields are not! Why?

Well here is what I got after I deep dived into the issue:

Navigate to the Index.cshtml and in the following input element, add an additional attribute

<input data-bind="value: description, attr: { 'id': position, 'name': position }"
    data-val="true" type="text" required/>


Yup you got it right, just the required attribute and if you are HTML5’ically inclined you can say required=”required”. Save and Refresh.

Add some Data, clear out the Description and hit Submit. Lo and Behold!!!

jquery-validator-success

“Eh! What’s up Duck?”

That’s what Bugs bunny would say, but it took me ‘a bit’ of digging around to comprehend what’s going on! Now I feel like Daffy Duck who just sat on an exploding dynamite stick!

Enter Unobtrusive jQuery Validation in MVC

Apparently the Unobtrusive jQuery Validator plugin from Microsoft doesn’t like dynamic elements so it drops them from the validation sweep. However, introducing the ‘required’ tag gets jQuery Validator in action and it goes ahead and kicks in the required validation (how could I forget that!! ). Hence you see the two different error messages above. The unbounded one was caught by MS Unobtrusive validator and is showing the related message as specified in the data-valmsg-for attribute. The ones caught by jQuery.Validator are showing the default jQuery validator’s messages. You can override it by using a minimum of one line in the document ready function

$.validator.messages.required = 'Required';

Refresh and repeat. Now you can see the validation errors are the same!

jquery-unobtrusive-validators-same-message

Wrap Up

I read a lot of posts and articles before I literally stumbled upon this solution on my own. The caveat is that it uses the latest versions of all the JS libraries in use, so your mileage may vary based on the combination of versions you are using.

There is another approach to solving this, that’s by creating a plugin that hooks into the newly created DOM elements at submit time and does the validation. You can refer to the solution here. Whichever solution you choose, jQuery Validation for Templated elements using Knockout is rather easy to achieve once you’ve gotten hang of the pitfalls.

Download the entire source code of this article (Github)

2 comments:

  1. Your Article is great, infect I was looking for this type of Article but I am having one problem, when I run your Application on VS2013 it worked fine but as I run it on VS2012 it stops working, could you please help me out what to do!

    ReplyDelete
  2. An important correction: Bugs Bunny says "What's up, Doc?". Doc is short for doctor.

    ReplyDelete