Monday, May 12, 2008

Using AjaxRepeater Control, ASP.NET AJAX

You can download full source code with samples and controls for the article here.

Recently after reading blog post Ajax Templates by Nikhil Kothari I really liked the idea, the source code is great, and the control is huge help for AJAX development.

In this blog post I want to share what I learned after investigating sample more carefully, and in what scenarios I feel this control is doing it's job quite well.

I want also to clarify that I will discuss this control's client side API, not touching it's server side capabilities.

Also one thing to mention - I converted the source code from the sample to stand alone server control, and changed it's namespace to one used in my library. Please read carefully the header of the source for the control, it states the license and says you can freely use the source for commercial or not commercial software, there you can also find some limitations.

Also - I modified only few lines of original code in the override of DataBind method, I wanted the template to be constructed even if I have no server side DataSource property specified.

So what is AjaxRepeater control, and how to use it?

To create Template, just drag the control on the page and specify template inside it, for example this way:

image

Here "itemTemplate" is the id of the element inside repeater. And inner XML/HTML of this element is the template representing one data item. In this case the template that will be repeated many times inside single "UL" element is:

image

"itemTemplate" id should be used for every template container, even if you have multiply AjaxRepeater controls on the page. Repeater finds template container to add template items to it by this id, searching for it inside parent AjaxRepeater control, which in the browser is rendered to DIV or SPAN element, depending on server-side property "RenderMode" which can be set to "Inline" or "Block". So this markup:

image

will be rendered in the browser (with empty data source) this way:

image

and this markup:

image

will be rendered as:

image

 

On the client side the repeater is javascript object, deriving from Sys.UI.Control, and it can be accessed using $find shortcut, specifying the ClientID of the control as control's ID. For example we can use this code to access the object:

image

The most important functions that we can call on the object are:

set_data(value) - where value is the array of objects that is used to generate as many html items as are data items in the array. After calling this function, whole inner contents of item container will be cleaned and new items generated and added to the document under container element.

addDataItem(dataItem) - adds one item to the container under last one. dataItem here is the object that will be used to generate one item from template.

So we can use the following tricks:

to clear the container:

image

to bind repeater to data source:

image

to incrementally add items to the container:

image

You may be interested what is generated in the html markup, let's see the result of execution of last sample:

image

And visually it looks like this:

image

Ok, lets see the simplest sample page that shows how to create items dynamically:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Sample1.aspx.cs" Inherits="Sample1" %>
<%@ Register Assembly="Devarchive.Net" Namespace="Devarchive.Net" TagPrefix="cc1" %>
<!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 id="Head1" runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="SM">
    </asp:ScriptManager>

    <script type="text/javascript">
        function addItem(value) {
            var data = {text:value};
            $find("AjaxRepeater1").addDataItem(data);
        }
    </script>

    <div>
        <cc1:AjaxRepeater ID="AjaxRepeater1" runat="server" RenderMode="Block">
            <ul id="itemContainer">
                <li>{text}</li>
            </ul>
        </cc1:AjaxRepeater>
        <input type="text" id="txt" />
        <input type="button" value="Add List Item" onclick="addItem($get('txt').value);" />
    </div>
    </form>
</body>
</html>

 

As you see the code is really simple - when you enter some text in the text box and then click "Add List Item" button, new Item is generated and added to the container.

The screen shot shows what we get:

image

 

Ok, lets look into templates, and data that is used to built the template. It is really simple. In the samples above

Template:

image

is used to build item:

image

using this dataItem:

image

To build table rows for example,

Repeater markup:

image

can be used to build the table with rows:

image

using this code:

image

 

Ok, but who said we cannot use repeater to generate only one item, and this way use it as client side view rather then repeater?

This is simple, suppose we have the following view:

image

Here we can use the following code to rebind view with new data:

image

 

I want to say one more thing - js script for templating feature uses innerHTML to initially retrieve and parse the template in the browser, and this makes problems when we want to use templates inside style attributes of the elements.

for example when using the following template, script cleans out the style attribute inner contents, when accessing it using innerHTML:

image

And renders it without styles, so to fix this I used a little fix/amend to the original script, With this approach you should use "_style" attribute instead of "style". Then when templating script loads the template it just strips out the leading underscore and generates correct items.

 

Now ! I want to show you the scenario which is really easy to achieve with this control !

Suppose you want to bind data from the web service. For example you want to fetch data and bind it to the view or repeater. It is simple!

