This article demonstrates an easy method to access the data of templated controls (
DataGrid) from client-side scripts. This will allow us to use this data in client-side operations such as calculation and validation.
Templated controls are a special type of data-bound server controls that offer great flexibility for rendering list-like data. At design-time, a templated control enables you to use a combination of HTML and server controls to design a custom layout for the fields of a single list item. And at run-time, it binds to the data source, walks through the bound items, and produces graphical elements according to the template you designed.
.NET framework 1.0/1.1 ships with three templated controls, the
DataList, and the
DataGrid. You can also build your own templated control by implementing the
A challenge you may encounter when using templated controls is how to access the data in the generated child controls from client-side script, because the client-side does not understand templates, it only understands the names of individual controls, and it’s hard to guess what the name of each of the child controls will be at run-time.
I’ll use a simple example to explain my idea. Let’s assume we have a database table that stores employee expenses over a certain period. The table has the following schema:
We want to allow the employee to enter the amount he spent on each expense type using a
DataGrid control. As the employee updates the amount of each expense, it is required to display the total amount he entered, and to validate that this total does not exceed 1000.
These requirements are easy to achieve if we decide to make the updates row by row. This means that as the employee updates each row, the form has to make post-backs to the server to calculate the new total, and apply the validation rule.
The client-side approach
My proposed approach has several benefits, as it conserves the server and network resources, and provides a more interactive user interface.
DataGrid used has the following structure:
<asp:datagrid id="dgExpenses" runat="server" AutoGenerateColumns="False">
<asp:BoundColumn DataField="ExpenseType" HeaderText="Expense Type">
<asp:TemplateColumn HeaderText="Expense Amount">
<asp:TextBox Text='<%# DataBinder.Eval(Container.DataItem,"ExpenseAmount")%>'
The code that populates the
DataGrid is straightforward:
SqlConnection conn = new
SqlCommand cmd = new SqlCommand("select * from timesheet", conn);
SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
dgExpenses.DataSource = rdr;
To expose the data to the client-side script, I use a hidden field to store the initial values of the
ExpenseAmount field as a string of comma-separated values.
hdnRowArr.Value = "";
foreach(DataGridItem dgi in dgExpenses.Items)
if(dgi.ItemType == ListItemType.Item ||
dgi.ItemType == ListItemType.AlternatingItem)
expAmount = ((TextBox)dgi.Cells.Controls).Text;
hdnRowArr.Value += expAmount + ",";
hdnRowArr.Value = hdnRowArr.Value.TrimEnd(',');
In the client-side, I split the comma-separated values into an array. Using an array makes it easier to update the values when the user makes a change. It also simplifies calculating the total. I use the following function to fill the array. Then I calculate the initial total to update the UI. This function is invoked in the Body
var commaSeparated = document.getElementById("hdnRowArr").value;
arrRows = commaSeparated.split(",");
Now we need a way to detect any changes made by the user, update the array, and re-calculate the total. I add the following code to the
if(e.Item.ItemType == ListItemType.Item ||
e.Item.ItemType == ListItemType.AlternatingItem)
int rowIndex = e.Item.ItemIndex;
TextBox txtExpAmount = (TextBox)e.Item.Cells.Controls;
string evtHandler = "updateValue(this," + rowIndex.ToString() + ")";
This code will add an event handler for the
onblur event of each
TextBox in the
ExpenseAmount template column. The event will be handled by the
updateValue client-side function. I pass to the function a reference to the child control, and the index of the row that contains the control. The index is used to locate the array element that corresponds to the
function updateValue(field, rowNum)
arrRows[rowNum] = field.value;
Calculating the total and updating the UI is done by this function:
var sum = 0;
for(var i = 0;i < arrRows.length; i++)
expAmount = parseFloat(arrRows[i]);
sum += expAmount;
document.getElementById("txtTotalExp").value = sum;
Finally, we can associate any validator with the
TextBox. We have to set the "
ReadOnly" property of the
TextBox to "
true" to prevent the user from updating the total manually. So, now with every change to the values of the
TextBoxes, the total will be automatically re-calculated.
Using the code
In order to apply this method for any templated control, you can follow these steps for each column you want to access from the client-side:
- Add a hidden field, and fill it with the initial data that are retrieved from the data source. Pass the data to the field as comma-separated values.
- Add an
Array variable, and the corresponding
sumArray function with another function to perform any other operation than calculating the total.
- In the Body
OnLoad event, add a call to the
- Use the
ItemDataBound event of the templated control to add a call to the
updateValue function to handle the child control client-side event.
- Add a
Label or a
TextBox to display the result of the client-side operation. You can then add a suitable validator and associate it with the
Points of interest
- The client-side approach can be extended to address a wide range of templated control validation requirements. For example, if you have a
DataGrid that has a template column with a
CheckBox (shown in picture), and you want to put a limit on the maximum or minimum number of
CheckBoxes that can be checked, it can easily be implemented by applying the same concept introduced here. This example is included in the sample code.
- It is important to note that the goal of having the validation or any other operation done on the client-side is not to move a part of the data processing from the server to the client, but rather to avoid the repetitive round trips to the server while the user is entering the data. So in the example I used here, when the user decides to submit the data he entered, I should have the server re-calculate and validate the total, and not rely on the result calculated on the client-side, as this could be tampered with by a malicious user.