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!
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: