Introduction
This is an AJAX based WhiteBoard application. Typically, unlike their desktop based counterparts, web applications need to be designed to use optimal server resources. This is one of the reasons for AJAX being popular. Here, I demonstrate a powerful use of AJAX to make communication possible between two or more clients.
So, what can this do? As I said, this is a WhiteBoard application. Users are provided with certain drawing tools, color selections, etc. The basic idea is to share the user drawings among all the clients. All the users viewing the main page can participate in the drawing and share it with all the other users. Clearing local canvas, server data, etc., is also possible.
Background
A couple of years ago, I was assigned the task of researching the capabilities of AJAX. Being a tech freak, I was hell bent on convincing the client to use the then new and hot technology. I wasted a few days thinking about the right approach to the design and GUI. I wanted the application to look something like a desktop application and yet be executed in a browser. My aim was to avoid postbacks completely (to impress the client). I was scampering through every result on Google, reading every article on AJAX, so as to get the perfect application as a demo.
I happened to see a JavaScript code (wz_jsgraphics.js) by Walter Zorn Link . It was then that the idea of building a web based WhiteBoard struck me. I give full credit to Walter for his excellent JavaScript code. Once I learned using his script, it was just a matter of days before my own Web based Whiteboard was up and running.
The Basics...
As in all AJAX based applications, I have a main page that is displayed to the user. A page on the server handles all the requests and responses to this page. All the drawing part is done using Walter's JavaScript code. I designed a messaging mechanism to identify the client requests and serve the clients accordingly. I have divided the JavaScript into various files, depending on the functionality. Understanding the application flow is fairly simple.
Notice that at any point, the client initiates a request for data. The server only acts as a central place to hold the data (using an ArrayList
). The client sends a number indicating the start position from where it is requesting the data.
Using the Code
You can find a detailed explanation of the graphics library on Walter's website.
I will start with a brief explanation on the contents of every file.
JavaScript files
- ajaxfunctions.js - Contains functions that send and receive data from the server. It also contains JavaScript functions to update the screen canvas after getting the data from the server. Functions for selection of tools and colors, sending a request to clear server data, etc., are also placed in this file.
- ajaxobjects.js - Contains functions to create an AJAX object and send the
XMLHTTP
request. - common.js - Contains functions I thought would cramp the main page. Mostly 1 - 2 line functions which are reused.
- constants.js - Contains all the client-side constants used for the application. Classified as colors, tools, query string, etc.
- drawing.js - Contains functions that will handle the drawing and also initiate the contact with the server.
- wz_jsgraphics.js - This is, of course, the most important drawing library without which this application is a stub. It contains all the functions that actually draw the shapes on the client-side canvas.
ASP.NET files (Server-side C#)
- Constants.cs - This contains all the server side constants.
- HandleData.aspx - This is the file where all the AJAX requests are posted to. It has functions to decide what the client wants and then respond with the user's data.
- index.aspx - This is the main form with all the controls. It is just a service user. This page serves as a GUI which initialises the sharing process.
Putting it all together, this is a simple explanation:
- User "A" loads the "index.aspx" page on his screen.
- He selects a tool and a color and starts designing on his screen.
- One more user, User "B", visits the "index" page.
- Since "B" is a new user, he gets all the data from the server, and user A's canvas is replicated on user B's screen.
- Now, AJAX comes into picture. To keep both the users in sync, AJAX scripts run at a regular interval.
- When a user makes a change in the selected tool, color, or draws something on the screen, an AJAX script updates the server.
- The steps are repeated with any number of users.
- I think, ideally, in a LAN/WAN environment, 15 users can be easily accommodated.
Let's begin the detailed explanation of some of the important client-side functions:
function setData() {
var requestUrl;
requestUrl = CONST_URL_PATH;
requestUrl += getToolString();
xmlHttp = GetXmlHttpObject(setDataHandler);
xmlHttp_Send_Request(CONST_METHOD_POST, xmlHttp, requestUrl);
}
function getData() {
var requestUrl;
requestUrl = CONST_URL_PATH;
requestUrl += CONST_QUERY_PARAM + CONST_QUERY_PARAM_GETDATA;
requestUrl += '&' + CONST_QUERY_READ_FROM + getValue('ReadFrom');
xmlHttp = GetXmlHttpObject(getDataHandler);
xmlHttp_Send_Request(CONST_METHOD_POST, xmlHttp, requestUrl);
}
function getToolString() {
var strOut;
strOut = CONST_QUERY_PARAM + CONST_QUERY_PARAM_SETDATA;
strOut += '&' + CONST_QUERY_ACTION + CurrentTool;
strOut += '&' + CONST_QUERY_COLOR + CurrentColor;
strOut += '&' + CONST_QUERY_STROKE + CurrentStroke;
strOut += '&' + CONST_QUERY_STARTX + StartX;
strOut += '&' + CONST_QUERY_STARTY + StartY;
strOut += '&' + CONST_QUERY_EndX + EndX;
strOut += '&' + CONST_QUERY_EndY + EndY;
return strOut;
}
The setData
function calls the getToolString
function before contacting the server. The getToolString
function constructs a string in the required format to be sent to the server. Then, the setData
function brings the AJAX object into use and sends a string of data to the server. The getData
function creates a request string and requests data from the server, again using AJAX objects.
Notice that in both the functions the callback data handler is different. (Data handler is a function that decides what to do with the data returned from the server.)
function setDataHandler() {
if (xmlHttp.readyState == CONST_INT_READY_STATE ||
xmlHttp.readyState == CONST_READY_STATE){
var strText = xmlHttp.responseText;
if (strText.indexOf(CONST_ERROR) >= 0) {
errHandler(strText.split(CONST_INTERNAL_SEPERATOR)[1]);
} else {
errHandler(CONST_MSG_DATASET);
setValue('ReadFrom', parseInt(getValue('ReadFrom')) + 1);
}
toggleLoading(false);
}
}
The setDataHandler
function first checks if the browser is in ready state. Then, it reads the returned ResponseText
from the AJAX object, checks for any errors, and then resets the status bar message accordingly. In the end, it also sets the current instruction number that it has received from the server.
function getDataHandler() {
if (xmlHttp.readyState == CONST_INT_READY_STATE ||
xmlHttp.readyState == CONST_READY_STATE){
var strText = xmlHttp.responseText;
if (strText.indexOf(CONST_ERROR) >= 0) {
errHandler(strText.split(CONST_INTERNAL_SEPERATOR)[1]);
} else {
errHandler(CONST_MSG_DATAGET);
var iNum = updateScreen(strText);
setValue('ReadFrom', parseInt(getValue('ReadFrom')) + iNum);
}
toggleLoading(false);
window.setTimeout('getData()', CONST_DATA_TIMEOUT);
}
}
The getDataHandler
function also checks if the browser is in ready state. It then reads the returned ResponseText
from the AJAX object, checks for any errors, and then resets the status bar message accordingly. Unlike the setDataHandler
function, this function first updates the screen (updateScreen
function call) and then sets the current instruction number.
function updateScreen(strText) {
var strLines = strText.split(CONST_LINE_SEPERATOR);
var i;
if (strLines.length > 0) {
for (i = 0; i < strLines.length; i++) {
var strMainText = strLines[i].split(CONST_INTERNAL_SEPERATOR);
if (strMainText[0] == CONST_ACTION_TOOL_CLEAR) {
clearCanvas();
} else {
setTool(strMainText[0]);
setColor(strMainText[1]);
setStroke(parseInt(strMainText[2]));
setStart(parseInt(strMainText[3]), parseInt(strMainText[4]));
setEnd(parseInt(strMainText[5]), parseInt(strMainText[6]));
drawPic();
}
}
return i;
}
return 0;
}
The updateScreen
function splits the received text based on predefined special characters. The first split is actually the split of instructions. The second split is the inter instruction split (to get the tool, color, start and end, etc.). After breaking the data, it draws the required image on screen (call to the drawPic
function). Now, let us have a look at the server side. The server side is handled by the HandleData.aspx page load event. Depending on the request, the getData
or setData
function is called.
private void getData()
{
string strReadFrom =
Request.QueryString.Get(Constants.CONST_QUERY_READ_FROM);
if (strReadFrom != null && strReadFrom.Length > 0)
{
StringBuilder strText = new StringBuilder("");
int iFrom = Int32.Parse(strReadFrom);
ArrayList arrData =
(ArrayList)Application[Constants.CONST_APP_DATA_ARRAY_NAME];
if (iFrom >= arrData.Count)
{
Response.Write(Constants.CONST_ERROR +
Constants.CONST_INTERNAL_SEPERATOR +
Constants.CONST_ERROR_ALREADY_UPDATED);
return;
}
strText.Append(arrData[iFrom]);
for (int i = iFrom + 1; i < arrData.Count; i++)
{
strText.Append(Constants.CONST_LINE_SEPERATOR + arrData[i]);
}
Response.Write(strText.ToString());
}
else
{
Response.Write(Constants.CONST_ERROR +
Constants.CONST_INTERNAL_SEPERATOR +
Constants.CONST_ERROR_PARAM);
}
}
This getData
function first reads the counter from where the client is requesting data. It then loops through the server-side ArrayList
that stores the instructions. Here it also checks for existence of new data. In case new data exists, the output string is created and then returned using a Response.Write
statement. (This is the string that is returned to the calling AJAX function.)
private void setData()
{
ArrayList arrData = (ArrayList)Application
[Constants.CONST_APP_DATA_ARRAY_NAME];
string strAction = Request.QueryString.Get(Constants.CONST_QUERY_ACTION);
string strColor = Request.QueryString.Get(Constants.CONST_QUERY_COLOR);
string strStroke = Request.QueryString.Get(Constants.CONST_QUERY_STROKE);
string strStartX = Request.QueryString.Get(Constants.CONST_QUERY_STARTX);
string strStartY = Request.QueryString.Get(Constants.CONST_QUERY_STARTY);
string strEndX = Request.QueryString.Get(Constants.CONST_QUERY_ENDX);
string strEndY = Request.QueryString.Get(Constants.CONST_QUERY_ENDY);
if (strAction != null && strAction.Length > 0)
{
if (strAction.Equals(Constants.CONST_ACTION_TOOL_CLEAR))
{
arrData.Add(strAction + Constants.CONST_INTERNAL_SEPERATOR);
}
else
{
if (strColor != null && strStroke != null &&
strStartX != null && strStartY != null &&
strEndX != null && strEndY != null &&
strColor.Length > 0 && strStroke.Length > 0 &&
strStartX.Length > 0 && strStartY.Length > 0 &&
strEndX.Length > 0 && strEndY.Length > 0)
{
arrData.Add(strAction + Constants.CONST_INTERNAL_SEPERATOR +
strColor + Constants.CONST_INTERNAL_SEPERATOR +
strStroke + Constants.CONST_INTERNAL_SEPERATOR +
strStartX + Constants.CONST_INTERNAL_SEPERATOR +
strStartY + Constants.CONST_INTERNAL_SEPERATOR +
strEndX + Constants.CONST_INTERNAL_SEPERATOR +
strEndY);
Response.Write("");
}
else
{
Response.Write(Constants.CONST_ERROR +
Constants.CONST_INTERNAL_SEPERATOR +
Constants.CONST_ERROR_PARAM);
}
}
}
else
{
Response.Write(Constants.CONST_ERROR +
Constants.CONST_INTERNAL_SEPERATOR +
Constants.CONST_ERROR_PARAM);
}
}
The setData
function does exactly the opposite task. It gets all the parameters from the request and makes a string out of it. It then adds the string to the server-side ArrayList
. In case of errors, it writes the error using a Response.Write
statement. Other places of concern are the function calls that are set on the index.aspx page. You can easily add new colors and new functionality. Just add the relevant constants and incorporate the required functions. The messaging architecture remains the same.
Points of Interest
A simple messaging architecture can be used to linkup many computers. When the application is running, it seems to be a very huge task. Since we are doing the bulk of the task on the client side, the user experience is also great.
History
- 2007/10/01 - Added the article (waiting for suggestions).
- 2009/10/26 - Updated the functionality to send text messages. The new version was developed in VS 2008.