.NET Gotchas: Part I – Multi-line Text boxes and the DefaultButton in ASP.NET 2.0
Have you ever received an error message that resisted even the mighty Google? An application failure that it seems only happens to you and 10 other people in the world (all of whom, as luck would have it, speak different languages)? Have you ever felt you were going mad, staring at the same piece of code over and over again, knowing it should work - willing it to work because it should, but it does not?
Good news! You are not going mad (at least not in this!). Sometimes, the fault lies not with your code, but with your tools. I suppose this is a glancing compliment to Microsoft, because frankly it had never occurred to me that the fault could lie with their products (as I type that it feels peculiar to say, but there it is). As our lives as developers are made easier in many ways due to new and better tools, this is due to a shifting, not removal, of complexity. That complexity has moved to our tools and effects are bound to pop up.
If you were a carpenter, you would be well within your rights to show surprise at your hammer behaving like a saw. Likewise, as a developer, you are, I believe, within your rights to be surprised when your tools do not respond in the expected manner.
My intent is to produce a series of articles providing succor to fellow developers faced with those .NET issues that crop up for which information is difficult to find. By their very nature, these issues should all be short-lived affairs, with corrections (hopefully) on the way in newer releases of the .NET framework or the Visual Studio IDE. However, in the meantime, I hope these articles will help prevent frustration for somebody else.
Many of the issues we explore here are not being explored due to their ground-breaking solutions or because the problems are of such an interesting, cutting-edge nature that the world will want to tune in to watch it all as it happens. On the contrary, they will most likely be mundane, uninteresting solutions to mundane, uninteresting problems. In fact, you will likely not believe that there could ever be such problems, but they can and do crop up.
Let's get to it.
.NET Gotchas - Part I – Multi-line Text boxes and the DefaultButton in ASP.NET 2.0
Different browsers each have their own default behaviour detailing what actions to take when a user hits enter in a form. This is well and good, but there seems to be some natural, unspoken standard that says one should be able to submit a form by simply pressing enter. Through the use of JavaScript, web developers have been able to happily and easily handle this requirement for years. In designing the ASP.NET portion of the .NET Framework 2.0, Microsoft, not being oblivious to this unspoken standard, provided methods of handling and implementing this desired behaviour.
The HtmlForm class contains a property called DefaultButton which gets or sets the child control of the HtmlForm that causes postback when the ENTER key is pressed. When the DefaultButton property is not explicitly set, the browser resorts to whatever its default behaviour is. As you would expect, this feature has been implemented with JavaScript emitted when the page is generated. ASP.NET includes a special handler named WebResource.axd designed to retrieve and serve assembly resources and all of the JavaScript code that makes up the magic of ASP.NET is embedded in such resources and served to the client browser through WebResource.axd.**
Sounds effortless, does it not? A quick mental ‘Thank you Microsoft’ and our work should be completed, right?
Unfortunately, it is not (never) quite so simple. Although I was quite happy with the DefaultButton solution and it was chugging happily along for me on many forms, things all fell apart when I threw a multi-line text box into the mix.
Let's create a web form to illustrate the problem:
<form id="form1" runat="server">
<asp:Panel runat="server" DefaultButton="btnSubmit">
Textbox 1:
<asp:TextBox ID="TextBox1" runat="server" TextMode="MultiLine"/>
<asp:Button ID="btnSubmit" runat="server" OnClick="SubmitClick" Text="Submit"/>
<asp:Label ID="Label1" runat="server" Width="500px"/>
</asp:Panel>
</form>
Your code-behind should resemble the following:
public partial class testbuttons : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.Label1.Text = "";
}
protected void SubmitClick(object sender, EventArgs e)
{
this.Label1.Text = "Submit button clicked";
}
}
If you have developed a website for use in multiple browsers, you may have bumped your head against an interesting issue with this feature. In Internet Explorer, load up your page with a form that has the DefaultButton duly set, type in some lines of text into the text box. Go ahead and type in many lines - feel the heady rush that overcomes you as the textbox bends to your will, quietly inserting newline characters into the textbox with each and every stroke of the ENTER key! Now, load up that self-same page in Firefox, start typing in some text and hit ENTER.
What happened? Our joy has been interrupted!
What happened is that our handy-dandy multi-line text box no longer acts like a multi-line text box. Rather than causing a new line, hitting ENTER activates the default button.
The reason for this behaviour is the FireDefaultButton JavaScript code that is responsible for supplying the behaviour. Unfortunately, it seems that some IE-specific code managed to make make its way into that method, the end result being that Firefox (and other browsers using a different object model than IE) will not run the code as expected.
Here is the code for FireDefaultButton with the offending parts emphasized:
function WebForm_FireDefaultButton(event, target)
{
if (event.keyCode == 13 &&
!(event.srcElement &&
event.srcElement.tagName.toLowerCase() == "textarea")) {
var defaultButton;
if (__nonMSDOMBrowser) {
defaultButton = document.getElementById(target);
} else {
defaultButton = document.all[target];
}
if (defaultButton && typeof defaultButton.click != "undefined") {
defaultButton.click();
event.cancelBubble = true;
if (event.stopPropagation) {
event.stopPropagation();
}
return false;
}
}
return true;
}
The property event.srcElement is not understood by Firefox which causes our problem. That's all. As I said, dead simple, but that simplicity is cold comfort when you are shaking your fist at a monitor for 3 hours cursing the heavens because you can see no reason why your code isn't working as it should.
The fix? Equally as simple. In my case, I added a new variable, var element = event.target || event.srcElement; and replaced event.srcElement with element, resulting in:
function WebForm_FireDefaultButton(event, target)
{
//event.srcElement doesn't work in FF so we check whether
//it or event.target exists, using whichever is returned
var element = event.target || event.srcElement;
if (event.keyCode == 13 &&
!(element &&
element.tagName.toLowerCase() == "textarea"))
{
var defaultButton;
if (__nonMSDOMBrowser)
{
defaultButton = document.getElementById(target);
}
else
{
defaultButton = document.all[target];
}
if (defaultButton && typeof defaultButton.click != "undefined")
{
defaultButton.click();
event.cancelBubble = true;
if (event.stopPropagation)
{
event.stopPropagation();
}
return false;
}
}
return true;
}
So that is the fix and to put it all together, we just add a call to ClientScript.RegisterClientScriptInclude giving it the name and path to our newly fixed script:
protected void Page_Load(object sender, EventArgs e)
{
/*register fixed FireDefaultButton script*/
ClientScript.RegisterClientScriptInclude("FixFireDefault",
"<<PATH TO SCRIPT>>/FireDefaultButtonFix.js");
}
And that is it. When your page loads, your new script will be registered, taking the place of the original.
**(For a more detailed explanation of Web Resources, check out this Microsoft article.)
