My take on implementing searches with AJAX (Part 5)

by Ishai Hachlili 15. February 2008 16:39

So far in this series of posts I created a search page that uses AJAX to fetch the results and display them using a modified version of the WebFX ColumnList control.
The only thing left to do to meet the requirements is add support for paging and sorting.

We already have the paging and sorting parameters in the SearchParameters object, so we're sending these parameteres and returning onlt the first page of the results.
Now we need to add paging buttons and enable users to view the other pages.
We also want to cache pages and support prefetching of the next page.

The Pager Control
I wanted to make a reusable paging control that I can use whenever I need paging support.

The main method of the pager control is ShowPage, this method is called with the page index and if the page is not in the cache it will call the server side method to get that page.
Before calling the ShowPage method we need to set some parameters to let the pager know what we want to do.
-ServerSideMethod - the name of the method to call
-ServerMethodType - 1 for a web service method, 2 for PageMethods
-UsePredictiveFetching - when true, the next page will be fetched and cached
-ShowPageCallback - a reference to a method for showing the page (needed because fetching a page is done asynchronously and the pager can be used by different classes)
-SearchParameters - a SearchParameters object that will be sent to the server side method, the pager control will set the paging and sorting parameters every time the ShowPage method is called, but the search specific parameters won't change between pages.

The Pager JavaScript Class:
Pager.js.txt (4.93 kb)

In the updated Search function the Pager class is created the first time the function is called, the SearchParameters are set and the ShowFirstPage method is called (this method simply calls ShowPage with the value 1).
The pager variable is declared outside of the function because we need it later for showing other pages (and if we created it every time, there will be no point for the page caching).

The updated Search function:

var pager = null;