As we know web services (and page methods) have nice built in feature - when called from javascript they are serializing return objects into JSON - and therefore can be used to fetch data and set it to the repeater right from the browser.

The source code for the sample page demonstrating this is:

 

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>

<!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 id="Head1" runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="SM">
        <Services>
            <asp:ServiceReference Path="~/WebService.asmx" />
        </Services>
    </asp:ScriptManager>
    <script type="text/javascript">
        function bind() {
            WebService.GetData(function(result) {
                var rpt1 = $find("rpt1");
                rpt1.set_data(result);
            });            
        }
    </script>
    <div>
        <input type="button" onclick="bind();" value="Bind From Service" />
        <dn:AjaxRepeater runat="server" ID="rpt1">
        <table cellpadding="4" cellspacing="2" width="400px">
            <tbody id="itemContainer">
                <tr>
                    <td _style="{style1}">{field1}</td>
                    <td _style="{style2}">{field2}</td>
                    <td _style="{style3}">{field3}</td>
                    <td _style="{style4}">{field4}</td>
                </tr>
            </tbody>
        </table>
        </dn:AjaxRepeater>
    </div>
    </form>
</body>
</html>

 

WebService.cs :

 

using System;
using System.Collections;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Collections.Generic;
using System.Web.Script.Services;

[WebService]
[ScriptService]
public class WebService : System.Web.Services.WebService
{
    Random r = new Random();

    [WebMethod]
    [ScriptMethod]
    public List<Data> GetData()
    {
        List<Data> data = new List<Data>();
        for (int i = 0; i < 5; i++)
        {
            Data d = new Data();
            d.field1 = "field1_" + i.ToString();
            d.field2 = "field2_" + i.ToString();
            d.field3 = "field3_" + i.ToString();
            d.field4 = "field4_" + i.ToString();
            d.style1 = "background-color:" + getRandomColor();
            d.style2 = "background-color:" + getRandomColor();
            d.style3 = "background-color:" + getRandomColor();
            d.style4 = "background-color:" + getRandomColor();
            data.Add(d);
        }
        return data;
    }

    private string getRandomColor()
    {
        string result = "#";
        for (int i = 0; i < 6; i++)
        {
            result += r.Next(0, 10).ToString();
        }
        return result;
    }

    public class Data
    {
        public string field1;
        public string field2;
        public string field3;
        public string field4;
        public string style1;
        public string style2;
        public string style3;
        public string style4;
    }
}
Is not this simple?
Here is the result page's screen shot:
 

image

 

Guys, we can use the control to build really cool dynamic contents using templates and web services !

 

Ok, but what about performance ? -

As I measured - on my computer (Athlon 64 4800+, 2 GB RAM DDR2) to fetch and build 5000 items in the repeater with markup used above it took only 13 seconds, this means 13/5000 = 0.0026 seconds for one template. Taking into account that performance degrade gracefully with growth of html markup size, these are really good figures. Besides this some time is spent to generate response on the server, and deserialize it on the client, all this is done in 13 seconds.

 

Now suppose scenarios when we have a lot of repeaters and views on the page - only templates are loaded first time, and all the data is bound later. This is really good approach very often - we can bind whole grids with sorting/paging this way, just need to specify sort direction and page number when calling web service. Or we can use master-detail relationship - where we select row in the table (master) which was bound using service, and in click event of the row bind view with corresponding data from service (details).

 

I hope this article will help you start with the control, And want to thank Nikhil for the great work.

 

Technorati Tags:

 

kick it on DotNetKicks.com

6 comments:

Anonymous said...

Nice post! :-)

One quick comment on the style attribute issue. I was anticipating the following usage:

td style="background-color: {color}">{field1}

So rather than replacing the entire style attribute as a single token, to replace style css attribute values.

Kirill Chilingarashvili said...

Hi Nikhil,
Thanks for clarification,
this does make sense, I just did not try to use it this way

Thanks.

Sonu Kapoor said...

You might want to take a look at the AjaxDataControls from DotNetSlackers.

http://dotnetslackers.com/projects/ajaxdatacontrols
http://codeplex.com/ajaxdatacontrols

Jonas said...

That one is really cool. Is there any way we can put AjaxLoader image to display GIF while loading data?

Cheers

aaricevans said...

Little long bur really really helpful thing it is for me.

mole removal at home said...

I seriously enjoy simply reading all your blogs. Just wanted to let you know that you have people like me who appreciate your work.