Tuesday, February 10, 2009

Overriding javascript in webresource.axd


ASP.NET simplifies a lot of work on the programmer's part. An ASP control can render both client side and server side script. Good news for the most part, however, what if the output of the control isn't exactly what you want? There are methods in the code behind to override rendering output, but I'm going to outline a simple way to override client side javascript rendered by ASP.NET in the webresource.axd file.

My latest struggle was with the validation controls. Security in webforms is crazy important, and if you're not validating your webforms then you're open to a heap of trouble. ASP.NET offers several built in validation options:
  • RequiredFieldValidator
  • RegularExpressionValidator
  • CompareValidator
  • RangeValidator
  • CustomValidator
Each validitator provides some pre-defined client and server side functions. All of the validators derive from the BaseValidator class, which, behind the scenes (technically speaking) does some stuff. The CustomValidator is the most open to your own creations, but if you want client side validation alerts then you'll have to tie those in on your own.

My problem was with the client side code. I really wanted to use the required field and regular expression validators. I wasn't going to take the time to build my own custom validators, but I was having trouble with the default rendered output. Let me add some visual aid to the fire.

Here's a very basic form:

here's the very basic markup:

<asp:textbox ID="textBox" runat="server" />
<asp:RequiredFieldValidator
ControlToValidate="textBox"
ID="requireTextBox"
runat="server"
ErrorMessage="This field is required">
</asp:RequiredFieldValidator>

By default ASP.NET renders the textbox as an <input> and the validation control as a <span>. A screenshot of the validation and the source of the rendered tag:





The first major frustration is that the validator is rendered with inline styles. I ran all over the internet trying to find an easy way to remove this, but ASP.NET is stronger than me, I couldn't win it, so I decided to work with it. I wanted my validation to look a certain way, and this default behavior wasn't it. I have a lot of forms that have a constrained width, but extending the form down wont be a problem. I tried several methods to put the validation alert below the input. You can stick a break tag between the validator and the control to validate. I didn't like my markup being littered with <br /> tags, so I looked into putting the control to validate and the validator in seperate list items, this also cluttered my markup something fierce. I didn't know how to override the controls output method to change the <span> to a <div> or some other block level element, so I used style sheets to make the validation rendered span tags display block level. The validation controls use the CssClass attribute, in the output CssClass="" renders as class="".

I don't want to spoil the surprise, but let me lay down my desired result:

This seemed easy with a style rule added to my stylesheet:
validationControl {
display:block;
background-color:Red;
padding:5px;
margin:5px;
}

I also had to add the ForeColor="white" attribute to my RequiredFieldValidator. The control now renders style="color:white;display:hidden;". It was at this point that I realized I had a problem. What happens when I have more than one control to validate?


Notice that there's a big ugly space between my two inputs? This is because my span, even though it's hidden, is rendered block level, so it will still take up that much space on the rendered page. The incomplete fix is to add another attribute to the RequiredFieldValidator, Display="Dynamic". Dynamic changes my inline styles to display:none.

This brings me to the point: webresource.axd. All the client side functions are rendered to a dynamic file called webresource.axd, there is no file on the server that you can edit, unless you're editing/overriding your controls. There is a function inside the webresource.axd file that handles the switching of your validation. Whether you use Dynamic or not. When the page does not validate, the function switches your inline style from display="hidden", or in case of Dynamic, display="none", to display="inline". Setting my validation spans to display:block is now a mute point, the inline style overrides my stylesheet's display:block. C'mon!


Let me show you how to override the javascript function created in the webresource.axd, first, let's view the webresource.axd. I'm sure there are other developer tools out there that are going to do the same thing for you, but I used the built in dev tools to Apple Safari. In the Develop menu select the Web Inspector. This allows you to inspect the document, images, and scripts; expanding the scripts tab shows me two WebResource.axd files are loaded. There's a search box at the top of the window. I typed in "inline" which returned two results. Double clicking on the resuts locates that line withine the source code. I've copied out the function that we're going to look at:

function ValidatorUpdateDisplay(val) {
if (typeof(val.display) == "string") {
if (val.display == "None") {
return;
}
if (val.display == "Dynamic") {
val.style.display = val.isvalid ? "none" : "inline";
return;
}
}
if ((navigator.userAgent.indexOf("Mac") > -1) &&
(navigator.userAgent.indexOf("MSIE") > -1)) {
val.style.display = "inline";
}
val.style.visibility = val.isvalid ? "hidden" : "visible";
}

