Wednesday 29 April 2009

Server Side Click, CommandName and Command Argument on an ASP.Net TextBox

We had a requirement a work recently to provide support for a TextBox in an ASP.Net Web Forms application that posted back to the server when it was clicked on. We needed to support both the Click event style functionality and also the CommandName - CommandArgument functionality so that the TextBoxcould be included in a DataGrid and fire the DataGrid's ItemCommand event. It would not be necessary to read the value entered into the text box as it would be read only and be updated server side.

The solution I came up with was to try and find a control that supported this functionality and that the server would think was a control of that type, but would be rendered in the browser as a TextBox. The best matching control was LinkButton. LinkButton provided all the server side functionality that I required, and little more, so I decided that I would subclass that control. Now all I had to do was make ASP render the control as a text box.

Rather than do all the hard work myself, I thought that the easiest way to create the rendering code was to get the LinkButton to render itself to a dummy renderer, read in the string it rendered and then just change the bits I needed to. In order to get the control to look like a text box I have to change the element name to Input and add a new attribute type="text". In order to get the value of the LinkButton's Text property to display in the TextBox I had to add value="". Finally to make the events fire when the TextBox was clicked I had to assign the value of the href attribute to the TextBox's onclick attribute. In order to get the events to fire when the user tabbed into the control I also added it to the onfocus attribute.

In order to not render the data part of the LinkButton in the ASP.Net page I overrode the RenderContents method so that it did not do anything. Finally I removed all the re-useable code to another class so that I could use the technique again with other controls.

Here is the final code...
public abstract class CustomLinkButton : LinkButton
{
protected abstract string GetInputType();
protected abstract void WriteControlSpecificAttributes(HtmlTextWriter writer);

public override void RenderBeginTag(HtmlTextWriter writer)
{
var builder = new StringBuilder();
using (var dummyWriter = new HtmlTextWriter(new StringWriter(builder)))
{
base.RenderBeginTag(dummyWriter);
var attributes = GetAttributes(builder.ToString());
foreach (var kvp in attributes)
{
if (kvp.Key == "href")
{
writer.AddAttribute("onClick", kvp.Value);
WritePostBackAttributes(writer, kvp.Value);
}
else
{
writer.AddAttribute(kvp.Key, kvp.Value);
}
}
writer.AddAttribute("type", GetInputType());
writer.AddAttribute("readonly", "readonly");
WriteControlSpecificAttributes(writer);
writer.RenderBeginTag("input");
}
}

protected virtual void WritePostBackAttributes(HtmlTextWriter writer, string hrefValue)
{
// do nothing
}

protected override void RenderContents(HtmlTextWriter writer)
{
// don't write anything
}

private Dictionary GetAttributes(string tag)
{
using (var reader = new XmlTextReader(tag, XmlNodeType.Element, null))
{
var result = new Dictionary();
reader.Read();
if (reader.MoveToFirstAttribute())
{
do
{
result[reader.Name] = reader.Value;
}
while (reader.MoveToNextAttribute());
}
return result;
}
}
}

public class NewTextBox : CustomLinkButton
{
protected override string GetInputType()
{
return "text";
}

protected override void WriteControlSpecificAttributes(HtmlTextWriter writer)
{
writer.AddAttribute("value", Text);
}

protected override void WritePostBackAttributes(HtmlTextWriter writer, string hrefValue)
{
writer.AddAttribute("onfocus", hrefValue);
}
}

No comments:

Post a Comment