Enhancing the ASP.NET DetailsView Control

Build Made-to-measure Record-based Views

Dino Esposito

October 30, 2009

10 Min Read
ITPro Today logo in a gray background | ITPro Today

It s fairly common for Web applications to work on asingle record at a time. For example, displaying the contents of a singlerecord of data is a necessary practice when you build master/detail views. InASP.NET 2.0, the DetailsView control renders a single record at a time from itsassociated data source, optionally providing paging buttons to navigate betweenrecords and Create/Read/Update/Delete (CRUD) capabilities. You can customizethe user interface of the DetailsView control to a large extent. You can choosethe most appropriate combination of data fields to render each piece ofinformation conveniently. The default set of data fields includes templated,image checkboxes, and hyperlink fields. For more information on the DetailsView control, see "Getting Under the DetailViews Control."

The DetailsView control is not limited to exposing itsdata to users in an appropriate and friendly way. The control also providesinteractive buttons to navigate data and edit and insert new records. Thesefeatures are provided out-of-the-box and are not configurable by programmers.Or, at least, not declaratively and, maybe, not easily. In this column, I llfirst answer a specific question posed by a reader, then move on to putting thesolution in perspective and extending other features of the DetailsViewcontrol.

 

The Problem

Let s say you need a Web page that collects data andpopulates a database. By using the DetailsView control you save yourself abunch of user interface stuff. The control provides a free table-basedinterface with one column for labels and one column for input fields. You setthe working mode of the DetailsView to Insert and go:

 

Figure 1 shows the output you get. Filling out the formand inserting a record into the database works well. Next, you can use aGridView control on another, or the same, page to retrieve any just-insertedrecords. As you can see in Figure 1, input fields are empty and don t suggestany values to users. As the reader pointed out, this is going to be a problemif you have to enter the same subset of data for, say, fifteen records. Lookingat Figure 1, wouldn t it be reasonable to automatically set the title fieldwith Mr. or whatever else is the most likely value? Especially for pages usedfor data entry, this little trick might greatly improve the user s experience.


Figure 1: The DetailsView controlworking in insert mode.

The DetailsView control lacks a property to indicate thedefault value of an input field. So how would you do that? You might think thatthe DetailsView control fires an event that, properly handled, gives you achance to set the default value of input fields. This is not entirely correct.More precisely, the DetailsView control doesn t fire a specific event thatwould let you do so. However, in the long list of events fired by the DetailsViewcontrol, there s one that you can leverage to implement a little piece of logicto set default input values.

 

The ItemCreated Event

The DetailsView control exposes several events that enablethe developer to execute custom code at various times in the lifecycle. Thetable in Figure 2 details the supported events.

Event

Description

ItemCommand

Occurs when any buttons in the user interface are clicked.

ItemCreated

Occurs whenever a new DetailsView item is created.

ItemDeleting, ItemDeleted

Events fire before and after the record is deleted, respectively.

ItemInserting, ItemInserted

Events fire before and after the record is inserted, respectively.

ItemUpdating, ItemUpdated

Events fire before and after the record is updated, respectively.

ModeChanging, ModeChanged

Events fire before and after the working mode of the control changes, respectively.

PageIndexChanging, PageIndexChanged

Events fire before and after a new record is displayed, respectively.

Figure 2: Eventsof the DetailsView control.

It is interesting to note that the ItemCommand event doesn tfire if the user clicks on the Edit or Insert buttons or any other standardbuttons. Clicking these buttons is handled internally to ensure the expectedbehavior. So the ItemCommand event, although promising, is not the righthandle. Reading through Figure 2, it s easy to see that another event can betaken into account the ItemCreated event.

The ItemCreated event is fired immediately after aDetailsView item is created. But what s a DetailsView item? They are header,footer, command row, and page bar. All data rows are considered a single item.In other words, you won t receive an ItemCreated event for each field of thedisplayed record, but only one when all labels and input fields have beencreated. Let s consider the following code snippet:

