Introduction
There are often scenarios where we as web developers wish we could provide users the richness and responsiveness of desktop applications. With web applications fast catching up, and also replacing their desktop counterparts, it’s only natural for the users to expect better user experience from the web applications we develop.
All of us must have hated the page refresh when we selected a country and then the state drop-down filled based on the country. There are other scenarios such as when we entered the wrong ticket ID on a website and waited for the long submit to render back to our browser only to find out we entered the wrong number, probably a typo. Such dependent drop-downs and server side validations on the website can soon start frustrating the users and force them to look at other websites which do it all on no submit of the page, just like a desktop application did. With businesses fast competing with each other, selling their products on the web and attracting probable customers, the user experience starts taking a primary role.
Today, we will take a look at a typical scenario which will demonstrate how a web application can provide better user experience using a set of existing technologies working together, called AJAX. Okay, let us first define AJAX.
AJAX – Defined
AJAX stands for Asynchronous JavaScript and XML. AJAX is a term coined by Adaptive Path and is not a new technology. It is a combination of various technologies working in conjunction with each other to provide a rich user experience to web users. AJAX uses:
- Presentation layer using standard technologies viz. HTML in addition to using the browser's DOM for dynamic presentation and interaction.
- Browser's DOM capability for data exchange between tiers.
- Information exchange amongst tiers using
XMLHTTPRequest
.
- Client scripting [preferably JavaScript] for making this all work together.
Let us now try dissecting the term AJAX.
A – Asynchronous
This means that this model is different from the Request – Response model associated with conventional web applications. In a conventional web application, the amount of time that elapses between a Request being sent to the server and the Response rendered to the browser can not be essentially put to utilization. The user is working synchronously with the various layers of the application and has an idle time during submits/refreshes. In the AJAX programming model, we can communicate with the business logic and the database layers while the user can still use the browser for doing other controlled [this is up to us to allow what we could allow the user to do during this time] actions. On the whole, this decouples the backend layers and the presentation layer as far as the user is concerned. We may call this as being Apparently Asynchronous.
JAX – JavaScript & XML
This means the Response received from the business logic and the database layer would be represented using XML whose manipulation can be done using JavaScript. JavaScript would also be used for dynamic presentation on the webpage. XML being only a preferred way of communication, the returned Response can even be text which your client side script would understand.
The figure above shows how the XMLHttpRequest
object takes the responsibility of providing a communication layer between the Web Server and the presentation layer, enabling asynchronous communication between the Web Server and the presentation layer.
AJAX brings with it various advantages we can put to our use in improving the user experience of our websites but at the same time, it also comes with a few disadvantages. Let’s take a look at both:
Advantages
- Lesser Data – Lesser Traffic- The amount of data sent over the network can be reduced since we speak of only the data interchange in the form of XML in most of the cases using AJAX. Compare this to the traditional view-submit-wait-view approach where the presentation data has to be sent to the browser as part of the response. You can imagine the difference in the generated network traffic here.
- Freedom from Page Refresh – The browser page need not be refreshed every time some data is required from the business logic layer. A classical example is the Country-State dependent drop-downs. The user experience is much better when the states dropdown is populated without the page being refreshed.
Disadvantages
- The trips to & fro the web server should be reduced as much as possible. Frequent trips to the server with large amount of data can quickly eat up the network bandwidth while the user may not realize it.
- Disabling client scripting on the browser would cause the scripts to stop executing. Thus, applications using AJAX should be capable of making an alternate functionality made available to the user.
Let us now take a look at the sample application:
The Sample Application
Employee-Territory [Northwind Database] dependent dropdown example
This application uses a pair Employee – Territory dropdown list. When an Employee is selected from the Employee dropdown, the corresponding territories are loaded into the Territory dropdown. This kind of usage of dependent dropdowns is very common in applications. Conventionally, we have been submitting the page using the OnChange
server side event of the Employee dropdown and filling the Territory dropdown on a server side event. This causes a page refresh, and depending on the size of the form, may take a long time to reload. The whole of the presentation code has to be rendered to the browser once again, in addition to maintaining the viewstate and filling the form fields with their previous values. In this example, we will see how the same operation can be performed with AJAX. With AJAX, we will eliminate the page refresh and will see how much we save on the network traffic.
We will use the famous Northwind database which ships as a sample database with the SQL Server installation. We consider three tables, Employees, EmployeeTerritories, and Territories having a structure and related to each other as per the following diagram:
Northwind Database Tables
EmployeeTerritoryAjaxClient.aspx
The EmployeeTerritoryAjaxClient.aspx is the page that is loaded to the browser. Initially, when the page is loaded, the Employee dropdown is filled on the Page_Load
server side event. The page will initially look like in figure below:
EmployeeTerritoryAjaxClient.aspx
The OnChange
event of the Employee dropdown invokes the populateTerritory()
client-side JavaScript function. We will take a look at this function along with the other helper functions in a later section in this article. Let us now take a look at the presentation code of the EmployeeTerritoryAjaxClient.aspx page.
<form id="frmEmployee" method="post" runat="server">
<table border="0" cellpadding="5" cellspacing="0" bgcolor="#ccffff" width="280">
<tr>
<th colspan="2">
AJAX Drop Down Demo</th>
</tr>
<tr>
<td>
<asp:Label id="lblEmployee" runat="server">Select Employee</asp:Label>
</td>
<td>
<asp:DropDownList id="ddEmployee" Runat="server"></asp:DropDownList>
</td>
</tr>
<tr>
<td>
<asp:Label id="lblTerritory" runat="server">Select Territory</asp:Label>
</td>
<td>
<asp:DropDownList id="ddTerritory" runat="server">
<asp:listitem Value="">--Select--</asp:listitem>
</asp:DropDownList>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<asp:Label id="lblStatus" runat="server" class="statustext"></asp:Label>
</td>
</tr>
</table>
</form>
Now, we will look at the code-behind C# code of this page.
EmployeeTerritoryAjaxClient.aspx.cs
private void Page_Load(object sender, System.EventArgs e)
{
if(!IsPostBack)
{
string connString = ConfigurationSettings.AppSettings["CONN_STRING"];
SqlConnection connection = new SqlConnection(connString);
connection.Open();
string SQL = "SELECT EmployeeID, TitleOfCourtesy + ' ' + LastName + ', ' +
FirstName as EmpName FROM Employees";
SqlDataAdapter da = new SqlDataAdapter(SQL, connection);
DataSet ds = new DataSet();
da.Fill(ds);
DataView dv = new DataView();
dv = ds.Tables[0].DefaultView;
BindEmployee(dv);
}
}
BindEmployee Method
private void BindEmployee(DataView dv)
{
try
{
if(dv != null)
{
ddEmployee.DataSource = dv;
ddEmployee.DataTextField = "EmpName";
ddEmployee.DataValueField = "EmployeeID";
ddEmployee.DataBind();
ListItem item = new ListItem("--Select--", "");
ddEmployee.Items.Insert(0, item);
}
else
{
throw new Exception("Dataview is null.");
}
}
catch(Exception ex)
{
Response.Write("Exception: " + ex.Message);
}
}
InitializeComponent Method
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
this.ddEmployee.Attributes.Add("OnChange", "return populateTerritory();");
}
The Page_Load
code is straightforward. A connection to the database is established and records from the Employees table are populated to a dataset via a SqlDataAdapter
. The DefaultView
of the Employees table from the dataset is passed as a parameter to the BindEmployee()
method. BindEmployee()
populates the Employee dropdown on the page.
One thing to notice here is the following code of the InitializeComponent()
method:
ddEmployee.Attributes.Add("OnChange", "return populateTerritory()");
where we add the OnChange
event to the Employee dropdown. This OnChange
event is the one which invokes the populateTerritory()
JavaScript function which in turn invokes other helper functions. We will look at these JavaScript functions next.
JavaScript functions:
<script language="javascript">
var request;
var response;
var Territory = document.getElementById("ddTerritory");
var status = document.getElementById("lblStatus");
function populateTerritory()
{
var Employee = document.getElementById("ddEmployee");
if(Employee.options[Employee.selectedIndex].value != '')
{
return SendRequest(Employee.options[Employee.selectedIndex].value);
}
else
{
clearSelect(Territory);
status.innerText = "";
}
}
function InitializeRequest()
{
try
{
request = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(Ex)
{
try
{
request = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(Ex)
{
request = null;
}
}
if(!request&&typeof XMLHttpRequest != 'undefined')
{
request = new XMLHttpRequest();
}
}
function SendRequest(ID)
{
status.innerText = "Loading.....";
InitializeRequest();
var url = "EmployeeTerritoryAjaxServer.aspx?EmployeeID="+ID;
request.onreadystatechange = ProcessRequest;
request.open("GET", url, true);
request.send(null);
}
function ProcessRequest()
{
if(request.readyState == 4)
{
if(request.status == 200)
{
if(request.responseText != "")
{
populateList(request.responseText);
status.innerText = "Territories Loaded";
}
else
{
status.innerText = "None Found";
clearSelect(Territory);
}
}
}
return true;
}
function populateList(response)
{
var xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc.loadXML(response);
var opt;
var TerritoriesElem =
xmlDoc.getElementsByTagName("EmployeeTerritories");
var TerritoryElem =
TerritoriesElem[0].getElementsByTagName("TERRITORIES");
clearSelect(Territory);
if(TerritoriesElem.length > 0)
{
for (var i = 0; i < TerritoryElem.length; i++)
{
var textNode = document.createTextNode(
TerritoryElem[i].getAttribute("TERRITORYDESCRIPTION"));
appendToSelect(Territory,
TerritoryElem[i].getAttribute("TERRITORYID"), textNode);
}
}
}
function appendToSelect(select, value, content)
{
var opt;
opt = document.createElement("option");
opt.value = value;
opt.appendChild(content);
select.appendChild(opt);
}
function clearSelect(select)
{
select.options.length = 1;
}
</script>
There are three JavaScript global variables: request
, state
, and status
. request
will contain the XMLHttpRequest
object. state
and status
will contain the ddTerritory
dropdown and lblStatus
objects respectively. Now, let us see how the execution flows when an employee is selected:
- The
populateTerritory()
function is invoked. This function in turn invokes the SendRequest()
function conditionally by passing the selectedIndex
value of the ddEmployee
dropdown.
SendRequest()
function invokes InitializeRequest
which creates an XMLHttpRequest
object named request
.
SendRequest()
also does the following operations:
- It delegates the
ProcessRequest()
function to the request object's onreadystatechange
event.
- It opens a GET request to the EmployeeTerritoryAjaxServer.aspx page URL.
- It sends a request to the page with a
null
argument. If you want to send the page values as part of a POST request, you can send those values using this parameter.
- The
ProcessRequest()
function is of special interest to us. We determine the current state of the request using this function, and accordingly assign the functionality to other functions. There are the following five possible readyState
values for the request object:
readyState value |
Status |
0 | Uninitialized |
1 | Loading |
2 | Loaded |
3 | Interactive |
4 | Completed |
- The
ProcessRequest()
function invokes the populateList()
function with the response obtained from the EmployeeTerritoryAjaxServer.aspx page. This response is an XML stream which is parsed using a DOM parser and calling the appendToSelect()
function which is self describing.
Let us now take a look at the EmployeeTerritoryAjaxServer.aspx page. This page essentially contains only the Page
attribute tag and no HTML. We return the response from the page with the content-type of "text/xml"
.
Here is the code-behind C# code of the EmployeeTerritoryAjaxServer.aspx page:
EmployeeTerritoryAjaxServer.aspx.cs
private void Page_Load(object sender, System.EventArgs e)
{
string ID = Request["EmployeeID"];
if(ValidateID(ID))
{
if(ID.Trim() != "" && ID != null)
{
try
{
SqlConnection conn = new
SqlConnection(ConfigurationSettings.AppSettings["CONN_STRING"]);
conn.Open();
SqlCommand command =
new SqlCommand("SELECT TERRITORYID, RTrim(TERRITORYDESCRIPTION) as" +
"TERRITORYDESCRIPTION FROM TERRITORIES WHERE TERRITORYID IN" +
"(SELECT TERRITORYID FROM EMPLOYEETERRITORIES WHERE EMPLOYEEID="
+ ID + ") for xml auto", conn);
SqlDataReader dr = command.ExecuteReader();
System.Text.StringBuilder sbXML =
new System.Text.StringBuilder();
if(dr.HasRows)
{
while(dr.Read())
{
sbXML.Append(dr.GetString(0).Trim());
}
Response.ContentType = "text/xml";
Response.Write("<EmployeeTerritories>" +
sbXML.ToString() + "</EmployeeTerritories>");
}
else
{
Response.Write("");
}
}
catch
{
Response.Write("");
}
}
else
{
Response.Write("");
}
}
}
Page_Load Event
The Page_Load
event is straightforward. One thing to note here is that I am using the FOR XML AUTO
option of SQL Server 2000 for getting the query results as XML. I append a <Territories>
tag around this result XML and send the response back to the caller. If you are using a different data source that does not support automatic XML generation, you can build your own XML nodes and write the response.
ValidateID method
private bool ValidateID(string ID)
{
for(int Count=0;Count<ID.Length;Count++)
{
if (!System.Char.IsNumber(ID, Count))
{
return false;
}
}
return true;
}
The ValidateID()
method validates the ID that is sent by the caller and returns a boolean value stating if it is valid or not.
Executing Sample Code
Before running the sample application, you will need to create a virtual directory of the AJAXDropDown folder inside the AJAX___ASPNET folder after unzipping. Also, make sure that you have an instance of SQL Server 2000 available and you have the Northwind database in that. Open the solution in Visual Studio and modify the following element in your web.config file.
<add key="CONN_STRING" value="data source=<your data source here >;
Database=northwind;User=<user name>;PWD=<password>"/>
Build the application before running it.
Try selecting an employee, you will see the Territory dropdown filling with the corresponding territories as in the figure below:
Summary
In this article, we discussed about AJAX and how it can be used in a web application to improve user experience. We also saw how we saved on the amount of data being transmitted over the network by sending the response as XML and not the whole rendering HTML code. There are various such applications which can benefit by using AJAX. At the same time, it is up to us to decide where we want to make use of this concept depending on various factors viz., security, environment, performance etc.
I would like to thank my colleague Rashmi Ramachandra for making those diagrams you saw earlier in the article.
I hope this made an interesting reading for you. You can send me your comments at srinivas.alagarsamy@gmail.com. Thank you!!