pop·cy·cli·cal

Sunday, January 3, 2010

jQuery Validate and Jeditable, Part 2

When using Jeditable, there is no form element to bind jQuery Validate rules with.  Instead, when an editable element is clicked or activated, it dynamically creates a new form and input element and destroys them after the user is done editing.  For the ViewModel from Part 1, the View might be rendered like so for Jeditable:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ValidateViewModel>" %>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<% using(Html.BeginForm()) {%>
    <%= Html.ValidationSummary() %>
    <label for="StringRequired">StringRequired:</label>
    <div class="editable" id="StringRequired" name="StringRequired">
        <%= Model.StringRequired %>
    </div>
    
    <label for="DoubleRange13_100">DoulbeRange13_100:</label>
    <div class="editable" id="DoubleRange13_100" name="DoubleRange13_100">
        <%= Model.DoubleRange13_100%>
    </div>
<%} %>
</asp:Content>

xVal’s ClientSideValidation<TViewModel>() used in Part 1 won’t work to validate this.  The reason?  It generates a script that binds validation directly to the form elements on page load.  The rendered script looks for the ViewModel looks like:

<script type="text/javascript">
xVal.AttachValidator(null, 
    {"Fields":[
      {
         "FieldName":"StringRequired",
         "FieldRules":[
            {
               "RuleName":"Required",
               "RuleParameters":{

               },
               "Message":"This string is required"
            },
            {
               "FieldName":"DoubleRange13_100",
               "FieldRules":[
                  {
                     "RuleName":"Range",
                     "RuleParameters":{
                        "Min":"13",
                        "Max":"100",
                        "Type":"decimal"
                     },
                     "Message":"Must be between 13 and 100"
                  }
               ]
            }
         ]
      }
    ]}, {})
</script>

The rules are in the xVal’s StandardJSON format and the AttachValidator function (in xVal.jquery.validate.js) scans the DOM and attaches jQuery Validate rules as attributes to the matched input elements.  Since Jeditable doesn’t create these elements until they’re actively being edited, the rules have nothing to attach to since they don’t exist yet.  Fortunately, jQuery Validate provides several strategies for defining the rules.  In addition to being able to attach attributes to the input elements, the rules can be placed in a separate data structure.  jQuery Validate refers to these as “static rules”.  Instead of attaching the xVal rule set directly to the elements, it can be adapted to the static rule set that jQuery Validate can use directly.  The structure for the ViewModel rules will look like:

{
    rules: {
        StringRequired: {
            required: true
        },
        DoubleRange13_100: {
            number: true,
            range: ["13”, "100"]
        }
    }
    messages: {
        StringRequired: {
            required: "This string is required."
        },
        DoubleRange13_100: {
            range: "Must be between 13 and 100"
        }
    }
}

I've adapted some javascript to do this conversion - it's available here

.  To get the ViewModel’s rules into this format for javascript consumption, this line is added:
<script type="text/javascript">
    var validateOptions 
        = convertXvalToValidateOptions(
            <%= Html.ClientSideValidationRules<ValidateViewModel>()%>
        );
</script>

 

To get these attached to form elements as soon as the user activates them, Jeditable’s “plugin” feature is utilized:

$(function() { // <- on document ready
    // register plugin with Jeditable to tie in jQuery Validate
    $.editable.types['text'].plugin = bindValidate;
    
    // attach Jeditable to each element with class "editable"
    // Note: this must be done one-by-one so that the 
    // element's name can be assigned to Jeditable's "name" 
    // option which is used by jQuery Validate
    $('.editable').each(function() {
        var element = $(this);
        
        element.editable(
            'SaveUrlOrFunctionGoesHere',
            {
                // submit when the element is blurred
                onblur: 'submit',
                onsubmit: jeditableValidate,
                // assign the name of the input element 
                // from the element's name - this is needed 
                // because it's what jQuery Validate uses 
                // to bind the rules to the input element
                name: element.attr('name')
            }
        );
    });
});

// Jeditable plugin
function bindValidate(settings, self) {
    // attach jQuery Validate to 
    // Jeditable's dynamically created form
    $('form', self).validate(validateOptions);
}

// runs before values are submitted to server
function jeditableValidate(settings, self) {
    // validate the Jeditable dynamically created form
    return $('form', self).valid();
}

With this glue in place, the form elements will now be validated with the rules defined in the ViewModel.  All fields valid:

jQueryValidateJeditable1

…and here after both have invalid values:

jQueryValidateJeditable2

A few notes:

  • Any additional options to be sent to jQuery Validate can be attached to the validateOptions object. I’ve used this to place all error messages into a separate errorLabelContainer (like here).
  • I feel that AttachValidator function in xVal.jquery.validate.js from could become more loosely coupled by separating the rule conversion from the DOM element attachment.

I think both of these jQuery libraries provide a great benefit when creating interactive and helpful forms.  Kudos to Jörn Zaefferer and Mika Tuupola for the good work.  xVal is likewise an excellent library – thanks to Steve Sanderson.

Thursday, December 31, 2009

jQuery Validate and Jeditable, Part 1

I was recently tasked to add server-and-client-side form validation for an ASP.NET MVC site - which already uses in-place editing courtesy Jeditable.  I really like the field editing experience that Jeditable provides – it makes form entry in the browser interactive, is fairly straightforward to integrate, and it’s adaptable to many scenarios.  It does not, however, have any validation mechanism built in. 

Our project already used jQuery Validate for a few forms by using the HTML class definitions – like adding class=”required phone” to an INPUT element.  This works great, but doesn’t provide any server-side validation tie-in.

Earlier this year, I remembered having seen a presentation by Elijah Manor who mentioned using xVal for robust server and client side validation. And with the news that MVC 2 will have a built in validation technique similar to xVal, it was an easy decision to start investigating this library.

xVal is a pretty easy to get integrated.  The first step is to decorate the model with validation rules – I’ve decided to use .NET framework’s DataAnnotations, which ends up looking like:

public class ValidateViewModel
{
    [Required(ErrorMessage = "This string is required")]
    public string StringRequired { get; set; }

    [Range(13, 100, ErrorMessage = "Must be between 13 and 100")]
    public double DoubleRange13_100 { get; set; }
}
public class ValidatedController
{
    public ViewResult Index()
    {
        return View(new ValidateViewModel());
    }
}
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ValidateViewModel>" %>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<% using(Html.BeginForm()) {%>
    <%= Html.ValidationSummary() %>
    <label for="StringRequired">String Required:</label>
    <%= Html.TextBoxFor(m => m.StringRequired) %>
    <%= Html.ValidationMessageFor(m => m.StringRequired)%>
    
    <label for="DoubleRange13_100">Doulbe between 13 and 100:</label>
    <%= Html.TextBoxFor(m => m.DoubleRange13_100) %>
    <%= Html.ValidationMessageFor(m => m.DoubleRange13_100)%>
<%} %>
</asp:Content>

To validate this ViewModel server side, we use a DataAnnotationsValidationRunner like the one in xVal’s documentation:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Update(ValidateViewModel model)
{
    try {
        DataAnnotationsValidationRunner.ValidateModel(model);
        // it’s valid, do the actual update
        var domainObject = ValidateDomainModel.Find(model.id);
        Map(model, domainObject);
        domainObject.Update();
    }
    catch(RulesException ex) {
        ex.AddModelStateErrors(ModelState, null);
    }

    return ModelState.IsValid ? RedirectToAction("Completed")
                              : (ActionResult) View();
}
public static class DataAnnotationsValidationRunner
{
    private static IEnumerable<ErrorInfo> GetErrors(object instance)
    {
        return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
               from attribute in prop.Attributes.OfType<ValidationAttribute>()
               where !attribute.IsValid(prop.GetValue(instance))
               select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(String.Empty), instance);
    }

    /// <summary>Validates the given <param name="model">model</param></summary>
    /// <exception cref="RulesException">Thrown if any errors are found</exception>
    public static void ValidateModel(object model)
    {
        var errors = GetErrors(model);
        if (errors.Any())
            throw new RulesException(errors);
    }
}

Aside: the project uses the Active record pattern via Castle ActiveRecord for data access without an intermediate business-logic layer.  For this case, the rules are defined on the ViewModel and are validated in the controller.  This does add some noise in the actions – I’m definitely interested in other methods for handling this. Such as - perhaps the validation could be placed inside the ViewModel?

The next step is to add client-side validation.  xVal’s built-in jQuery Validate rule generator makes this ridiculously simple – just reference jquery.validate.js and xVal.jquery.validate.js in the view, and then this single line:

<%= Html.ClientSideValidation<ValidateViewModel>() %>

The rules defined in the ViewModel will now be validated client side and enforced for server side actions.  This works great for a statically defined HTML form, but I learned that integrating with Jeditable’s dynamic inline forms to be not so straight forward.

Continued in Part 2