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.
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.
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 { get; set; } |
/// <summary> |
/// Gets or sets the last name. |
/// </summary> |
/// <value>The last name.</value> |
string LastName { get; set; } |
/// <summary> |
/// Gets or sets the title. |
/// </summary> |
/// <value>The title.</value> |
string Title { get; set; } |
/// <summary> |
/// Gets or sets the phone. |
/// </summary> |
/// <value>The phone.</value> |
string Phone { get; set; } |
/// <summary> |
/// Gets or sets the E mail. |
/// </summary> |
/// <value>The E mail.</value> |
string EMail { get; set; } |
/// <summary> |
/// Gets or sets the cell phone. |
/// </summary> |
/// <value>The cell phone.</value> |
string CellPhone { get; set; } |
/// <summary> |
/// Gets or sets the responsibilities. |
/// </summary> |
/// <value>The responsibilities.</value> |
string Responsibilities { get; set; } |
/// <summary> |
/// Gets or sets the photo. |
/// </summary> |
/// <value>The photo.</value> |
string Photo { get; set; } |
/// <summary> |
/// Gets or sets the department ID. |
/// </summary> |
/// <value>The department ID.</value> |
Guid DepartmentID { get; set; } |
/// <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; } |
} |
/// <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; |
} |
<%@ WebService Language="C#" Class="Sample.Contacts.Services.ContactsService, Sample.Contacts" %> |
/// <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 |
} |
<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> |
<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"> |
<a href="<%= Parent.Parent.ContactEditUrl %>">Edit</a> |
</telerik:ClientTemplate> |
<telerik:ClientTemplate Name="Delete" runat="server"> |
<a href="javascript:if(confirm('Are you sure you want to delete this contact?')) DeleteContent('{#ID#}')">Delete</a> |
</telerik:ClientTemplate> |
</telerik:ClientTemplatesHolder> |
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 = " "; |
} |
} |
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()); |
} |
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.
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.
Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.
Learn MoreSubscribe to get all the news, info and tutorials you need to build better business apps and sites
Progress collects the Personal Information set out in our Privacy Policy and the Supplemental Privacy notice for residents of California and other US States and uses it for the purposes stated in that policy.
You can also ask us not to share your Personal Information to third parties here: Do Not Sell or Share My Info
We see that you have already chosen to receive marketing materials from us. If you wish to change this at any time you may do so by clicking here.
Thank you for your continued interest in Progress. Based on either your previous activity on our websites or our ongoing relationship, we will keep you updated on our products, solutions, services, company news and events. If you decide that you want to be removed from our mailing lists at any time, you can change your contact preferences by clicking here.