Walkthrough: Making Data-Bound Controls Easier to Access from JavaScript
This walkthrough shows you how to access data-bound controls in client script. In the walkthrough, you will create a Web page that displays a list of product names and prices from the Products table in the AdventureWorksLT sample database. You will add client script that periodically calls a Web service to retrieve and update any prices that have changed. You will then update the page to show the new information without refreshing the whole page.
The product information will be displayed in Label controls in a ListView control. In HTML, Label controls are rendered as span elements that have id attributes. When you are using client script, the easiest way to reference the span elements and change the text in them is to use the document.getElementById method. To use this method, you must know what the id attribute values of the span elements will be.
To cause ASP.NET to generate id values that you can predict, you will set the ClientIDMode and ClientIDRowSuffix properties of the ListView control so that ASP.NET will generate id attribute values that include the product ID. Because the Web service returns ProductID values with the changed prices, and because the ProductID is in the id attributes, you can easily find the span element that you have to change for individual products.
Prerequisites
In order to run this walkthrough, you must have the following:
Visual Studio 2010 installed on your computer.
An ASP.NET Web site.
The AdventureWorksLT sample database. For information about how to install AdventureWorksLT and how to add it to a Web site in Visual Studio, see How to: Set Up an AdventureWorksLT Sample Database for ASP.NET Development.
Creating a Web Page That Generates Predictable Client Identifiers
In this section of the walkthrough, you create a page that contains a data-bound control that you will access later by using client script. To make the instances of the control easy to access in client script, you can set the ClientIDMode property of the data-bound control so that the control creates predictable id attributes.
To create a Web page that generates predictable client identifiers
In Visual Studio, create a new Web page named ProductList.aspx.
In Solution Explorer, right-click the new page and then click Set as Start Page.
Switch to Design view.
From the Toolbox, drag a SqlDataSource control onto the Web page.
Configure the SqlDataSource control to connect to the AdventureWorksLT database and to use a custom SQL statement that selects the ProductID, Name, and ListPrice columns for all rows of the Product table.
Switch to Source view.
The markup for the control will resemble the following example:
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:AdventureWorksLTConnectionString %>" SelectCommand="SELECT ProductID, Name, ListPrice FROM SalesLT.Product"> </asp:SqlDataSource>
Add the following markup between the opening and closing <div> tags
<table> <tr> <th>Product ID</th> <th>Product Name</th> <th>List Price</th> </tr> <asp:ListView ID="ListView1" runat="server" DataSourceID="SqlDataSource1" ClientIDMode="Predictable" ClientIDRowSuffix="ProductID"> <ItemTemplate> <tr> <td><%# Eval("ProductID") %></td> <td><%# Eval("Name") %></td> <td align="right"> <asp:Label ID="PriceLabel" runat="server" Text='<%# Eval("ListPrice","{0:#.00}") %>' /> </td> </tr> </ItemTemplate> <LayoutTemplate> <div id="itemPlaceholderContainer" runat="server"> <span id="itemPlaceholder" runat="server" /> </div> </LayoutTemplate> </asp:ListView> </table>
This markup uses a ListView control to display a table with columns named Product ID, Product Name, and List Price. Prices are formatted as currency amounts.
In the markup, the ClientIDMode property of the ListView control is set to Predictable and the ClientIDRowSuffix property is set to ProductID. The id attributes for the rendered HTML span elements that contain the list price will be ListView1_PriceLabel_number where number is the ProductID. You will use this id pattern in a later step to update individual prices that have changed.
You can set ClientIDRowSuffix to the name of any data field. However, if you select a field that contains values that are not unique for each row, the rendered id attributes in HTML might not be unique and you will not be able to get a reference to a specific control by using the document.getElementById method. In addition, the data in the field must be usable in id attributes without change. For example, if you select a field that has embedded spaces, ASP.NET will generate invalid id attributes.
Save the changes and then press CTRL+F5 to view the page in a browser.
In the browser, view the source code for the displayed Web page.
Notice that the Label controls have been created as span elements. The id attributes have names such as ListView1_PriceLabel_680, where the number at the end of the id is the ProductID for the record.
Close the browser.
Adding a Web Service That Can Be Called by Using AJAX
To make the current prices for the products available to client script, you create a LINQ to SQL class for the Product table and an AJAX-enabled Web service that returns information about changed prices.
There are many ways to make data available to a Web service. LINQ to SQL is used in this walkthrough because it is relatively easy to use. For information about alternatives, see Accessing Data in Visual Studio.
To make the Product table available to the Web service
If the Web site does not have an App_Code folder, right-click the project in Solution Explorer, click Add ASP.NET Folder, and then click App_Code.
In Solution Explorer, right-click the App_Code folder and then click Add New Item.
From the installed templates, select LINQ to SQL Classes.
Enter AdventureWorks.dbml in the Name field and then click Add.
The Object Relational Designer window is displayed.
In Server Explorer, expand the Tables node for the AdventureWorksLT database.
Drag the Product (SalesLT) table to the panel of the Object Relational Designer design surface that is for creating data classes.
In the Object Relational Designer window, delete all properties of the Product class that you just created except for ProductID and ListPrice.
Close the Object Relational Designer window and save the changes to AdventureWorks.dbml.
After you have made the Product table available for Web-service access, you can create the Web service itself. For this walkthrough, you create a Web service that can be called directly from client script by using AJAX.
To create a Web service that can be called using AJAX
In Solution Explorer, right-click the Web site and then click Add New Item.
Under Installed templates, select Web Service.
Name the service PriceWebService.asmx.
Visual Studio creates the PriceWebService.vb or PriceWebService.cs file in the App_Code folder and opens it.
Uncomment the System.Web.Script.Services.ScriptService attribute.
This makes the Web service available for AJAX calls.
Replace the HelloWorld method with the following code:
<WebMethod()> _ Public Function GetPrices() As Product() Dim context As New AdventureWorksDataContext() Dim productsToReturn As New List(Of Product) Dim randomNumber = New Random() For Each p As Product In context.Products If p.ProductID Mod 10 = randomNumber.Next(10) Then p.ListPrice = p.ListPrice + 1000 productsToReturn.Add(p) End If Next Return productsToReturn.ToArray End Function
[WebMethod] public Product[] GetPrices() { AdventureWorksDataContext context = new AdventureWorksDataContext(); List<Product> productsToReturn = new List<Product>(); Random randomNumber = new Random(); foreach (Product p in context.Products) { if (p.ProductID % 10 == randomNumber.Next(10)) { p.ListPrice += 1000; productsToReturn.Add(p); } } return productsToReturn.ToArray<Product>(); }
This code simulates a query that returns only products whose prices have changed since the last time the query was run. It does this by randomly selecting one tenth of the rows in the table, adding 1000 to the price of each row selected, and returning the modified rows to the calling program.
Close and save PriceWebService.vb or PriceWebService.cs.
Making the Web Service Accessible to the Web Page
In the Web page, Visual studio must create a proxy for the Web service so that you can call it from client script. To facilitate this, you add a ScriptManager control to the page and specify the path to the Web service that you created in the previous procedure.
To make the Web service available to client script in the Web page
In Solution Explorer, open ProductList.aspx.
Switch to Design view.
In the Toolbox, expand the Ajax Extensions tab and drag a ScriptManager control to the Web page.
Right-click the ScriptManager control and then click Properties.
In the Properties window, expand the Services collection to open the ServiceReference Collection Editor window.
Click Add, specify PriceWebService.asmx as the Path referenced, and then click OK.
Switch to Source view.
The markup that was added for the ScriptManager control resembles the following example:
<asp:ScriptManager ID="ScriptManager1" runat="server"> <Services> <asp:servicereference Path="PriceWebService.asmx" /> </Services> </asp:ScriptManager>
Adding Client Script to Update Product Data
To update the Web page with data from the Web service, you add client script that calls the service and updates the appropriate controls.
To call the WCF service and update changed prices
With ProductList.aspx still open in Source view, add the following client-script code before the closing </head> tag:
<script language="javascript" type="text/javascript"> //Call the web service to get changed prices. function UpdatePrices() { var pricesService = new PriceWebService(); pricesService.GetPrices(onSuccess, onFailure, null); setTimeout('UpdatePrices()', 5000); } //Update the web page after a successful web service call. function onSuccess(result) { for (var i = 0; i < result.length; i++) { $get("ListView1_PriceLabel_" + result[i].ProductID).innerHTML = "<b>" + result[i].ListPrice.toFixed(2) + "</b>"; } } function onFailure(results) { alert('failed'); } </script>
The UpdatePrices method calls the Web service asynchronously and sets the onSuccess method as the callback method if the call is successful. The method then waits five seconds before it calls itself again. Therefore, prices will be updated every five seconds.
The onSuccess method loops through the database rows it receives from the WCF service. For each item, it constructs the predicted client id attribute based on the ProductID, uses that to retrieve the element by using the $get method (which is a shorthand form of document.getElementById), and sets the element to the new ListPrice value. In order to make changes more visible, it also encloses the new price in <b> (bold) tags.
Add an onload attribute to the opening <body> tag that calls the new UpdatePrices function after a five-second delay, as shown in the following example:
<body onload="setTimeout('UpdatePrices()', 5000)">
Save the changes and then press CTRL+F5 to view the page in a browser.
The page loads as it did before. After five seconds you see that the prices of one tenth of the products increase by 1000. The changed prices appear in bold. Every five seconds, the prices of additional products are changed.
If you did not set the ClientIDMode property of the ListView control to Predictable, the client id attributes would have been created in the format ListView1_ctrl99_PriceLabel where 99 is the relative index of the item. Because each call to the Web service retrieves only the rows that have changed, the relative index of any given row retrieved from the service will not be the same as the corresponding item's position in the complete list in the Web page. It would then be very difficult to write client script code to find the appropriate row and update it.
Next Steps
This walkthrough has shown you how to use client script to access data-bound controls that are located on a Web page. From here, you might want to learn more about how ASP.NET generates ClientID values or about special considerations for user controls. The following list suggests topics for additional learning.