Efran Cobisi's Blog

.NET wonders (and alike)

Panel's DefaultButton whims: not every IButtonControl is the same...

Today's story begins with a poor developer trying to obey his creative director new guidances in terms of how that web application should have its buttons rendered. I will omit the motivation behind this decision, because it is not relevant to this post's subject.
The creative director said: "Hey, I'd like them to be rendered as html anchors!". The poor developer replied saying: "I can't see any difference, what about leaving them as they are (html input buttons)?". And the other: "Well, we can apply CSS styles better to html anchors than to html input buttons!".
So, the poor developer started reflecting his creative director desires.
After some hours spent in playing nicely with the new html anchor based buttons, something hurt the poor developer heart: all the Panels which uses the DefaultButton property in the web application and previously worked well now started behaving in a strange manner. To his amazement, the poor developer discovered that all of the DefaultButtons now worked only in Internet Explorer. Oh my... What about that cross browser thing?

Let's help that poor developer.

Whenever you use the DefaultButton property of the Panel control, you can choose among a set of ready to use controls which could act as buttons, all of them implementing the IButtonControl interface. At runtime, the Panel control registers a client script which would fire that default button, upon the user has hitted the enter key inside one of the html element hosted inside that Panel.
The client function which handles the keypress event for the Panel client element is WebForm_FireDefaultButton. Here's its body:

var __defaultFired = false;
function WebForm_FireDefaultButton(event, target) {
    if (!__defaultFired && 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") {
            __defaultFired = true;
            defaultButton.click();
            event.cancelBubble = true;
            if (event.stopPropagation) event.stopPropagation();
            return false;
        }
    }
    return true;
}


As you could see, it checks (among other things) if the default button "click" property is defined before getting the work done. Unluckily, and this is the culprit of our poor developer pain, the "click" property is not supported by every IButtonControl rendered html element. In fact, html anchors seem to not support that property (yes, IE does support it, but we wanted it to be cross browser, do you remember?).
So, how could we solve this problem and make DefaultProperty works with... say... FireFox? One way could be to redefine WebForm_FireDefaultButton and make it smarter than the original one; we could check for the "click" property first and then for the "href" property, which html anchors have.

So, here's a little smarter reimplementation of the aforementioned client function, ready to be pasted in your web page or client script file:

function WebForm_FireDefaultButton(event, target) {
    if (!__defaultFired && event.keyCode == 13 && !(event.srcElement && (event.srcElement.tagName.toLowerCase() == "textarea"))) {
        var defaultButton;
        if (__nonMSDOMBrowser)
            defaultButton = document.getElementById(target);
        else
            defaultButton = document.all[target];

        if (typeof(defaultButton.click) != "undefined") {
            __defaultFired = true;
            defaultButton.click();
            event.cancelBubble = true;
            
            if (event.stopPropagation) event.stopPropagation();
            return false;
        }

        if (typeof(defaultButton.href) != "undefined") {
            __defaultFired = true;
            eval(defaultButton.href.substr(11));
            event.cancelBubble = true;
            
            if (event.stopPropagation) event.stopPropagation();
            return false;
        }

    }
    return true;
}

As all of the postback event references begin with the "javascript :" fake protocol, we could get the href of the DefaultButton, strip that protocol prefix and evaluate the rest. That will perform our craved postback, in a cross browser way.

And our poor developer is finally happy. Now guess who was that poor developer... :)
Posted: Mar 15 2007, 02:49 PM by efran.cobisi | with 6 comment(s) |
Filed under:

Comments

Lisa said:

HI , I had the same issue as you and search all over the web to find the solution. Thanks for you post, it was really helpful.

For amusement purposes, Here is the Microsoft answer "To resolve this issue, use a Button control instead of a LinkButton control or an ImageButton control when you want to assign the DefaultButton property in a Web application."

http://support.microsoft.com/kb/921277/en-us?spid=8940&sid=500

# May 28, 2007 1:29 AM

Peter said:

This is a pretty cool solution and just what I was looking for.  Just curious, though, how can you override the implementation of WebForm_FireDefaultButton which is apparently included through a webresource.axd link?  I tried putting the new implementation in my page and it doesn't get called (instead the one from the axd file gets called).

Thanks a bunch,

Peter  

# June 26, 2007 1:43 AM

efran.cobisi said:

I'm glad to see this post has been so useful, thanks.

Peter: You should make sure the script appears after the webresource.axd inclusion; this way it will override the original function implementation.

Hope this helps.

# June 26, 2007 3:25 AM

Paul Maxwell said:

Great solution, this help me to resolve half of the issue I was having with MasterPages, LinkButtons and DefaultButton behaviour (ASP .NET 2.0).

