SMercer

jQuery Style ASP.NET Form Validators

Jul
14,
2008
by SMercer
     

Recently, we here at delphic sage having been starting down the road to adopting jQuery as our client side framework of choice with regards AJAX and DHTML deliciousness. Part of this process included identifying which .NET controls we needed to replace with jQuery plugins. One of these that came up was the ASP.NET validators, which although highly effective and easy to use, offer a rather dated UI experience. I did a bunch of searching around and discovered these wonderful jQuery validators provided by bassisstance.de (Jörn Zaefferer has written many great jQuery plugins, check them out!).  These validators are just what we were looking for, but it didn't take long to discover that (A) they are not very .NET friendy because they rely on elements having a "name" attribute, and (B) they are not nearly as easy to implement as the asp.net validation controls from a developer perspective.

validator in action

  Here is a look at the final result. See Demo | Download the Source Code

OK, So Let's Eat Our Cake, too

The jQuery validators look and behave greatly, but the asp.net validators are much easier to use. My mission here is to extend the asp.net validator controls to highlight and focus on invalid form fields in way that is comparable to what Jörn has done with his jQuery validators. The key advantage that I will try to attain is that I want the validators to be completely transparent to our developers when implementing in forms. They should continue to implement ASP.NET validation the same way they always have, and I should be able to upgrade the validators accross various legacy projects with very little effort. Because of this requirement, using inheritance to override the control behavior was out of the question since it would require developers to use new control tags and references for the new controls. Now that the goals have been defined, it's time to get hacky with some javascript.

Making it Happen

The first thing I did was use firebug to dissect the ASP.NET validation code that is included in all pages and identify the events and function that I needed to hijack. Then, I cooked up a few new javascript functions to handle the field highlights and focus change. The way I overrided the default ASP.NET functionality without traditional inheritance was using the "last one wins" rule, where I essentially just redefined the ASP.NET javascript validation functions after they were instantiated in the resource.axd includes. Because my version of the ASP.NET functions is written later in the HTML output stream, my code takes precedence and overrides the default behaviors. To guarantee that my scripts are rendered last, I had to register the include in the Render() event handler of the page lifecycle, which I accessed through a shared, base master page so that it would be global throughout our applications.

Long story short, here are the steps to jQuery Style validation for ASP.NET:

1. Create a CSS Class that allows you to highlight invalid inputs and display messages in a pretty way

   1:  input.error  { background: #fed; border: 1px solid red; }
   2:  select.error { background: #fed; border: 1px solid red; }
   3:  label.error, .form-side-labels label.error, .form-top-labels label.error
   4:  {
   5:   	display: block;
   6:   	margin: 0 0 0 5px;
   7:   	padding: 3px;
   8:   	width: auto;
   9:   	font-weight: bold;
  10:   	color: #fff;
  11:   	background: red;
  12:  }
  13:  .form-top-labels label.error
  14:  {
  15:   	margin: 3px 0 0;
  16:   	width: 416px;
  17:  }
  18:  .form-top-labels .side-by-side label.error, .form-top-labels .side-by-side-last label.error { width: 200px; }

2. Override the Render event handler of your Page or MasterPage class to insert a reference to your re-written event validation scripts

First, create an include file to contain the new javascripts. I called it validators.js. Here are the contents from that file:
   1:  ValidatorCommonOnSubmit = function() {                    
   2:      ClearValidatorCallouts();
   3:      var result = SetValidatorCallouts();                                                                                           
   4:      return result;
   5:  }
   6:   
   7:  ValidatorValidate = function(val, validationGroup, event) {
   8:      val.isvalid = true;
   9:      if ((typeof(val.enabled) == 'undefined' || val.enabled != false) && IsValidationGroupMatch(val, validationGroup)) {
  10:          if (typeof(val.evaluationfunction) == 'function') {
  11:              val.isvalid = val.evaluationfunction(val);
  12:              if (!val.isvalid && Page_InvalidControlToBeFocused == null &&
  13:                  typeof(val.focusOnError) == 'string' && val.focusOnError == 't') {
  14:                  ValidatorSetFocus(val, event);
  15:              }
  16:          }
  17:      }
  18:      
  19:      ClearValidatorCallouts();
  20:      SetValidatorCallouts(); 
  21:   
  22:      ValidatorUpdateDisplay(val);
  23:  }
  24:   
  25:  SetValidatorCallouts = function()
  26:  {
  27:      var i;
  28:      var pageValid = true;                    
  29:      for (i = 0; i < Page_Validators.length; i++) {         
  30:          var inputControl = document.getElementById(Page_Validators[i].controltovalidate);               
  31:          if (!Page_Validators[i].isvalid) {                                                        
  32:              if(pageValid)
  33:                  inputControl.focus();
  34:              WebForm_AppendToClassName(inputControl, 'error');
  35:              pageValid = false;                                                     
  36:          }                        
  37:      }                    
  38:      return pageValid;
  39:  }
  40:   
  41:  ClearValidatorCallouts = function()
  42:  {
  43:      var i;                    
  44:      var invalidConrols = [];
  45:      for (i = 0; i < Page_Validators.length; i++) {         
  46:          var inputControl = document.getElementById(Page_Validators[i].controltovalidate);               
  47:          WebForm_RemoveClassName(inputControl, 'error');                                                  
  48:      }                                        
  49:  } 

Next, we'll register this file as a javascript include at the latest point possible in the asp.net page lifecycle, which is Render(). This timing is what allows us to redefine the built ASP.NET functions.

   1:  protected override void Render(HtmlTextWriter writer)
   2:  {
   3:      string validatorOverrideScripts = "<script src=\"/js/validators.js\" type=\"text/javascript\"></script>";
   4:      this.Page.ClientScript.RegisterStartupScript(this.GetType(), "ValidatorOverrideScripts", validatorOverrideScripts, false); 
   5:      base.Render(writer);
   6:  }

3. Lastly, Add some validators to a web form and see it in action

   1:  <fieldset>
   2:      <legend>Login Information</legend>
   3:      <div class="row">
   4:          <label class="required">
   5:              Email Address
   6:              <span class="subtle"></span>
   7:          </label>
   8:          <asp:TextBox ID="txtEmail1" CssClass="large required email"  runat="server" ></asp:TextBox>                            
   9:          <asp:RequiredFieldValidator ID="valEmail" runat="server" Display="Dynamic" ControlToValidate="txtEmail1" ErrorMessage="Email Address is required."><span class="error">Email Address is required.</span></asp:RequiredFieldValidator>
  10:          <asp:RegularExpressionValidator ID="valEmailAdd" runat="server" Display="Dynamic" ControlToValidate="txtEmail1" ValidationExpression=".*@.{2,}\..{2,}" ErrorMessage="Email Address should be in name@domain.com format."><span class="error">Email Address should be in name@domain.com format.</span></asp:RegularExpressionValidator>
  11:          <div class="clear"></div>
  12:      </div>
  13:      <div class="row">
  14:          <label class="required">Password</label>
  15:          <asp:Textbox ID="txtPassword" CssClass="large required" runat="server"  ></asp:Textbox>
  16:          <div class="clear"></div>
  17:      </div>
  18:      <div class="row">
  19:          <label class="required">Confirm Password</label>
  20:          <asp:TextBox ID="txtConPassword" CssClass="large required"  runat="server" ></asp:TextBox>
  21:          <div class="clear"></div>
  22:      </div>
  23:  </fieldset>

There you have it, all the utility of ASP.NET validation controls without the antiquated user experience. Feel free to download the source code and share the love!

Archives: July 2008