protected void DetailsView1_ItemCreated(object sender, EventArgs e){  if (DetailsView1.FooterRow == null)      return;   :}

The FooterRow property returns an object that representsthe footer of the DetailsView. The footer is the item that gets generated afterall data rows. When FooterRow is not null, you can be sure that all data rowsexist and, as such, can be set to custom values. To set default values onlywhen in insert mode, you modify the preceding code as follows:

protected void DetailsView1_ItemCreated(object sender, EventArgs e){   if (DetailsView1.FooterRow == null)       return;   if (DetailsView1.CurrentMode == DetailsViewMode.Insert)   {       SetDefaultValue(3, "USA");       SetDefaultValue(4, "Mr.");   }   :}

How can you retrieve the control instance of the textboxesyou see in Figure 1? That s a bit tricky. To start off, you must know the indexof the field you want to set. In Figure 1, the title field is the fifth row inthe table and has an index (0-based) of 4. The Rows property of the DetailsViewcontrol returns a collection of DetailsViewRow objects, each of whichrepresents a data row in the control. A data row maintains a collection ofcells at least two (for the label and the input control). Each cell isrepresented by an object of type DataControlFieldCell. Let s consider thefollowing code:

protected void SetDefaultValue(int rowIndex, string msg){   const int DataCellIndex = 1;   DetailsViewRow row = DetailsView1.Rows[rowIndex];   DataControlFieldCell cell =    (DataControlFieldCell)row.Cells[DataCellIndex];   TextBox txt = (cell.Controls[0] as TextBox);   if (txt != null)       txt.Text = msg;}

The field cell is simply the container of any control usedto render the user interface for the input, be it a textbox, a checkbox, orperhaps a calendar. To find the textbox, or to add a validator on the fly, youneed to access the Controls collection of the cell. The effect of the SetDefaultValuemethod is shown in Figure 3.


Figure 3: Predefined values show upin DetailsView when in insert mode.

 

Persisting Values

In the sample code presented a moment ago, the value foreach initialized field is fixed. According to the code snippet, the countryfield, for instance, will always be set to USA.What if you want to persist the last typed data until the user changes it? Thismight be a valuable feature that obviates retyping and saves time especiallyin scenarios where many records must be entered.

In Figure 2 you see a pair of pre/post events for theinsertion task. The ItemInserted event handler is invoked immediately after therecord has been processed by the bound data source object. This is a great timeto persist any inserted data that you want to reiterate. Where should youpersist it? It s data specific to the ongoing session; hence, it should go tothe session state.

Figure 4 shows the updated code; Figure 5 demonstrates thefeature in action. Once you add a record where country is, say, Italy ,you ll retrieve that value as the default value for the country field for the nextrecords, as well.

protected void SetDefaultValue(int rowIndex, string defaultText){   const int DataCellIndex = 1;   DetailsViewRow row = DetailsView1.Rows[rowIndex];   DataControlFieldCell cell =    (DataControlFieldCell)row.Cells[DataCellIndex];   TextBox txt = (cell.Controls[0] as TextBox);   if (txt != null)   {       string key = GetDataEntryKey(rowIndex);       object o = Session[key];       if (o == null)           txt.Text = defaultText;       else           txt.Text = (string) o;   }}protected void DetailsView1_ItemInserted(object sender, DetailsViewInsertedEventArgs e){   string key = "";   string title = (string) e.Values["title"];   key = GetDataEntryKey(3);   Session[key] = title;   string country = (string) e.Values["country"];   key = GetDataEntryKey(4);   Session[key] = country;}private string GetDataEntryKey(int rowIndex){   return String.Format("DataEntry_{0}", rowIndex);}

Figure 4: Persistinginserted data to reiterate in subsequent insertions.


Figure 5: The user enters a newrecord and some of its data are cached for subsequent entries.

