When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article
Jobs

ASP ASP.NET ASP FAQs Message Board Feedback ASP Jobs
 
Print this Page!
Published: Wednesday, January 25, 2006

Subtleties in Providing a Read-Only User Interface

By Scott Mitchell


Introduction


A common pattern in data-driven Web applications is that of the read-only user interface. Often a website contains a page that displays records from a database that, by default, the visitor can edit. However, there are times when the data is not editable, and needs to be displayed in a read-only interface. Deciding whether or not to display the user interface in read-only mode can be dependent on any number of factors. Perhaps only certain users can edit the data, while others can only view it, or perhaps the data can only be edited by the person who added the particular information, or maybe there's some sort of expiration date, after which the data can no longer be edited.

Regardless of why the data may or may not be read-only, there are two general techniques that can be used to implement such an interface on a single ASP.NET page. The prettiest, most intuitive way is to display textboxes, drop-down lists, checkboxes, and so on for editable data and plain-text for read-only data. This approach, of course, takes a little bit of extra work since for each input both an input Web control needs to be added to the page as well as a Label, and then the appropriate control needs to be shown or hidden based on whether or not the data is editable. Most developers opt for a somewhat simpler approach and use disabled or read-only textboxes, drop-down lists, and checkboxes. With this second approach, there's just one user interface, but the developer sets the Enabled or ReadOnly properties of the Web controls based on whether or not the data is editable.

When using the latter approach, there are certain subtleties that can creep up and lead to unexpected behavior due to the way browsers handle disabled form elements. In this article we'll discuss the differences between disabled and read-only controls and examine some of the subtleties that arise when creating read-only user interfaces, along with workarounds. Read on to learn more!

- continued -

A Common Read-Only User Interface Pattern


Each ASP.NET Web control contains an Enabled property that, if set to False, renders the control as disabled. A disabled control's HTML content contains the attribute disabled="disabled", which causes the browser to make the resulting element inactive. The actual appearance varies by browser, but typically the text is grayed out and the control does not respond to user interaction.

When creating an ASP.NET page that needs to sometimes be editable and other times read-only, many developers simply create the user interface for editing and then, if the data is read-only, programmatically set the Enabled property to False for all of those controls that constitute the data entry portion of the page. In my projects that use this pattern, I typically define a DisableControls(c) method in my library of common functions (perhaps in a custom base Page class) that disables the control c and all of the controls in the control hierarchy rooted at c. Such a DisableControls() method might look like:

'VB
Private Sub DisableControls(ByVal c As Control)
    If TypeOf c Is WebControl Then
        CType(c, WebControl).Enabled = False
    End If

    For Each child As Control In c.Controls
        DisableControls(child)
    Next
End Sub


// C#
private void DisableControls(Control c)
{
  if (c is WebControl)
    ((WebControl) c).Enabled = false;
  
  foreach(Control child in c.Controls)
    DisableControls(child);
}    

Then, from my ASP.NET web page's code-behind class I can call this method. To disable all controls in the Web Form I simply pass in the Web Form itself (form1, by default), but more often than not I want to only disable a certain subset of controls (the data entry-related ones), and have those in a Panel, which I can then pass in to this method.

In the ASP.NET Page_Load event handler, then, my code looks something like:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
   If Not Page.IsPostBack Then
      'Load in data...
      '     (Connect to database, query data, populate form fields...)
      
      'Determine if user interface should be displayed as editable or read-only...
      '... maybe depends on user, or data from database, or something else ...
      
      If DispalyAsReadOnly Then
         DisableControls(PanelControlThatContainsDataEntryControls)
      End If
   End If
End Sub

Subtleties with Disabled Form Fields


One subtlety of disabled form fields that many developers aren't aware of is that, according to the official spec, a browser is not supposed to postback the values of disabled form fields. If you check out the W3C specification on HTML forms you'll find that there are two classes of form fields: "successful" form fields and form fields that are not "successful." A "successful" form field is one that's "valid" for submission, meaning that the form field's name and value are sent back to the web server when the form is submitted.

This subtlety can raise problems if you need to receive back the values of the Web controls in order to have your page process correctly. This sort of need arises when creating a form that uses client-side script to disable certain form fields based on user action. For example, imagine that, by default, a form's elements are editable, but if the user checks a "Lock Values" checkbox, the behavior is to disabled all form fields on the page. Or perhaps if the user selects a particular item from a drop-down list, another form field on the page becomes moot, and to illustrate this you decide to disable the form field using client-side script in such a scenario.

Since the value of these form fields that have been disabled on the client-side are not posted back to the server, the ASP.NET page assumes that the value is what it was on the previous page visit (which is either the value specified declaratively, or whatever's stored in view state). In other words, a user's changes to the form field will be lost if the form field is marked as disabled on the client-side.

To illustrate this, consider the following example: an ASP.NET page that displays a particular customer's address, along with an ability to lock the record. If the visitor clicks the Lock checkbox, the form fields are disabled using client-side script. The problem is that if the user makes any changes to the form field values before checking the Lock checkbox, these changes are lost because the disabled form fields' values are not posted back to the web server. The download at the end of this article includes a demo illustrating this problem; you can also check out this live demo.

As the live demo shows, the following client-side JavaScript function can be used to toggle the disabled status of a control in the page:

<script language="javascript">
function toggleInputElementsDisabledStatus(id)
{
    // client-side script that toggles the specified
    // input control's disabled status
    var elem = document.getElementById(id);
    if (elem != null)
    {
        // toggle the disabled status
        elem.disabled = !elem.disabled;
    }
}
</script>

To toggle the disabled status of an HTML element on the page, simply call toggleInputElementsDisabledStatus(id), where id is the value of the HTML element's id attribute. In the live demo, this client-side function is called, passing in the ids of the three textboxes, whenever the "Lock" checkbox is clicked.

The problem illustrated by the live demo is due because disabled HTML elements do not postback their values on form submission. There are two ways to fix this: using client-side script to "faux" disable a control, and using client-side script to make disabled controls enabled immediately before submitting the form. Let's examine both of these potential workarounds.

Why Not Use the ReadOnly Attribute?
The <input> element in HTML has an additional readonly attribute that, if set, disallows the element to be edited and still posts back the values. Therefore, one might wonder why I bother with discussing the problems with disabled controls and don't just advocate using the readonly attribute.

The readonly attribute has a couple of negatives that prohibit using it in most scenarios. The main detrator is that the readonly attribute only applies to text and password elements (namely, <input type="text">, <input type="password">, and <textarea>) and does not apply to checkboxes, radio buttons, drop-down lists, or buttons. However, all of these elements can be made disabled. Therefore, unless your data entry form is just TextBox Web controls, the readonly attribute won't cut the mustard.

The ASP.NET TextBox Web control provides a ReadOnly property that, if set to True, will inject the readonly attribute into the TextBox's rendered HTML. However, there are some subtle changes in the server-side behavior of readonly TextBoxes in ASP.NET 2.0. A more thorough discussion on these changes and differences between the readonly attribute and disabled controls can be found at this blog entry.

Fixing the Disabled Form Fields Problem By Using a "Faux" Disabled State


Rather than actually disabling an HTML element, imagine if we could "fake" disable an element. Technically, the element would still be considered "successful," but would not allow the user to edit the contents, thereby maintaining the appearance of the control being disabled. To accomplish this, simply update the JavaScript toggleInputElementsDisabledStatus() function to use the following code:

<script language="javascript">
function toggleInputElementsDisabledStatus(id)
{
    // client-side script that toggles the specified
    // input control's disabled status
    var elem = document.getElementById(id);
    if (elem != null)
    {
        // toggle the disabled status
        if (elem.onfocus != null)
        {
            // there is a client-side focus event handler
            // assumption: this element is disabled, so enable it!                    
            elem.onfocus = null;
            elem.style.backgroundColor = 'white';
        }
        else
        {
            // assumption: this element is enabled, so disable it!
            elem.onfocus = function preventFocus(e) { this.blur(); };
            elem.style.backgroundColor = '#CCCCCC';
        }
    }
}
</script>

What this new JavaScript does is check to see whether the passed-in HTML element has an event handler specified for its client-side onfocus event. If it does, then we assume that the control is "faux disabled." To "enable" it we clear the element's onfocus event handler out and set the background color to white. If it does not have an event handler for its onfocus event, we assume that is is "enabled" and therefore make it "faux disabled" by setting the background color to gray and set its onfocus event handler to a function that immediately removes focus when the control receives focus.

This behavior mimics the functionality of disabled controls, but since the controls are really still enabled, they submit on postback, thereby eliminating the problem identified earlier.

Fixing the Disabled Form Fields Problem Using ASP.NET 2.0's SubmitDisabledControls Property


The second method for fixing the disabled form fields is to make all controls enabled right before submitting the form. While we could write our own client-side JavaScript to accomplish this, ASP.NET 2.0 relieves us of this burden by adding the SubmitDisabledControls property to Web Forms. By setting this property to True, the <form> element emitted to the page includes an onsubmit event handler that calls the WebForm_ReEnableControls() JavaScript function. This client-side function, which is provided by the ASP.NET engine, iterates through all of the form fields in the page and enables the disabled ones.

To set the SubmitDisabledControls property to True, simply add it to the <form runat="server"> tag in your ASP.NET web page, like so:

<form id="form1" runat="server" SubmitDisabledControls="True">
  ...
</form>

Keep in mind, this property is new to ASP.NET 2.0, and therefore won't produce the expected results in ASP.NET 1.x.

The live demo examined earlier, along with the two workarounds, are available for download at the end of this article.

Happy Programming!

  • By Scott Mitchell


    Attachments:


  • Download the complete source code


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article