22/11/2016

How to validate dynamic UI with JQuery?

Home


Source: own resources, Authors: Agnieszka and Michał Komorowscy

One of the most interesting task I developed some time ago was a library responsible for the generation of dynamic UI based on XML description in ASP.NET MVC application. The task was not trivial. The UI had to change based on the selections made by a user. I had to support many different types of controls, relations between them e.g. if we select the checkbox A then the text box B should be disabled and of course validations. In order to perform the client side validations I used jQuery Unobtrusive Validation library. I thought that it'll work just like that but it turned out that a dynamic UI may cause problems. Here is what I did.

Let's start with the short reminder of how we usually use jQuery Unobtrusive Validation in ASP.NET MVC project. Firstly, let's create a new ASP.NET MVC project with a very simple view model with the data annotation that specifies that Name property is obligatory:
public class SimpleVM
{
    [DisplayName("Name:")]
    [Required(ErrorMessage = "Please enter the name.")]
    public string Name { get; set; }
}
We also need 2 actions in a controller. One to return an instance of a view model and the second one to process it.
[HttpGet]
public ActionResult Simple() { return View(new SimpleVM()); }

[HttpPost]
public ActionResult Simple(SimpleVM vm)
{
   if (ModelState.IsValid) return View(vm);
   return View("Error");
}
As you can see in POST action we check IsValid property to be sure that a model is correct. It is a server side validation. If something is wrong we redirect user to an error page. Of course it is extremely simplified approach. Now let's move to the view and to the client side validation:
@model SimpleVM
@using (Html.BeginForm()
{
   <div>
      @Html.LabelFor(x => x.Name)
      @Html.TextBoxFor(x => x.Name)
      @Html.ValidationMessageFor(x => x.Name)
    </div>
    <div>
      <input type="submit" value="Save" />
    </div>
}
@section Scripts { @Scripts.Render("~/bundles/jqueryval") }
We have a form with a label, a text box and a placeholder for a validation message. I also included a bundle because without that validations will not work. If we leave the text box empty and then submit a form, we will see an error message and the request won't be sent to the server. This a very brief introduction. If you want more details for example see this post.

Now let's try to generate the part of a page dynamically. Here is an amended view:
@using (Html.BeginForm())
{
    <div id="placeholder">
    </div>
    <div>
        <input type="submit" value="Save" />
    </div>
}

<input type="submit" value="Load UI" onclick="loadUI()" />

@section Scripts 
{ 
    @Scripts.Render("~/bundles/jqueryval")
    <script type="text/javascript">
        function loadUI() {
            $.get('/Home/Generate/',
                function(data) {
                    $('#placeholder').html(data);
                });
        }
    </script>
}
This time we have an empty div/placeholder inside a form. It's content will be set if a user clicks Load UI button. loadUI function simply makes a call to the server to generate UI/HTML. The last part is Generate action. For simplicity it'll only return a hardcoded HTML in the following way:
[HttpGet]
public ActionResult Generate()
{
   var html =
      "<label for=\"Name\">Name:</label>" +
      "<input data-val = \"true\" data-val-required = \"Please enter the name.\" id = \"Name\" name=\"Name\" type=\"text\" >" +
      "<span class\field-validation-valid\" data-valmsg-for=\"Name\" data-valmsg-replace=\"true\"></span>";
   return Content(html);
}
Now, if we run the application and click Load UI button, we'll get exactly the same HTML as before. However, if we leave the text box empty and then submit a form, we will NOT see an error. Why? The problem is that jQuery actually doesn't know anything about the part of a page that was generated dynamically after the whole page has been already loaded. We need to inform it about this fact. Let's modify loadUI function:
@using (Html.BeginForm("Simple", "Home", FormMethod.Post, new { id = "myForm" }))
...
function loadUI() {
   $.get('/Home/Generate/',
      function(data) {
         $('#placeholder').html(data);

         var form = $("#myForm");
         form.removeData("validator");
         form.removeData("unobtrusiveValidation");
         $.validator.unobtrusive.parse(form);
      });
}
Firstly, I find a form with a given id and I remove already existing validations. Then I instruct jQuery to parse a form again to create validations for all controls including these generated dynamically.

The same problem as described may occur in a more common scenario i.e. with partial views and Ajax requests which are sent to the server after the page has been already loaded. It's not the rocket science but it's worth knowing about it.

1 comments:

for ict 99 said...
This comment has been removed by a blog administrator.

Post a Comment