Login


Dynamically Populating Controls using AJAX

By Jonathan Wood on 7/2/2011
Language: C#
Technology: AJAXASP.NETWebForms
Platform: Windows
License: CPOL
Views: 18,076
Web Development » ASP.NET » General » Dynamically Populating Controls using AJAX

Sample Project Screenshot

Download Source Code Download Source Code

Introduction

ASP.NET WebForms make it very easy to implement AJAX on your web pages. You simple add a ScriptManager control to your page, and then wrap the content to be dynamically updated in an UpdatePanel. Any event raised by a control inside the UpdatePanel will cause the UpdatePanel and it's content to be refreshed, without refreshing the rest of the page. And you can even add a trigger so that a control outside of the UpdatePanel can trigger that UpdatePanel to refresh.

However, there are times when you might need a little more control. One example is where you want to update content from JavaScript or jQuery. The AJAX controls pretty much work automatically. While it's possible to call __doPostBack() from JavaScript to trigger a partial update, you might want your JavaScript to have more control over the entire process.

Recently, I was developing a page that had a link to run some JavaScript. The JavaScript made a combo box visible, and this combo box needed to list a large number of items. Since the combo box would only be needed if the user clicked the link, it seemed a waste to populate this control on the server side, before the page was rendered.

It made more sense to have the JavaScript that displayed the combo box also populate that combo box. However, I still needed to call server-side code because the data that populated the list would be coming from a database. Fortunately, ASP.NET makes this fairly easy to do.

Setting Up the ASPX Page

Listing 1 shows the Default.aspx page from my DynaPop (Dynamic Population) project. It contains a combo box with the ID DropList. Note that this combo box is implemented using an HTML <select> tag and not an ASP.NET DropDownList control.

When you use the ASP.NET DropDownList control, ASP.NET will verify the items in the list are the same items the list contained when the page was rendered. Since we are adding items on the client side, a postback results in an ASP.NET validation error. This postback validation is designed to prevent users from editing your web pages and trying to submit invalid data. You can disable this validation but, for our purposes, it's simpler just to use a <select> tag.

The page also contains a button with the ID PopList. The onclick attribute of this control calls the PopulateList() JavaScript function. This is the function that populates the combo box control.

PopulateList() calls PageMethods.GetListData(). PageMethods.GetListData() is actually generated by ASP.NET, and it will call the method GetListData() in the code behind. The first argument is passed to the code behind method. (In my example, this argument is not used.) And the second argument is the name of the JavaScript function to be called once the data has been successfully returned. In my example, the second argument specifies that the OnPopulateList() function will be called.

The OnPopulateList() function accepts a single argument, which will be the data returned from the server. In my example, it will contain a list of items. This function then loops through those items, adding each one as a new <option> in the DropList combo box.

Finally, notice that this page has a <ScriptManager> control with the EnablePageMethods set to true. This is necessary for our code to work. However, you may have noticed that we are not using an AJAX UpdatePanel.

Listing 1: Default.aspx

<%@ Page Language="C#" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="DynaPop.Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>

    <style type="text/css">
        body
        {
            font-family: Arial, Helvetica, sans-serif;
            font-size: small;
            color: #555555;
        }
    </style>

    <script type="text/javascript">

        function PopulateList() {
            PageMethods.GetListData(0, OnPopulateList);
        }
        function OnPopulateList(list) {
            var dropList = document.getElementById('DropList');
            for (i = 0; i < list.length; i++) {
                var option = document.createElement('OPTION');
                option.text = list[i].text;
                option.value = list[i].value;
                dropList.options.add(option);
            }
        }

    </script>

</head>
<body>
    <form id="form1" runat="server">
    <div>

        <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="True">
        </asp:ScriptManager>

        <p>
            This page demonstrates how to dynamically populate a control using AJAX.
        </p>

        <p>
            Click the Populate List button to populate the drop-down list. Then click
            the Submit button to submit the selected value back to the server.
        </p>

        <p>
            <input id="PopList" type="button" value="Populate List"
                onclick="PopulateList();" />
        </p>

        <p>
            <select id="DropList" name="DropListName" style="width: 300px">
            </select>
        </p>

        <p>
            <asp:Button ID="btnSubmit" runat="server" Text="Submit" />
        </p>

        <p>
            <asp:Label ID="lblSubmitValue" runat="server" Text="">
            </asp:Label>
        </p>
    
    </div>
    </form>
</body>
</html>

Writing the Code Behind

Now that we have our ASPX page, we need to write some C# code behind. Listing 2 shows my Default.aspx.cs file. The first thing to notice is the ListData class. The data I return will be a list of objects of this class. Notice that the two members (text and value) are referenced in the OnPopulateList() function in Listing 1.

Next, take a look at my GetListData() method. This is the method that is called from the client-side JavaScript, and this method returns the data (a list of ListData objects) used to populate the combo box. Normally, this data is likely to be read from a database. In my example, the code simply creates 100 objects in a loop. Also note that this method has both the WebMethod and ScriptMethod attributes. These attributes are required in order for the method to be called from client-side JavaScript. In addition, notice that this method must be declared as static.

Finally, the Page_Load() handler contains code to detect data posted back from the combo box. It does this by checking the Request.Form collection. This collection represents the raw values being posted to the page. Note that the string used, DropListName, is the value we assigned to the combo box's name attribute. If any data is found, it is copied to a Label control to demonstrate that the selection was correctly posted back.

Listing 2: Default.aspx.cs

using System;
using System.Collections.Generic;

namespace DynaPop
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (Request.Form["DropListName"] != null)
                lblSubmitValue.Text = String.Format("Submitted Value: \"{0}\"",
                    Request.Form["DropListName"]);
        }

        // Class to hold data passed to client-side JavaScript
        public class ListData
        {
            public string text { get; set; }
            public int value { get; set; }
        }

        // Method called from client-side JavaScript
        [System.Web.Services.WebMethod]
        [System.Web.Script.Services.ScriptMethod]
        public static IEnumerable<ListData> GetListData(int arg)
        {
            // Construct list of data
            List<ListData> list = new List<ListData>();
            for (int i = 0; i < 100; i++)
                list.Add(new ListData()
                {
                    text = String.Format("List Item {0}", i),
                    value = i
                });
            // Return data list
            return list;
        }
    }
}

Other Considerations

There are a number of things to watch out for when populating controls from client-side script this way. While these are standard techniques, some can cause problems with ASP.NET. One example is the problem I described earlier where ASP.NET might report a postback validation error when an ASP.NET control posts back data that the control did not contain when it was originally rendered.

Another problem is related to view state. Instead of the code I placed in the Page_Load() handler in Listing 2, I could've added the runat="server" attribute to the combo box and just looked at the control's Value property instead. However, there are cases where ViewState will overwrite the data being posted back. For example, when you initialize the value of a server-side control, there are cases where the control will be reinitialized to that value.

I've tried to simplify the technique here and avoid getting too deep into some of these issues. For the most part, I've done this by using pure HTML controls (no runat="server") and checking the Request.Form collection on postback. A little more work may be required when you are adding server-side controls to the mix.

Conclusion

The techniques described in this article have produced some great results for me. I'm confident you'll benefit from them if you have a situation where you can put them to work.

The attached download includes all of the source code for my test project.

End-User License

Use of this article and any related source code or other files is governed by the terms and conditions of The Code Project Open License.

Author Information

Jonathan Wood

I'm a software and website developer working out of the greater Salt Lake City area of Utah. I've developed many websites including Black Belt Coder, Trail Calendar, and others.

I hike each week with my dogs Suki and Sasha. You can see my hiking blog at Hiking Salt Lake.