function Search() {
var fc = new FormController();
var searchParams = fc.CollectFormValues("searchFormContainer");

if (pager==null) {
pager = new Pager();
pager.ServerMethodName = 'IshaiHachlili.MyTakeOnDotNet.WebServices.ContentsWS.DoSearch';
pager.ServerMethodType = 1;
pager.ShowPageCallBack = showGrid;
pager.UsePredictiveFetching = true;

pager.SearchParams = searchParams;


var searchResultsObject;
function showGrid(result) {

if (typeof(aColumns)!="undefined") {
if (TotalPages>0) {

var el =$get("searchResultsContainer"); //the element that will contain the grid
if (searchResultsObject == null) {
var searchResultsObject = new WebFXColumnList();
searchResultsObject.SortingCallback = pager.Sort.bind(pager);
searchResultsObject.create(el, aColumns);
} else {

} else {
alert('No results returned');

I renamed the onComplete method to reflect that it is now used to show the grid and not as the onComplete call back to the server side call.
You can also see that I moved the searchResultsObject declaration outside of the function. The reason for doing that is to keep the same positions and widths for columns (If the ColumnList is created everytime a page is shown any changes the user made to the widths and positions of columns will be reset).

The Pager class has two methods that can be used for next/previous paging, the ShowNextPage and ShowPreviousPage. It also has two methods for showing the first and last pages.
Adding buttons to control the paging is easy, you can also use the TotalPages value to create direct page links.

Sorting when there's more than one results page.
If there's only one results page the WebFX control can be sorted on the client side, but when there's more than one page we need to change the sorting paramters in SearchParameters and execute the search again.
If you take a look at the Pager class, you can see the Sort method and all it does is change the sorting parameters, clear the page cache and call the ShowFirstPage method.

The Sort method should be called from the grid, when a column header is clicked. Since the WebFX control already support sorting, all I had to do is add a call from the WebFXColumnList.sort function to the Pager's Sort method.
I also wanted to cancel the client side sorting if there's more than one page, no reason to sort on the client side and then replace the whole grid with the new sorted data from the server side.
I added a property to the ColumnList called SortingCallback, and in the showGrid function I set the value of this property to the pager's sort method.

Let's see what we have so far
1. A search page with support for paging, pre-fetcing the next page and caching previous pages.
2. A grid that supports column dragging, resizing, and formatting and links in the cell contents.
3. Easy maintainance. Adding a new search parameter requries only a simple update to the HTML form and the stored procedure, no need to touch the server side code at all.
4. Better performance. Because we only transfer the page data instead of refreshing the entire page (or loading the whole grid's HTML with an update panel), the size of the download is much smaller. Also, caching saves more requests for the same data and pre-fetching gives the user a better experience by having the next page ready faster.

and, best of all, with this implementation I just saved myself a lot of work. From now on, the DBA and designer can create search pages without me being involved. I actually made it even easier by moving the search function inside another class, and the only code that needs to be written is a call to a function with the values for some properties.

What's next? Some advanced features...
This framework will allow you to create pages easily without having to recompile you code but there are some other features that can be added to make it even cooler.

Saved Searches
Allowing users to saves their favorite and most used searches is a great feature that will be easy to implement.
By using the FormController's CollectFormValues to get a JSON of the search parameters and saving these parameters with some name, it should be easy to load the values back to the form later.
Most of the work will be the function that sets the values for the form elements. Apart from that we need to add some HTML for saving and loading searches, a text box with a button for saving and a drop down with a button for loading. Should'nt be too hard.

New search results alerts
Once we have the JSON saved on the server side we can execute this searches at any time, we just need to deserialize the JSON back to the SearchParameters object and execute a search.
It should be very easy to create a service that executes searches periodically and sends a notification when new results are found.

Grid Personalization
Different users might use the same search pages for different things and it might be easier for each user to get the results grid he way they want it. Since we already have a grid that supports setting the widths and positions of columns, we can save this properties for each user and load them back.
In my implementation of this feature I simply saved the column ids, positions and widths for each user and after the default grid is created I just ran over the columns and re-set this properties.

Dynamically created forms
By adding support for dynamically created forms, I can move the whole process of adding a new search page to the database layer. Add a stored procedure, add the data query, columns and form input controls in the database, and open a generic search page with the name of the form to load. Now you don't even need to upload an HTML file.
Of course, getting to a place where these forms look good enough and support all the layout requirements might be too hard to make it worth it.

Files: (935.24 kb)

This zip file contains all the code shown in this series and some additional code that was mentioned.


Tags: ,

AJAX | Javascript

My take on implementing searches with AJAX (Part 4)

by Ishai Hachlili 12. February 2008 13:40

In the previous posts in this series I collected the form values, executed the search and processed the search results columns on the server side.

Now it’s time to return the results to the client side and display the search results grid.

I’m going to use the WebFX columnlist for my grid. This is my favorite javascript grid control.
It allows column resizing and drag & drop reordering as well as client side sorting (which will be great for one page search results).
It doesn’t support any formatting or links in the grid, so I’ll have to make some upgrades to it.

If you look at the documentation for the WebFXColumnList, you can see it uses an array of the column names and an array of data rows where each row is an array of values (that should be in the same order as the column names).

JavaScript sample – showing a grid using WebFXColumnList

var aColumns = [

var aData = [
['3','Butter pecan','Light brown','5.3%'],
['5','Neapolitan','Greenish brown','4.2%'],
['6','Chocolate chip','Brown','3.9%'],
['7','French vanilla','Yellowish white','3.8%'],
['8','Cookies and cream','Light brown','3.6%'],
['9','Vanilla fudge ripple','White', '2.6%'],
['10','Praline pecan','Brown','1.7%'],
['12','Chocolate almond','Brown','1.6%'],
['13','Coffee','Dark brown', '1.6%'],
['14','Rocky road','brown', '1.5%'],
['15','Chocolate marshmallow','Light brown','1.3%']

var el = document.getElementById('container');

var o = new WebFXColumnList();
o.create(el, aColumns);

Sample taken from

Right now I have a DataSet, so I need to convert it to these arrays and the best way to do it is with xslt.
Because I want more than just column names returned to the client side, I’m going to use the Column Collection DataTable I created in the previous post for the aColumns array.

My XSLT will create the actual javascript vars, in the client side I'll simply evaluate the returned string and I'll have the variables ready to use


<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="">
<xsl:output method="text"/>
<xsl:template match="/" >

var aColumns = [<xsl:for-each select="*/Columns">
<xsl:sort select="Position" data-type="number" order="ascending"/>
<xsl:for-each select="./*">
<xsl:value-of select="name()"/>:'<xsl:value-of select="."/>'<xsl:if test="position() &lt; last()">,</xsl:if>
}<xsl:if test="position() &lt; last()">,</xsl:if>

var aData = [<xsl:for-each select="//Data">
[<xsl:for-each select="./*[name()=//Columns/Name]">
'<xsl:call-template name="escape-quotes">
<xsl:with-param name="text" select="normalize-space(text())"/>
</xsl:call-template>'<xsl:if test="position() &lt; last()">,</xsl:if>
</xsl:for-each>]<xsl:if test="position() &lt; last()">,</xsl:if>

<xsl:variable name="quote-char">

<xsl:variable name="escaped-quote">

<xsl:template name="escape-quotes">
<xsl:param name="text"/>
<xsl:when test="contains($text,$quote-char)">
<xsl:variable name="pre" select="substring-before($text,$quote-char)"/>
<xsl:variable name="post">
<xsl:call-template name="escape-quotes">
<xsl:with-param name="text" select="substring-after($text,concat($pre,$quote-char))"/>
<xsl:value-of select="concat($pre,$escaped-quote,$post)"/>
<xsl:value-of select="$text"/>

I'm going to do the transformation in the web service.
I called the method GetResultString and in the web method code from part 1 I already called it.

protected string GetResultString(DataSet ds, int pageSize)
string result = TransformXsl(ds, "~/XSLT/ConvertSearchResultsDS.xslt");
int rowCount = (int)ds.Tables["TotalRows"].Rows[0]["TotalRows"];
result += String.Format("var TotalPages = {0};", CalculateTotalPages(rowCount, pageSize));

return result;

TransformXsl is a simple function that takes the dataset and xslt and does the transformation using the XslCompiledTransform class.
CalculateTotalPages returns the number of total pages using the total row count and page size

Displaying the grid

The JavaScript Search function that calls the web method defined a function called onComplete for the callback.
This function will receive the results string, create the WebFXColumnList object, initialize it and add the rows.

function onComplete (result) {

if (typeof(aColumns)!="undefined") {
var el =$get("searchResultsContainer"); //the element that will contain the grid
var searchResultsObject = new WebFXColumnList();
searchResultsObject.create(el, aColumns);
} else {
alert('No results returned');

After evaluating the returned javascript code I should have the aData and aColumns variables and from that point I can continue with the create and addRows method calls to display the grid.

The string created by the xslt is not exactly what the WebFXColumnList expects, so I'll need to make some changes to the javascript.

The create method of the ColumnList expects a simple array with names, so I need to go through it and change all references to aColumns.
I replaced the aColumns[i] to aColumns[i].Header when creating the head table.

I also need to add support for hidden columns, I want the create method to skip these. I need to add support for the formatting in the addRows function, and make sure everything else works.

I'm not going to go into all of these changes, but an updated script will be included in the download.

Files: (919.09 kb)


Tags: , ,

AJAX | Asp.Net | Javascript

My take on implementing searches with AJAX (Part 3)

by Ishai Hachlili 12. February 2008 09:44

This series of posts will show you my way of implementing search pages using .Net and AJAX.

In part 1 and part 2, I created client side functions for collecting form values and the server side methods for executing the search.

In this post I’ll go into more details on how ProcessDataTable works.

ProcessDataTable accepts two parameters, a data table and a column collection name.

The Column Collection

The column collection is a set of column definitions. This is where the properties for each column in the search results are saved.
There are a lot of properties for each column, from display to permissions. Instead of defining these properties in HTML (the way the ASP.Net grid is defined) I decided to save the definitions in the database.
Saving the properties in the database allows me to change them without touching the code, while the application is live.

There are two tables for saving the columns. The first simply saves the column collection name and Id

Table ColumnCollections

The Columns table includes the actual definitions for each column

Table Columns
Name – The name of the column in the search results this definition applies to.
Header – The header text for the column in the grid
Position – the position of the column in the grid
Type– the type of column (columns can be labels, links, etc…)
SortType – this property is used by WebFXColumnList for client side sorting
Width – the width of the grid column
Align – the alignment of the grid column
VirtualColumn – Virtual columns don’t have a column in the search results, the contents of these columns are created dynamically from other column values.
AllowedRoles – Column permissions, we will check if the current user is allowed to see each column
ActionExpression – This is used for links. The expression can also be a javascript call.
ActionFields – The names of fields in this column collection to be used in the expression (separated by commas)
ActionConditions – a javascript code segement that can be evaluated to true or false
ActionConditionFields the names of fields to be used in the condition
ShowWhenConditionIsFalse – if the action condition is false, you can still show the column value without the link
FormatExpression – a formatting expression
FormatLocation – where to do the formatting (client side or server side)
FormatType – the type of formatting to do (composite string formatting, regex, and other types)
FormatFields – the fields to use for formatting.

Here is an example for using these properties

Adding an edit button for each row:
Assuming we have an ItemId column and a Status column where 1=Editable and 2=Closed

Name = Edit
LinkType = ActionLink (a type we’ll use in the grid for javascript actions)
VirtualColumn = true (this column won’t have an actual column in the results)
ActionExpression = Edit({0})
ActionFields = ItemId
ActionConditions = {0}==1
ActionConditionFields = Status
ShowWhenConditionIsFalse = false (don’t show the Edit link when the value of Status is not 1)
FormatExpression = ‘Edit’
FormatLocation = ClientSide

The action expression will get the value of item id for each row, so the actual code created for the link will be Edit(1), Edit (4), etc..
The ActionCondition will get the value of the status field, which can be 1 or 2, so the code will be 1==1 or 2==1
There is no real formatting to be done here, we just want to have the Edit text, so composite formatting with no fields will work here.

The ProcessDataTable method
The first step in this method is to get the column collection from the database.
Keep in mind that we will need to get the same column collection again every time a new search is done or another page is requested, so this information should be cached as well.

public static DataSet ProcessDataTable(DataTable Data, string columnCollectionName)
DataSet ds = new DataSet();//The dataset that will be returned

//Get the Columns Collection
DataSet columnProperties = ColumnsDL.GetColumns(columnCollectionName);

//Add virtual columns with empty values
foreach (DataRow dr in columnProperties.Tables[0].Rows)
bool isVirtual = (bool)dr["VirtualColumn"];
if (isVirtual)
DataColumn dc = Data.Columns.Add(dr["Name"].ToString(), typeof(String));
foreach (DataRow drData in Data.Rows)
drData[dc] = "NA";

//Remove unauthorizes columns
foreach (DataRow dr in columnProperties.Tables[0].Rows)
string columnName = dr["Name"].ToString();
bool userHasPermissions = UtilitiesBL.CheckUserPermissions(dr["AllowedRoles"].ToString(), dr["DeniedRoles"].ToString(), dr["AllowedUsers"].ToString(), dr["DeniedUsers"].ToString());
if (!userHasPermissions)
if (Data.Columns[columnName] != null)

//Server Side formatting
foreach (DataRow dr in columnProperties.Tables[0].Rows)
string columnName = dr["Name"].ToString();

if (!String.IsNullOrEmpty(dr["FormatExpression"].ToString()) && dr["FormatLocation"].ToString() == "1")
string formatExpression = dr["FormatExpression"].ToString();
string formatFields = dr["FormatFields"].ToString();
FormatTypes formatType = (FormatTypes)Convert.ToInt32(dr["FormatType"]);

foreach (DataRow dataRow in Data.Rows)
switch (formatType)
case FormatTypes.Composite:
dataRow[columnName] = CompositeFormatting(formatExpression, formatFields, dataRow, columnName);

case FormatTypes.RegEx:
dataRow[columnName] = RegExFormatting(formatExpression, dataRow, columnName);

//Add Data table to final dataset
ds.Tables["Data"].Merge(Data, true, MissingSchemaAction.AddWithKey);

//Add ColProps table to final data set
ds.Tables["Columns"].Merge(columnProperties.Tables[0], true, MissingSchemaAction.AddWithKey);

return ds;

CheckUserPermissions is a simple method that takes the current user (using HttpContext.User.Identity.Name)and checks if that user has permissions or if that user is in a role (using HttpContext.Current.User.IsInRole) that’s allowed to see this column

The CompositeFormatting uses String.Format and the RegExFormatting method uses RegEx.Replace to perform the server side formatting.

The last step is to add the Data and Columns DataTables to the new DataSet.
We will need the Columns information in the client side when creating the grid so we can add the actions, conditions and do the client side formatting

The next post will deal in returning the dataset we just created to the client side and displaying the grid

Files: (880.94 kb) (140.25 kb)

Tags: ,

AJAX | Asp.Net

About Me

Ishai Hachlili is a web and mobile application developer.

Currently working on Play The Hunt and The Next Line

Recent Tweets

Twitter October 23, 05:22
@BenThePCGuy a standard where that doesn't matter is better. One more reason to get the #Lumia920, wireless charging, no need for microUSB

Twitter October 23, 05:21
@ManMadeMoon where they dance around the issues and don't really talk about them

Twitter October 23, 05:20
@BenThePCGuy are you a @wpdev ?

Twitter October 23, 04:17
@JonahLupton But if it's black it's usually better

Twitter October 23, 02:58
@jongalloway next time ask your 5 year old how to spell