Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Accessing the data of ASP.NET templated controls from client-side script

0.00/5 (No votes)
17 Jul 2005 1  
The article demonstrates an easy method to access the data of templated controls (Repeater, DataList, DataGrid) from client-side scripts.

Sample Image - ControlClientScript.jpg

Introduction

This article demonstrates an easy method to access the data of templated controls (Repeater, DataList, DataGrid) from client-side scripts. This will allow us to use this data in client-side operations such as calculation and validation.

Background

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 Repeater, the DataList, and the DataGrid. You can also build your own templated control by implementing the INamingContainer interface.

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.

The problem

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:

ID int
ExpenseType varchar
ExpenseAmount decimal

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.

The DataGrid used has the following structure:

<asp:datagrid id="dgExpenses" runat="server" AutoGenerateColumns="False">
<Columns>
  <asp:BoundColumn DataField="ExpenseType" HeaderText="Expense Type">
  </asp:BoundColumn>
  <asp:TemplateColumn HeaderText="Expense Amount">
   <ItemTemplate>
    <asp:TextBox Text='<%# DataBinder.Eval(Container.DataItem,"ExpenseAmount")%>' 
    Runat="server" ID="txtExpAmount"></asp:TextBox>
   </ItemTemplate>
  </asp:TemplateColumn>
 </Columns>
</asp:datagrid>

The code that populates the DataGrid is straightforward:

SqlConnection conn = new 
     SqlConnection("server=localhost;database=pubs;uid=sa;pwd=");
SqlCommand cmd = new SqlCommand("select * from timesheet", conn);
conn.Open();
SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
dgExpenses.DataSource = rdr;
dgExpenses.DataBind();
rdr.Close();

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 = "";
string expAmount;
foreach(DataGridItem dgi in dgExpenses.Items)
{
    if(dgi.ItemType == ListItemType.Item || 
       dgi.ItemType == ListItemType.AlternatingItem)
    {
        expAmount = ((TextBox)dgi.Cells[1].Controls[1]).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 OnLoad event.

function refreshArray()
{
    var commaSeparated = document.getElementById("hdnRowArr").value;
    arrRows = commaSeparated.split(",");
    sumArray();
}

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 DataGrid ItemDataBound event.

if(e.Item.ItemType == ListItemType.Item || 
     e.Item.ItemType == ListItemType.AlternatingItem)
{
    int rowIndex = e.Item.ItemIndex;
    TextBox txtExpAmount = (TextBox)e.Item.Cells[1].Controls[1];
    string evtHandler = "updateValue(this," + rowIndex.ToString() + ")";
    txtExpAmount.Attributes.Add("onblur", evtHandler);
}

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 DataGrid row.

function updateValue(field, rowNum)
{
    arrRows[rowNum] = field.value;
    sumArray();
}

Calculating the total and updating the UI is done by this function:

function sumArray()
{
    var sum = 0;
    var expAmount;
    for(var i = 0;i < arrRows.length; i++)
    {
        expAmount = parseFloat(arrRows[i]);
        if(!isNaN(expAmount))
            sum += expAmount;
    }
    document.getElementById("txtTotalExp").value = sum;
}

Finally, we can associate any validator with the txtTotalExp 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 ExpenseAmount 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:

  1. 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.
  2. Add an Array variable, and the corresponding refreshArray, updateValue, and sumArray JavaScript functions to the form. You can replace the sumArray function with another function to perform any other operation than calculating the total.
  3. In the Body OnLoad event, add a call to the refreshArray function.
  4. Use the ItemDataBound event of the templated control to add a call to the updateValue function to handle the child control client-side event.
  5. 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 TextBox.

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.

    Sample screenshot

  • 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here