In Figure 4 I use a custom schema to name the key ofsession state slots used to persist data. Obviously, this schema is totallyarbitrary and may be changed in your own implementation. I just found it easierif a hint remains in the key name that points to the index of the field. Notethat in the handler of the ItemInserted event you retrieve the entered valuethrough the Values collection of event data structure theDetailsViewInsertedEventArgs class. The collection returns objects whose realtypes depend on the input field used. It would be a date, for example, if youhave a calendar rather than a textbox.

 

Further Enhancements

The DetailsView control lends itself very well to arrangequick data entry pages. Like most semi-automatic tools, though, it may lack keyfeatures, such as validation and confirmation. To take full control of theediting process you can use templated fields or perhaps opt for the much moreflexible and fully customizable FormView control. A further nice touch to thepiece of code we crafted thus far would be adding a client, JavaScript-poweredconfirm message box before critical operations start. For example, you mightwant to request confirmation before deleting a record or saving changes. To doso, you must walk your way through the DetailsView control tree and locate theinstance of the button control that triggers the delete or save operation. Youextend the ItemCreated handler as shown in Figure 6.

protected void DetailsView1_ItemCreated(object sender, EventArgs e){   if (DetailsView1.FooterRow == null)       return;   if (DetailsView1.CurrentMode == DetailsViewMode.Insert)   {       SetDefaultValue(3, "USA");       SetDefaultValue(4, "Mr.");   }   if (DetailsView1.CurrentMode == DetailsViewMode.ReadOnly)       AddConfirmToDeleteButton();   if (DetailsView1.CurrentMode != DetailsViewMode.ReadOnly)       AddConfirmToUpdateButton();}

Figure 6: Extendingthe ItemCreated handler.

The Delete button shows up in read-only mode, whereas theInsert and Update buttons appear only when you re in Insert and Edit mode,respectively. The helper methods are detailed in Figure 7.

protected void AddConfirmToDeleteButton(){   int commandRowIndex = DetailsView1.Rows.Count - 1;   DetailsViewRow commandRow =     DetailsView1.Rows[commandRowIndex];   DataControlFieldCell cell =     (DataControlFieldCell)commandRow.Controls[0];   foreach (Control ctl in cell.Controls)   {       LinkButton link = ctl as LinkButton;       if (link != null)       {           if (link.CommandName == "Delete")           {               link.ToolTip = "Click here to delete";               link.OnClientClick = "return confirm('Do                you really want to delete this record?');";           }           else if (link.CommandName == "New")               link.ToolTip = "Click here to add                a new record";           else if (link.CommandName == "Edit")               link.ToolTip = "Click here to edit                 the current record";       }   }}protected void AddConfirmToUpdateButton(){   int commandRowIndex = DetailsView1.Rows.Count - 1;   DetailsViewRow commandRow =     DetailsView1.Rows[commandRowIndex];   DataControlFieldCell cell =     (DataControlFieldCell)commandRow.Controls[0];   foreach (Control ctl in cell.Controls)   {       LinkButton link = ctl as LinkButton;       if (link != null)       {           if (link.CommandName == "Update" ||             link.CommandName == "Insert")           {               link.ToolTip = "Click here to save changes";               link.OnClientClick = "return confirm('Do you                 really want to save changes?');";           }           else if (link.CommandName == "Cancel")               link.ToolTip = "Click here to                 cancel editing";       }   }}

Figure 7: Addingclient message boxes to the DetailsView.

The command row is the last row in the Rows collection andcontains exactly one cell. You walk your way through the child controls of thecell until you find link buttons. You recognize link buttons through theCommandName property and add a tooltip and client click handler on the fly.

 

Conclusion

In ASP.NET 2.0, the developer s toolbox for databindingoperations is definitely rich and complete. Now you have a new, and radicallyrevised, grid control, as well as two controls to manage views of a singlerecord: DetailsView and FormView. In this article, I explored the internalstructure of a DetailsView control and provided a working solution to a coupleof real-world issues.

The source code forthis article is available for download.

 

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like