Sample pluggable module: Contacts pluggable module - Implementing client-side data binding for RadGrid

Sample pluggable module: Contacts pluggable module - Implementing client-side data binding for RadGrid

Posted on April 06, 2009 0 Comments

The content you're reading is getting on in years
This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.

[This post is part of the developer's manual preview published on this blog. You can find temporary TOC here.]

 

In this article we are going to examine how to take advantage of the excellent feature provided by the Telerik RadGrid control to implement fast and responsive grids in Sitefinity modules. As you may have noticed, in Sitefinity 3.6 all grids displaying content are using this approach, making them highly performing controls which work (e.g. sort, page, delete) without postbacks. As our sample contacts pluggable module demonstrated, this feature is available to all modules, not only Generic Content based ones.

 

General overview of the implementation


Before we dig into the actual implementation details, let us examine the steps we will need to complete in order to have a client side data bound grid control in our module. Note that we will describe what we believe to be best practice at the moment and that not all steps are required. As long you understand the principle you are free to modify it as you wish.
  • Implement a simplified version of the persistent data class to which you are going to bind
  • Implement a webservice which the grid will use to work with data
  • Implement the RadGrid, ClientSide templates and JavaScript for binding
Even though this may seem as a lot work for simple data binding operation, you will see that in reality every of these steps is very simple and straightforward. The little more effort invested in this will pay off big dividends through dramatically improved user experience.

 


Implement a simplified version of the persistent data class to which you are going to bind


The purpose of the client side data binding is to increase responsiveness and performance of the user interface. We are doing that by reducing the amount of information that needs to be sent from the server to the client (browser). With classic, server side data binding, every time a command on the grid is performed (e.g. user sorts the grid), server prepares the whole page and sends the whole page (including navigation and all the other controls that stay unchanged between the two commands) to the client. With client side data binding, server will send only the data used by the grid, thus making the whole process much faster.

 

Since the purpose of the whole implementation is to increase the performance, it is only natural to wonder if we can speed things up even more. Namely, perhaps we don’t need all the data on the client - perhaps we can send only the data that grid actually uses. This is where the simplified data classes come into the play. In our sample contacts module we have Contact class (Sample.Contacts.Data namespace) which implements IContact interface. The IContact interface implementation looks like this:
    /// <summary> 
    /// Defines what properties every Contacts object should implement 
    /// </summary> 
    public interface IContact 
    { 
        /// <summary> 
        /// Gets the ID. 
        /// </summary> 
        /// <value>The ID.</value> 
        Guid ID { get; } 
 
        /// <summary> 
        /// Gets or sets the first name. 
        /// </summary> 
        /// <value>The first name.</value> 
        string FirstName { getset; } 
 
        /// <summary> 
        /// Gets or sets the last name. 
        /// </summary> 
        /// <value>The last name.</value> 
        string LastName { getset; } 
 
        /// <summary> 
        /// Gets or sets the title. 
        /// </summary> 
        /// <value>The title.</value> 
        string Title { getset; } 
 
        /// <summary> 
        /// Gets or sets the phone. 
        /// </summary> 
        /// <value>The phone.</value> 
        string Phone { getset; } 
 
        /// <summary> 
        /// Gets or sets the E mail. 
        /// </summary> 
        /// <value>The E mail.</value> 
        string EMail { getset; } 
 
        /// <summary> 
        /// Gets or sets the cell phone. 
        /// </summary> 
        /// <value>The cell phone.</value> 
        string CellPhone { getset; } 
 
        /// <summary> 
        /// Gets or sets the responsibilities. 
        /// </summary> 
        /// <value>The responsibilities.</value> 
        string Responsibilities { getset; } 
 
        /// <summary> 
        /// Gets or sets the photo. 
        /// </summary> 
        /// <value>The photo.</value> 
        string Photo { getset; } 
 
        /// <summary> 
        /// Gets or sets the department ID. 
        /// </summary> 
        /// <value>The department ID.</value> 
        Guid DepartmentID { getset; } 
 
        /// <summary> 
        /// Gets the department. 
        /// </summary> 
        /// <value>The department.</value> 
        IDepartment Department { get; } 
 
        /// <summary> 
        /// Gets the name of the department. 
        /// </summary> 
        /// <value>The name of the department.</value> 
        string DepartmentName { get; } 
    } 
 
It is obvious that we don’t need all this data to bind a grid, so in order to reduce the amount of the data that we will transfer from server to client when binding a grid, we will implement a simplified version of this data class. In the sample contacts module this class is named SimpleContactItem and it contains only the properties that we actually need to successfully bind the grid: 
    /// <summary> 
    /// Lightweight representation of IContact object used for webservice data transfer 
    /// </summary> 
    public class SimpleContactItem 
    { 
        /// <summary> 
        /// Initializes a new instance of the <see cref="SimpleContactItem"/> class. 
        /// </summary> 
        /// <param name="contact">The contact.</param> 
        public SimpleContactItem(IContact contact) 
        { 
            this.id = contact.ID; 
            this.firstName = contact.FirstName; 
            this.lastName = contact.LastName; 
            this.title = contact.Title; 
            this.departmentName = contact.DepartmentName; 
        } 
 
        /// <summary> 
        /// Gets or sets the ID. 
        /// </summary> 
        /// <value>The ID.</value> 
        public Guid ID  
        {  
            get { return this.id; } 
            set { this.id = value; } 
        } 
 
        /// <summary> 
        /// Gets or sets the first name. 
        /// </summary> 
        /// <value>The first name.</value> 
        public string FirstName  
        { 
            get { return this.firstName; } 
            set { this.firstName = value; } 
        } 
 
        /// <summary> 
        /// Gets or sets the last name. 
        /// </summary> 
        /// <value>The last name.</value> 
        public string LastName  
        { 
            get { return this.lastName; } 
            set { this.lastName = value; } 
        } 
 
        /// <summary> 
        /// Gets or sets the title. 
        /// </summary> 
        /// <value>The title.</value> 
        public string Title  
        { 
            get { return this.title; } 
            set { this.title = value; } 
        } 
 
        /// <summary> 
        /// Gets or sets the name of the department. 
        /// </summary> 
        /// <value>The name of the department.</value> 
        public string DepartmentName  
        {  
            get { return this.departmentName; } 
            set { this.departmentName = value; } 
        } 
 
        private Guid id; 
        private string firstName; 
        private string lastName; 
        private string title; 
        private string departmentName; 
    } 
 
Now that we have our simplified data class, we can continue with the implementation and start working on the webservice.

 


Implement a webservice which the grid will use to work with data


Since we will bind our data to the grid on the client side we need to implement the webservice which we will call from the javascript. The purpose of the webservice is very similar to the manager class - but while manager class is used from our server side code, webservice will be used from our client side code.
If you are unfamiliar with webservices, please follow these steps to create a new webservice:
  • In the Sitefinity website, navigate to ~/Sitefinity/Admin/Services folder
  • Right click on the Services folder and select “Add new item”
  • From the “Add new item” dialog, select “Web Service”, give your service a name and uncheck the “Place code in separate file” checkbox
  • Delete all the code that Visual Studio generated and leave only the first line (declaration)
  • Set the Class attribute to the full name of the class that will provide code for this webservice (we will implement this class in a moment). The webservice file for the Contacts module (ContactsService.asmx) looks like this:
    <%@ WebService Language="C#" Class="Sample.Contacts.Services.ContactsService, Sample.Contacts" %> 
  • Now we need to implement the webservice class. In your pluggable module project, create a new class and make sure its name corresponds to the name of the class you have defined in the .asmx file. For example in our sample contacts module this class is located in the Sample.Contacts project in the Services folder and has the name of ContactsServices.cs
  • In this class you should implement all the methods that you will need to make your grid work properly (for example method for retrieving data, deleting data and so on). The webservice class implemented in ContactsService webservice looks like this:
        /// <summary> 
        /// Provides web service methods for Contacts module. 
        /// </summary> 
        [ScriptService] 
        public class ContactsService 
        { 
            #region Properties 
     
            /// <summary> 
            /// Gets or sets the filter. 
            /// </summary> 
            public string FirstLetterFilter 
            { 
                get { return this.firstLetterFilter; } 
                set { this.firstLetterFilter = value; } 
            } 
     
            /// <summary> 
            /// Gets or sets the provider name. 
            /// </summary> 
            public string ProviderName 
            { 
                get { return this.providerName; } 
            } 
            #endregion 
     
            /// <summary> 
            /// Gets contacts for client-side data binding. 
            /// </summary> 
            /// <param name="firstLetterFilter">The first letter filter.</param> 
            /// <param name="providerName">Name of the provider.</param> 
            /// <returns>GridBindingData object.</returns> 
            [WebMethod] 
            public GridBindingData GetContacts(string firstLetterFilter, string providerName) 
            { 
                this.firstLetterFilter = firstLetterFilter; 
                this.providerName = providerName; 
                return this.GetContacts(firstLetterFilter, new ContactsManager(providerName)); 
            } 
     
            /// <summary> 
            /// Gets the contacts. 
            /// </summary> 
            /// <param name="firstLetterFilter">The first letter filter.</param> 
            /// <param name="manager">The manager.</param> 
            /// <returns></returns> 
            public GridBindingData GetContacts(string firstLetterFilter, ContactsManager manager) 
            { 
                IList list; 
     
                if (!string.IsNullOrEmpty(firstLetterFilter)) 
                { 
                    list = manager.GetContacts(firstLetterFilter); 
                } 
                else 
                { 
                    list = manager.GetContacts(); 
                } 
     
                int count = list.Count; 
                List<object> dataList = new List<object>(count); 
                foreach (IContact contact in list) 
                { 
                    dataList.Add(new SimpleContactItem(contact)); 
                } 
                return new GridBindingData(dataList, count); 
            } 
     
            ///<summary> 
            /// Deletes a contact. 
            ///</summary> 
            ///<param name="contentId">Id of the contact to be deleted</param> 
            ///<param name="providerName">Name of the provider to which contact belongs to</param> 
            [WebMethod] 
            public void DeleteContact(Guid contactId, string providerName) 
            { 
                ContactsManager manager = new ContactsManager(providerName); 
                manager.DeleteContact(contactId); 
            } 
            #region Private fields 
     
            private string firstLetterFilter; 
            private string providerName; 
            #endregion 
        } 
There are several things to note here.
  • In order to make a class a webservice class you need to declare ScriptService attribute on it.
  • Every method that should be accessible from the client side must have a WebMethod attribute declared on it.
  • The data is being returned through GridBindingData class, which has our data source and count arguments.
Everything else is pretty clear and straightforward. Now that our webservice is ready, we can go to our template and implement the RadGrid.

 

Implement the RadGrid, ClientSide templates and JavaScript for binding


When it comes to implementing RadGrid, there are two basic approaches. You can go the standard way as it is demonstrated in the Telerik RadGrid demos or you can go the Sitefinity way where we provide some Sitefinity specific enhancements. In this article we will illustrate the Sitefinity way.
Let us start by opening a template where we wish to have the grid and declare the control. Note that if you are going the Sitefinity way all your columns should be declared as template columns. Here is how the RadGrid declaration looks like in the ContactsItemList.ascx template of our sample contacts module.
<telerik:RadGrid ID="contactsGrid" runat="server" AutoGenerateColumns="false" EnableViewState="false" Skin="SitefinityItems" EnableEmbeddedSkins="false"
            <MasterTableView AllowMultiColumnSorting="false" CssClass="listItems listItemsBindOnClient" Width="98%"
                <Columns> 
                    <telerik:GridTemplateColumn UniqueName="LastName" HeaderText="Last Name" /> 
                    <telerik:GridTemplateColumn UniqueName="FirtsName" HeaderText="First Name" /> 
                    <telerik:GridTemplateColumn UniqueName="Title" HeaderText="Title" /> 
                    <telerik:GridTemplateColumn UniqueName="DepartmentName" HeaderText="Department" /> 
                    <telerik:GridTemplateColumn UniqueName="Edit" HeaderText="Edit" ItemStyle-CssClass="gridActions edit" /> 
                    <telerik:GridTemplateColumn UniqueName="Delete" HeaderText="Delete" ItemStyle-CssClass="gridActions delete" /> 
                </Columns> 
            </MasterTableView> 
            <ClientSettings> 
                <ClientEvents OnCommand="RadGrid_Command" OnRowDataBound="RadGrid_RowDataBound" /> 
            </ClientSettings> 
        </telerik:RadGrid> 
 
Now that we have declared the RadGrid, let us declare the Sitefinity control which will inject the appropriate data into the Grid. The control is called ClientSideTemplates and its declaration looks as follows:
<telerik:ClientTemplatesHolder ID="GridTemplates" runat="server"
        <telerik:ClientTemplate Name="LastName" runat="server"
            {#LastName#} 
        </telerik:ClientTemplate> 
        <telerik:ClientTemplate Name="FirtsName" runat="server"
            {#FirstName#} 
        </telerik:ClientTemplate> 
        <telerik:ClientTemplate Name="Title" runat="server"
            {#Title#} 
        </telerik:ClientTemplate> 
        <telerik:ClientTemplate Name="DepartmentName" runat="server"
            {#DepartmentName#} 
        </telerik:ClientTemplate> 
        <telerik:ClientTemplate Name="Edit" runat="server"
            <href="<%= Parent.Parent.ContactEditUrl %>">Edit</a> 
        </telerik:ClientTemplate> 
        <telerik:ClientTemplate Name="Delete" runat="server"
            <href="javascript:if(confirm('Are you sure you want to delete this contact?')) DeleteContent('{#ID#}')">Delete</a> 
        </telerik:ClientTemplate> 
    </telerik:ClientTemplatesHolder> 
 
Let us now take a step back and see what exactly we have done. First we have declared the RadGrid with several GridTemplateColumn columns. Then we have declared the ClientTemplatesHolder control and created a ClientTemplate for each of the columns in the grid. Notice that client templates are associated with the grid columns, by having the same name as the UniqueName of the column.

 

Inside of every client template we are free to implement any kind of html we wish to (not that server controls are not allowed). Once the grid is bound to data, it will take a look at the ClientTemplatesHolder and replace its cells with the one defined in the ClientTemplates.

 

You may have noticed a special kind of syntax in the client templates: {#LastName#}. Namely, if you go back to the SimpleContactItem class implementation, you will notice that we have a property LastName in that class. Namely, {#PropertyName#} syntax is used to designate where the value from the data item should be placed when data is bound. You can think of it as an equivalent of the <%# Eval(“PropertyName”) %>.

 

Finally, we come to the JavaScript functions which do the actual binding. In the ContactsItemList.ascx template you can find all the JavaScript used for contacts module, but for the purpose of this article we will extract (and simplify) only the functions that are really needed.

var dataProviderName = "<%= Parent.Parent.ProviderName %>"
var gridTemplates = ClientTemplates.GetSet("<%= GridTemplates.ClientID %>"); 
 
function loadData() { 
    var firstLetterFilterField = document.getElementById('<%= FirstLetterFilterField.ClientID %>'); 
    DataBindGrid(firstLetterFilterField.value); 
 
Sys.Application.add_load(loadData); 
 
function DataBindGrid(firstLetterFilterAsSQL) { 
    Sample.Contacts.Services.ContactsService.GetContacts(firstLetterFilterAsSQL, dataProviderName, updateGrid, OnFailed); 
 
 
function updateGrid(result) { 
    var tableView = $find("<%= contactsGrid.ClientID %>").get_masterTableView(); 
    tableView.set_virtualItemCount(result.Count); 
    tableView.set_dataSource(result.Data); 
    tableView.dataBind(); 
 
function RadGrid_RowDataBound(sender, args) { 
    var dataItem = args.get_dataItem(); 
    var item = args.get_item(); 
    var columns = item.get_owner().get_columns(); 
    var cells = args.get_item().get_element().cells; 
     
    for (var i = 0; i < cells.length; i++) { 
        var cell = cells[i]; 
        var html = gridTemplates.Replace(columns[i].get_element().UniqueName, dataItem); 
        if (html != null
            if( html != ""
                cell.innerHTML = html; 
            else 
                cell.innerHTML = "&nbsp;"
    } 
 
function OnFailed(error) { 
    alert("Stack Trace: " + error.get_stackTrace() + "/r/n" + 
        "Error: " + error.get_message() + "/r/n" + 
        "Status Code: " + error.get_statusCode() + "/r/n" + 
        "Exception Type: " + error.get_exceptionType() + "/r/n" + 
        "Timed Out: " + error.get_timedOut()); 
 
So, let us briefly explain what this code does. First we subscribe to the load event of the page and handle it with the loadDate function. This is done by this line:

 

Sys.Application.add_load(loadData);

Next, in the loadData function we check if there is a first letter filter present (remember that the contacts module supports filtering contacts by the first letter of the contact’s last name) and call DataBindGrid function.

 

DataBindGrid function calls the method on our webservice which we have defined at the beginning of this article and specifies successful callback function (“updateGrid”) and failure callback function (“OnFailed”).

 

The updateGrid function (successful callback) will return us the result object (GridBindingData object, check the webservice implementation if unsure) in which we have Data and Count objects. We’ll take advantage of the RadGrid client side API then and bind this data to the grid.

 

Finally, we also have a client side handler for the RadGrid’s RowDataBound client side event. In this function, we are simply performing the substitution of the grid’s template column content with the one defined in our ClientTemplatesHolder.

 

Conclusion


While the approach may seem a bit exhaustive at the moment, you will soon notice that after the first implementation it becomes very natural. The benefits of this approach will surely overshadow the effort invested.
 

 

 


progress-logo

The Progress Team

View all posts from The Progress Team on the Progress blog. Connect with us about all things application development and deployment, data integration and digital business.

Comments

Comments are disabled in preview mode.
Topics

Sitefinity Training and Certification Now Available.

Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.

Learn More
Latest Stories
in Your Inbox

Subscribe to get all the news, info and tutorials you need to build better business apps and sites

Loading animation