At line 7: val.style.display = val.isvalid ? "none" : "inline"; Without explaining too much about ternary operators, this is a compressed if statement. And where it says "inline" is the source of all that is unholy. I copied the function as it was and pasted it at the bottom of my source, before the </body>, inside of script tags. Then, you change the "inline" to "block". The important part here is that my function needs to be interpreted by the browser after the webresource.axd files are. When you view source you'll see them at the top, inside the <body> and before the <form> tag. Placing the script at the bottom is a safe bet, and it's out of the way. Here is my complete source if you're not keeping score at home:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>A Validation Headache</title>
<style type="text/css">
div#content {width:300px; border:solid 1px black; padding:5px;}
ul {list-style-type:none; margin:0; padding:0;}
li {padding:5px;}
.submitButton {margin-top:5px;margin-left:120px;}

.validationControl {
display:block;
background-color:Red;
padding:5px;
margin:5px;
}
</style>
</head>

<body>
<form id="form1" runat="server">
<div id="content">
<ul>
<li>
<label for="textBox">Input 1:</label>
<asp:textbox ID="textBox" runat="server" />
<asp:RequiredFieldValidator
ControlToValidate="textBox"
ID="requireTextBox"
CssClass="validationControl"
ForeColor="White"
runat="server"
Display="Dynamic"
ErrorMessage="This field is required">
</asp:RequiredFieldValidator>
</li>
<li>
<label for="textBoxTwo">Input 2:</label>
<asp:TextBox ID="textBoxTwo" runat="server" />
<asp:RequiredFieldValidator
ControlToValidate="textBoxTwo"
ID="requireTextBoxTwo"
CssClass="validationControl"
ForeColor="White"
runat="server"
Display="Dynamic"
ErrorMessage="This field is required">
</asp:RequiredFieldValidator>
</li>
<li class="submitButton">
<asp:Button ID="submitTextBox" runat="server" Text="Submit" />
</li>
</ul>
</div>
</form>
<script type="text/javascript">
function ValidatorUpdateDisplay(val) {
if (typeof(val.display) == "string") {
if (val.display == "None") {
return;
}
if (val.display == "Dynamic") {
val.style.display = val.isvalid ? "none" : "block";
return;
}
}
if ((navigator.userAgent.indexOf("Mac") > -1) &&
(navigator.userAgent.indexOf("MSIE") > -1)) {
val.style.display = "inline";
}
val.style.visibility = val.isvalid ? "hidden" : "visible";
}
</script>
</body>
</html>

Labels: , , , ,

8 Comments:

Blogger Dan Hiester said...

What happens if you tell it to display: inherit instead of display: block? Would the element then inherit a display attribute assigned to it via a CSS class in the stylesheet?

August 17, 2010 at 12:46 PM  
Blogger William Denton said...

thank you. just what i was looking for.

I used
val.style.display = val.isvalid ? "none" : "";

this allows the css class to do all the control

February 21, 2011 at 5:43 PM  
Blogger frappant said...

This comment has been removed by the author.

October 7, 2011 at 6:58 AM  
Blogger frappant said...

Great solution to an ever returning problem!

October 7, 2011 at 6:58 AM  
Blogger lutano said...

Great! Thank you so much for this!

November 15, 2011 at 3:07 AM  
Blogger Slavebator said...

this is a hacky way, did you look for the problem in your code? I've just figured out mine, it was a validation group property was not set the same for all the group, and I fixed it and now it's working. There is a reason why ValidatorUpdateDisplay method is there.

November 16, 2011 at 10:48 AM  
Blogger Unknown said...

Thanks! You've helped me sidestep another "dealbreaker!"

Working professionally as a framework developer while completing a CIS degree at a 4 year college really increases my "Microsoft Rage"... At school, they force us to use "oldschool" ASP.Net, which has so many dealbreakers built into it that I want to tell my professors "if I were to apply what you're teaching us, my clients would go out of business!"

Thankfully, it really improved my morale when I *gave my function a meaningful name* ;)

Here's what I call it:

function overrideStupidMicrosoftASP() {
ValidatorUpdateDisplay = function (val) {
...
}
}

$(document).on("ready", overrideStupidMicrosoftASP);

October 23, 2013 at 12:37 PM  
Blogger jaslegume said...

Kevin
This is GREAT and you are the very best to fix this very frustrating issue

January 14, 2014 at 11:18 AM  

Post a Comment

Subscribe to Post Comments [Atom]

<< Home