Monday, May 28, 2007

From time to time I have to create a form that is used both for display and input of data (or in other words for viewing or adding/editing). As an example, imagine a form for entering customer data. The form will include fields such as Name, Address etc. There are few possible solutions to this problem.

First and most easy to implement is to create a single form, put required input controls on it: one TextBox for Name and one for Address. When constructing the page on the server, we have to check if the form is used  in View mode in which case we have to set the controls' ReadOnly property to true. Otherwise we can leave it as false. ASP.NET controls mostly support this kind of mechanism. The problem is that a ReadOnly TextBox is still rendered as HTML input control. You can try to hide the fact by using CSS styles, but underneath it is still an input control for a form that does not require one.

Another solution I have seen used a lot is to create a separate page used only to display the date and another page for editing/adding data. In this case, the page used to display data uses controls such as Label control so no input field is generated. The other page works as can be expected. The problem with this solution is that now we have two pages and some of the logic/layout/whatever is duplicated.

ASP.NET architecture to the rescue! Since ASP.NET is designed in such a way that it makes heavy use of controls, we can take advantage of the fact and introduce a special kind of TextBox control that is intelligent in a way it renders itself.

The way to achieve this goal is simple. Create a new Class, make it inherit from the TextBox control and override few methods:

public class TextBox : System.Web.UI.WebControls.TextBox
{
    [DefaultValue(false)]
    public bool RenderReadOnlyAsText
    {
        get { return (bool)(ViewState["RenderReadOnlyAsText"] ?? false); }
        set { ViewState["RenderReadOnlyAsText"] = value; }
    }
    protected override void AddAttributesToRender(System.Web.UI.HtmlTextWriter writer)
    {
        if ((!RenderReadOnlyAsText) || (!ReadOnly))
        {
            base.AddAttributesToRender(writer);
        }
    }
    protected override void Render(System.Web.UI.HtmlTextWriter writer)
    {
        if ((RenderReadOnlyAsText) && (ReadOnly))
        {
            System.Web.UI.WebControls.WebControl span = 
            new System.Web.UI.WebControls.WebControl(System.Web.UI.HtmlTextWriterTag.Span); span.RenderBeginTag(writer); writer.Write(Text.Replace(Environment.NewLine, "<br />")); span.RenderEndTag(writer); } else { base.Render(writer); } } }

Notice the RenderReadOnlyAsText property which is false by default making the new control behave just like a normal TextBox. Next we need to override the Render method. I have used span control to replace the normal input control, but feel free to experiment. The thing to remember is to replace the NewLine characters with HTML line breaks (probably some CSS style would also do). Additionally I have overridden the AddAttributesToRender method so that it does not add any attribute to the control in case it is rendered as a plain text (or span in my case).

Using this control on a page is simple. Just set the RenderReadOnlyAsText to true and set ReadOnly property to true when necessary.

kick it on DotNetKicks.com