Tuesday, September 29, 2009

Custom Validation Summary - The Javascript way!

I was once faced with a unique requirement, I had to make all the validation summaries which appear in a website clickable such that once you click on the error list, the control which produed the error should be focused upon.
This was intresting and I thought it would interest other people who face the same dillemma.
Now, all this can of course be done through custom controls however nobody wanted to go through 100's of pages and then change the validation summary to the custom control, so I decided to do it the Javascript way.

To start off, Validations in ASP.NET are done through Javascript, no revelation there, these scripts are stored in a WebResource.axd file which is downloaded into the clients temporary internet files folder. The methodology followed here is that I took the function generated by .NET from the WebResource file, modified it and shadowed the function so that my custom function gets called every time instead of the default one which .NET produces.

To dwelve into the code, below is an extract from a ".JS" file that i had created:

function InitializeValSummary() {

if (typeof (ValidationSummaryOnSubmit) == 'function') { ValidationSummaryOnSubmit = ValidationSummaryOnSubmit_Overload; }

}


function SetElementFocus(strValID) {

var objValidator = strValID;

var blnFlag = false;

if (objValidator.controltovalidate == null && objValidator != null) {



var arrElementsToFocus = document.getElementById(objValidator.id).parentNode.getElementsByTagName('Input');

if (arrElementsToFocus && arrElementsToFocus.length > 0){

for (var intIndex = 0;intIndex<arrElementsToFocus.length;intIndex++){

if (arrElementsToFocus[intIndex].type != 'hidden'){

arrElementsToFocus[intIndex].focus();

blnFlag = true;

break;

}

}

}

if (!blnFlag){

arrElementsToFocus = document.getElementById(objValidator.id).parentNode.getElementsByTagName('TextArea');

if (arrElementsToFocus && arrElementsToFocus.length > 0) arrElementsToFocus[0].focus();

}

return;

}

if (objValidator == null || typeof (objValidator.controltovalidate) != "string") return;


var objControlToFocus = document.getElementById(objValidator.controltovalidate);

if (objControlToFocus == null) return;



var arrCtrlToFocus = document.getElementById(objControlToFocus.id).parentNode.getElementsByTagName('Input');

if (arrCtrlToFocus && arrCtrlToFocus.length > 0) {

for (var intCount = 0; intCount < arrCtrlToFocus.length; intCount++) {

if (arrCtrlToFocus[intCount].type != 'hidden') {

arrCtrlToFocus[intCount].focus();

break;

}

}

}

else {

objControlToFocus.focus();

}

}


function ValidationSummaryOnSubmit_Overload(validationGroup) {

if (typeof (Page_ValidationSummaries) == "undefined")

return;

var summary, sums, s;

for (sums = 0; sums < Page_ValidationSummaries.length; sums++) {

summary = Page_ValidationSummaries[sums];

summary.style.display = "none";

if (!Page_IsValid && IsValidationGroupMatch(summary, validationGroup)) {

var i;

if (summary.showsummary != "False") {

summary.style.display = "";

if (typeof (summary.displaymode) != "string") {

summary.displaymode = "BulletList";

}

switch (summary.displaymode) {

case "List":

headerSep = "<br>";

first = "";

pre = "";

post = "<br>";

end = "";

break;

case "BulletList":

default:

headerSep = "";

first = "<ul>";

pre = "<li>";

post = "</li>";

end = "</ul>";

break;

case "SingleParagraph":

headerSep = " ";

first = "";

pre = "";

post = " ";

end = "<br>";

break;

}

s = "";

if (typeof (summary.headertext) == "string") {

s += summary.headertext + headerSep;

}

s += first;

for (i = 0; i < Page_Validators.length; i++) {

if (!Page_Validators[i].isvalid && typeof (Page_Validators[i].errormessage) == "string") {

s += pre + '<a style="cursor:pointer" onclick=SetElementFocus(' + Page_Validators[i].id + ')>' + Page_Validators[i].errormessage + '</a>' + post;

}

}

s += end;

summary.innerHTML = s;

window.scrollTo(0, 0);

}

if (summary.showmessagebox == "True") {

s = "";

if (typeof (summary.headertext) == "string") {

s += summary.headertext + "\r\n";

}

var lastValIndex = Page_Validators.length - 1;

for (i = 0; i <= lastValIndex; i++) {

if (!Page_Validators[i].isvalid && typeof (Page_Validators[i].errormessage) == "string") {

switch (summary.displaymode) {

case "List":

s += Page_Validators[i].errormessage;

if (i < lastValIndex) {

s += "\r\n";

}

break;

case "BulletList":

default:

s += "- " + Page_Validators[i].errormessage;

if (i < lastValIndex) {

s += "\r\n";

}

break;

case "SingleParagraph":

s += Page_Validators[i].errormessage + " ";

break;

}

}

}

alert(s);

}

}

}

}


function AddFunctionToLoad(){

var objFunc = window.onload;

if (objFunc !=null && typeof(objFunc) == 'function'){

window.onload = function(){

objFunc();

InitializeValSummary();

}

}else{

window.onload = function() { InitializeValSummary(); }

}

}

AddFunctionToLoad();




Lets start from the basics; The first step is to create a copy of the validation function to do that simply locate the webresource.axd file (there will be multiple you'll have to fish out the one which contains the validation scripts), copy the function "ValidationSummaryOnSubmit" which basically is responsible for creating the summary that we see in the UI. After copying change the function name to something else (in my case I usually suffix it with "_Overloaded" to know which function it originally was). In the bold line in the code i have slightly modified the .Net output such that instead of a regular red text, the text is wrapped in a "anchor" tag which calls a function in its "onclick" event.
The "Page_Validators" is an array which will already be available when this function is run and contains the validator objects of the validators which lie in the same validation group as the validation summary.


Now that we have the custom function ready we have to shadow the .Net version such that our custom function gets called instead of the default one.
To achieve the above the following points need to be kept in mind:

  1. A javascript function can be shadowed by simply changing the pointer of that function by making it "equal" to another function (see function InitializeSummary)

  2. The .Net function will be available only after the page fully loads.

  3. To shadow the function we have to then include our initialize method in the window.onload event, which is achieved by the function "AddFunctionToLoad" (see code)

  4. Once shadowed, the original function is lost and all calls will come to our custom function.





Now all that is remaining is to create a function which sets the element focus based upon the validator object that is passed onto the the function (see SetElementFocus).
The latter function was made for handling all circumstances that occurred in my web application which required the script, I'll leave it up to the users to figure the function out, it's pretty straight forward in my opinion.

Hope this saved you a lot of time researching.

No comments:

Post a Comment

Followers