views:

411

answers:

2

I have created a custom web control to act as a button with an image. I would like to be able to set this as the target of the DefaultButton parameter of an ASP.NET Panel control. I have implemented the IButton interface, and no error is generated when loading the page using the control. However, when I press enter in a textbox within that panel, the Click event of the control is not raised. When I replace my control with a standard ASP.NET button, everything works fine.

I have a test page with a panel containing a textbox, an instance of my custom button, and a standard asp.net button. The buttons are wired to an event handler which will change the textbox to the ID of the caller.

When DefaultButton of the panel is set to the ASP.NET button, hitting enter in the next box works correctly - the page posts back, and the text box is populated with the name of the standard button. When DefaultButton of the panel is set to my button, hitting enter in the textbox causes the page to postback, but the Click event is not fired. Clicking the button manually works correctly.

Does anyone know what I have to add to my custom control to make it handle the event coming from the Panel control? I'm looking using Reflector at the source code for Button, but cannot identify what is enabling this behaviour.

I have posted the source of the control below, and the relevant source of the test page.

Control Source:

public class NewButton : WebControl, IPostBackEventHandler, IButtonControl
{
    public NewButton() : base(HtmlTextWriterTag.A) { }

    public event EventHandler Click;

    public event CommandEventHandler Command;

    public string Text
    {
        get { return ViewState["Text"] as string; }
        set { ViewState["Text"] = value; }
    }

    public string ImageUrl
    {
        get { return ViewState["ImageUrl"] as string; }
        set { ViewState["ImageUrl"] = value; }
    }

    protected virtual void OnClick(EventArgs e)
    {
        if (Click != null)
            Click(this, e);
    }

    protected virtual void OnCommand(CommandEventArgs e)
    {
        if (Command != null)
            Command(this, e);
    }

    protected override void AddAttributesToRender(HtmlTextWriter writer)
    {
        base.AddAttributesToRender(writer);

        writer.AddAttribute(HtmlTextWriterAttribute.Class, "Button");
        writer.AddAttribute(HtmlTextWriterAttribute.Onclick, Page.ClientScript.GetPostBackEventReference(this, "Click"));
    }

    protected override void RenderContents(HtmlTextWriter writer)
    {
        // base.RenderContents(writer);

        writer.WriteFullBeginTag("span");
        writer.WriteFullBeginTag("span");

        if (!String.IsNullOrEmpty(ImageUrl))
        {
            writer.WriteBeginTag("img");
            writer.WriteAttribute("src", Page.ResolveClientUrl(ImageUrl));
            writer.Write(HtmlTextWriter.SelfClosingTagEnd);
            writer.Write(" ");
        }

        writer.WriteEncodedText(Text);
        writer.WriteEndTag("span");
        writer.WriteEndTag("span");
    }

    public void RaisePostBackEvent(string eventArgument)
    {
        if (this.CausesValidation)
        {
            Page.Validate(this.ValidationGroup);
        }
        OnClick(EventArgs.Empty);
        OnCommand(new CommandEventArgs(this.CommandName, this.CommandArgument));
    }

    public bool CausesValidation
    {
        get { return ViewState["CausesValidation"] as bool? ?? true; }
        set { ViewState["CausesValidation"] = value; }
    }

    public string CommandArgument
    {
        get { return ViewState["CommandArgument"] as string; }
        set { ViewState["CommandArgument"] = value; }
    }

    public string CommandName
    {
        get { return ViewState["CommandName"] as string; }
        set { ViewState["CommandName"] = value; }
    }

    public string PostBackUrl
    {
        get { return ViewState["PostBackUrl"] as string; }
        set { ViewState["PostBackUrl"] = value; }
    }

    public string ValidationGroup
    {
        get { return ViewState["ValidationGroup"] as string; }
        set { ViewState["ValidationGroup"] = value; }
    }
}

Code to call it:

<asp:Panel ID="Panel1" runat="server" CssClass="StandardForm" DefaultButton="NewButton1">
    <asp:TextBox runat="server" ID="text1" Columns="40" />
    <MyControls:NewButton ID="NewButton1" runat="server" Text="Test" OnClick="OnClick" />
    <asp:Button runat="server" ID="OldButton1" OnClick="OnClick" Text="Test" />
</asp:Panel>

Code Behind:

   protected void OnClick(object sender, EventArgs args)
    {
        text1.Text = "Click event received from " + ((WebControl) sender).ID;
    }

The control renders as follows:

<div id="ctl00_MainContent_Panel1" class="StandardForm" onkeypress="javascript:return WebForm_FireDefaultButton(event, 'ctl00_MainContent_NewButton1')">
    <input name="ctl00$MainContent$text1" type="text" size="40" id="ctl00_MainContent_text1" />
    <a id="ctl00_MainContent_NewButton1" class="Button" onclick="__doPostBack('ctl00$MainContent$NewButton1','')"><span><span>Test</span></span></a>
    <input type="button" name="ctl00$MainContent$OldButton1" value="Test" onclick="javascript:__doPostBack('ctl00$MainContent$OldButton1','')" id="ctl00_MainContent_OldButton1" />
</div>
+1  A: 

Hey,

View the client-side markup (view source in browser); this feature should render something like within the source:

javascript:return WebForm_FireDefaultButton(event, 'ct100$bodyplaceholder$newbutton1')";

in your page if this is setup correctly... If the second param doesn't correctly match your button's, it won't fire correctly. That can be for a few reasons:

  1. Do you render the client/unique ID during the rendering phase or call AddAttributesToRender. The ClientID proprety is needed as that is how it will link the client-side element to the feature, and it has to be rendered to the id attribute. This happens in the AddAttributesToRender method.
  2. If in that JS method, you see null, it's not able to get the client ID, often because that property is overridden.

HTH.

Brian
The javascript is in the markup rendered correctly:<div id="ctl00_MainContent_Panel1" class="StandardForm" onkeypress="javascript:return WebForm_FireDefaultButton(event, 'ctl00_MainContent_NewButton1')">The ID here matches the ID of my button, so it's not this.
Richard
What does your button markup look like? Could you post that too?
Brian
I've updated the original question with the render output
Richard
+1  A: 

The way the WebForm_FireDefaultButton function works is that it is explicitly looking for a click() event on the element in question. Note that I am referring to the ability to call element.click(), not the OnClick attribute. The browser will link this up automatically for certain elements, but not links which is how your control is rendering. You'll notice the same behavior if you use a LinkButton. There are a couple of different ways around this. You can override the WebForm_FireDefaultButton javascript function as in this post:

http://stackoverflow.com/questions/938957/link-button-on-the-page-and-set-it-as-default-button-work-fine-in-ie-but-not-in

Or you can add some code to your control to properly hook up the client side click function, maybe something like this...though this is off the top of my head:

protected override void OnPreRender(EventArgs e)
{
    string clickString = string.Format("document.getElementById('{0}').click = function() {{ {1} }};", this.ClientID, Page.ClientScript.GetPostBackEventReference(this, "Click"));

    Page.ClientScript.RegisterStartupScript(this.GetType(), "click_hookup_" + this.ClientID, clickString, true);
}
Zach Parrish
I was looking at the code of FireDefaultButton yesterday and had noticed that it was looking for .click(), rather than onclick. My methods of trying to use this hadn't worked though - your second solution works with no problems. I would rather use this than override the WebForm_FireDefaultButton function.
Richard