I've combined this with another posting to solve the other half and produced an override of the Panel control. (see Joteke's Blog: aspadvice.com/.../Lesson_3A00_-DefaultButton-property-of-HtmlForm-or-Panel.aspx )

This result is a Panel control where the DefaultButton property actually works with LinkButtons in Master pages in both IE and Firefox!!

I was having a problem on a basket page where pressing the enter key on a quantity input box caused a Search box in a master page to postback instead and I'm sure others will find this useful!

The embedded resource is a javascript file with an adapted version of the 'FireDefaultButton' function from above (the __defaultFired variable was causing a problem).

You have to mark the file as an embedded resource (file->properties->Set Build Action) and also add in int the AsemblyInfo.cs file.(see: aspnet.4guysfromrolla.com/.../080906-1.aspx )

e.g. [assembly: WebResource("Test.EmbeddedResources.DefaultButton.js", "text/javascript")]

In this example the embedded javascript file (DefaultButton.js) file was placed in a class library under the namespace Test.EmbeddedResources as I am using it with templated server controls

Modified javascript...

----------------------------------------

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 (typeof(defaultButton.click) != "undefined") {            

           defaultButton.click();

           event.cancelBubble = true;

           if (event.stopPropagation) event.stopPropagation();

           return false;

       }

       if (typeof(defaultButton.href) != "undefined") {            

           eval(defaultButton.href.substr(11));

           event.cancelBubble = true;

           if (event.stopPropagation) event.stopPropagation();

           return false;

       }

   }

   return true;

}

----------------------------------------

C# Class...

----------------------------------------

using System;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace Test.UtilityControls

{    

   /// <summary>

   /// A new Panel to overcome problems with setting default buttons

   /// on a standard panel when using Master Pages and/or composite

   /// controls with button child controls

   ///

   /// This also utilises a javascript override for the WebForm_FireDefaultButton

   /// call via the DefaultButton.js embedded resource to enable this to work

   /// with LinkButtons and non IE browsers

   ///

   /// The override is included below the default implementation with exactly the same function name

   /// and an override is achieved as the browser framework should always use the second implementation

   /// </summary>

   public class PanelWithDefault : Panel

   {

       protected override void OnPreRender(EventArgs e)

       {

           base.OnPreRender(e);

           if (DefaultButton.Length > 0)

           {

               //register the default button scrtipt with reflection                

               System.Reflection.MethodInfo minfo = typeof(Page).GetMethod("RegisterWebFormsScript", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);

               minfo.Invoke(Page, null);

           }

           // Register the client-side function using WebResource.axd

           if (Page != null &&

               !Page.ClientScript.IsClientScriptIncludeRegistered(GetType(), "DefaultButton"))

               Page.ClientScript.RegisterClientScriptInclude(GetType(), "DefaultButton",

                                                             Page.ClientScript.GetWebResourceUrl(GetType(),"Test.EmbeddedResources.DefaultButton.DefaultButton.js"));

       }

       protected override void AddAttributesToRender(HtmlTextWriter writer)

       {

           string defaultbutton = DefaultButton;

           DefaultButton = "";

           //Hack over the check in base method

           base.AddAttributesToRender(writer);

           if (defaultbutton.Length > 0)

           {

               Control ctrl = Page.FindControl(defaultbutton);

               if (ctrl != null && ctrl is IButtonControl)

               {

                   string text = "javascript:return WebForm_FireDefaultButton(event, '" + ctrl.ClientID + "')";

                   writer.AddAttribute("onkeypress", text);

               }

           }

       }

   }

}

# February 8, 2008 12:21 PM

Mendez said:

Hey Maxwell !

   Nice setup ... would you consider changing anything more, after reviewing the following:

forums.asp.net/.../1759690.aspx

/* This method is identical to the method in the MS javascript lib except that it adds a click function to anchors in non-IE browsers */

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];

   }

/* This is the only addition to this method, the rest is identical to MS version.*/

   if (defaultButton && typeof(defaultButton.click) == "undefined") {

     defaultButton.click = function() {

       var result = true;

       if (defaultButton.onclick)

         result = b.onclick();

       if (typeof(result) == "undefined" || result)

         eval(defaultButton.href);

     }

   }

   if (defaultButton && typeof(defaultButton.click) != "undefined") {

     defaultButton.click();

     event.cancelBubble = true;

     if (event.stopPropagation) event.stopPropagation();

     return false;

   }

 }

 return true;

}

# May 8, 2008 9:24 AM

Alan Singfield said:

I found I had to do this:

eval(unescape(defaultButton.href.substr(11)));

The defaultButton.href has "javascript:" at the start of it and contains %20 instead of space.

# October 15, 2008 10:14 AM
Leave a Comment

(required) 

(required) 

(optional)

(required)