A Flexible AJAX Framework

AJAX = Asyncronous (but not always), JavaScript, XML (Text or JSON). These technologies have been around almost as long as the Internet itself, but have only recently been used together to change the way that web applications are built.

XMLHTTPRequest is an API used mainly by browser-based Javascript applications to send and retrieve data from servers. It was developed originally by Microsoft for Exchange Server’s Outlook Web Access, but it has since been widely supported in browsers and is the heart of AJAX dynamic Web applications.

I’ve messed with many of the frameworks available online, and while they are very good, I’ve routinely found issues or features that they lack. Over time I’ve cobbled together my own framework, I fully expect this to change over time, but publish it here for your consideration!

The XSS methods (and “otherHost” variable) are provided as MSIE6 allows for Cross-site Scripting here. MSIE7 and Mozilla will indicate errors if connections are attempted to a ‘non-originating host’. I’ve exploited this vulnerability in a few applications, as such I provide the test for you too!

NOTE: The example response program is written in PHP, but should be easily ported to any other language.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>

<head>

<title>AJAX Example</title>

<script type=”text/javascript”>

var xbusy = true;

var otherHost = ‘http://www.giantgeek.com’; // DO NOT USE the ending slash!

var xhr = null;

/**

* Check for existance on DOM browsers (Mozilla, etc.)

* @return xhr

*/

function ajaxCheckDOM(){

var myxhr = null;

if(window.XMLHttpRequest) {

try {

myxhr = new XMLHttpRequest();

}

catch(e) {}

}

return myxhr;

}

/**

* Check for existance on Windows/MSIE (prior to MSIE7 which is now DOM)

* Evaluate using – new ActiveXObject(“Microsoft.XMLDOM”);

* @return xhr

*/

function ajaxCheckActiveX(){

var myxhr = null;

//if(window.ActiveXObject){

try {

myxhr = new ActiveXObject(“Msxml2.XMLHTTP”);

}

catch(e) {

try {

myxhr = new ActiveXObject(“Microsoft.XMLHTTP”);

}

catch(e) {}

}

//}

return myxhr;

}

/**

* This is a default response hook for AJAX, you SHOULD create your own as it’s only for DEMO

* @param obj – the clicked item

* @param customObj – user defined

*/

function ajaxResponseHook(obj,customObj){

var txt = xhr.responseText;// NOTE: xhr.responseXML is also valid

popStatus(‘AJAX Response value [‘ + txt + ‘|’ + customObj +’]’,”);

}

/**

* This is a common Response handler AJAX, you SHOULD NOT need to modify, use a ‘custom’ ajaxhook function to process the responses.

* @param obj – the clicked item

* @param ajaxhook Function (default will be used if undefined)

* @param customObj (optional – left for developer implementation, passed to the ajaxhook)

*/

function ajaxResponse(obj,ajaxhook,customObj){

var status=”;

if(xhr!=undefined){

var state=xhr.readyState;

if(state!=undefined){

if(state==4){

var code = xhr.status;

status = xhr.statusText;

if(code==200){

popStatus(‘AJAX Response Received.’,”);

if(ajaxhook==undefined){

ajaxhook = function(){ ajaxResponseHook(obj,customObj); }

}

ajaxhook(obj,customObj);

ajaxBusy(obj,false);

} else {

popStatus(‘AJAX Error ‘ + code + ‘ ‘ + status + ‘.’,”);

}

ajaxBusy(obj,false);

xhr=null; /* MSIE6 leak fix */

}

}

}

}

/**

* NOTE: MSIE6-7 supports XSS url’s (be careful!)

* @param obj – the clicked item

* @param url – the GET url params (FQDN)

* @param async (true or false) – false will LOCK browser until response – use cautiously!

* @param callback Function – allows for customized response handling

*/

function ajaxObj(obj,url,async,callback){

if(xbusy == true){

popStatus(‘AJAX BUSY, Please Retry.’,”);

} else {

if(callback==undefined){

callback = function(){ ajaxResponse(obj,hook,”); }

}

ajaxInit(obj,url,async,callback);

}

}

/**

* This is a Non-Caching implementation of ajaxObj() to show how you can avoid the MSIE caching issue.

* NOTE: You can use similar approaches to cache per page or session.

* @param obj – the clicked item

* @param url – the GET url params (FQDN)

* @param async (true or false) – false will LOCK browser until response – use cautiously!

* @param callback Function – allows for customized response handling

*/

function ajaxObjNoCache(obj,url,async,callback){

var cacheBuster=uniqueUrl(url);// damn MSIE!

ajaxObj(obj,cacheBuster,async,callback)

}

/**

* Initializes the AJAX operation

* @param obj – item clicked

* @param url – FQDN

* @param async (true/false) – for locking

* @param callback Function – provided for customization.

*/

function ajaxInit(obj,url,async,callback){

xhr=ajaxCheckDOM();

if(!xhr){

xhr=ajaxCheckActiveX();

}

if(xhr){

ajaxBusy(obj,true);

popStatus(‘AJAX Start.’,”);

xhr.onreadystatechange = callback; // method call

try {

xhr.open(‘GET’,url,async); /* POST */

//xhr.setRequestHeader(‘Content-Type’,’application/x-www-form-urlencoded’);

xhr.send(”); /* null */

}

catch(e)

{

if(xhr){

var code = xhr.status;

var status = xhr.statusText;

popStatus(‘AJAX Client SECURITY ‘ + navigator.appName +’ ‘+ navigator.appVersion + ‘ ‘ + code + ‘ ‘ + status + ‘.’,”);

}

}

ajaxBusy(obj,false);

} else {

popStatus(‘AJAX Client ERROR.’,”);

}

}

function ajaxBusy(obj,yn){

if(yn){

swapStyleObj(obj,’idle’,’busy’);

} else {

swapStyleObj(obj,’busy’,’idle’);

}

xbusyInd(yn,”);

}

function xmillis(){

return new Date().getTime();

}

function swapStyleObj(obj,oldCSS,newCSS){

if(obj!=undefined) {

var current=obj.className;

if(current!=undefined){

var txtOld = current.replace(newCSS,’ ‘);//no doubles

var txtMid = txtOld.replace(oldCSS, ‘ ‘);

var txtNew = (txtMid + ‘ ‘ + newCSS);

obj.className = txtNew;

} else {

obj.className = newCSS;

}

}

}

function xbusyInd(valnew, msg){

xbusy = valnew;

if(xbusy==true){

document.body.style.cursor=’wait’;

if(msg != ”){

top.window.defaultStatus=msg;

popStatus(msg,”);

}

showDiv(‘throbber’);

//blockScreen();

} else {

document.body.style.cursor=’default’;

hideDiv(‘throbber’);

//unBlockScreen();

}

}

function showDiv(id) { //show a div

var obj=xgetHelper(id);

if(obj!=null){ obj.style.display=”block”;}

//xrestart();

}

function hideDiv(id) { //hide a div

var obj=xgetHelper(id);

if(obj!=null){ obj.style.display=”none”;}

//xrestart();

}

function popStatus(txt,title){

popit(‘statusdyn’,’statusarrow’,’statusul’,’statusdiv’,txt,false,title,”);

}

function popit(dynId,arrowId,ulId,divId,txt,expand,title,cssCls){

popText(ulId,txt,title,cssCls);

showDiv(divId);

if(expand == true){

var arrowObj=xgetHelper(arrowId); if(arrowObj!=null){ arrowObj.style.backgroundPosition=’2px -106px’; }

showDiv(dynId);

}

}

function popText(id,txt,title,cssCls){

var obj = xgetHelper(id);

if(obj != null){

var oldHTML = obj.innerHTML;

var cls=”; if(cssCls!=”){ cls=’ class=”‘ + cls +'”‘; }

var htm = ‘<‘+’li’+ cls +’>’ + txt + ‘<‘+’/’+’li’+’>’ + oldHTML;

obj.innerHTML = htm;

}

}

function xgetHelper(id){

var obj = null;

try {

obj = document.getElementById(id);

} catch(z) {

var dummy=alert(“Error:” + z);

}

return obj;

}

function arrowTog(objectID,arrow) {

var obj = xgetHelper(objectID);

if(obj!=null){

if (obj.style.display ==’block’) {

arrow.style.backgroundPosition = ‘2px -21px’;}

else {arrow.style.backgroundPosition = ‘2px -106px’;}

objTog(objectID);

}

return false;

}

function objTog(objectID) {

var obj = xgetHelper(objectID);

if(obj!=null){

if (obj.style.display ==’block’) obj.style.display=’none’;

else {obj.style.display=’block’;}

}

return false;

}

function headTog(objectID,arrow) {

var obj = xgetHelper(objectID);

if(obj!=null){

if (obj.style.display ==’block’) {

arrow.style.borderBottomWidth = ‘1px’;}

else {arrow.style.borderBottomWidth = ‘0px’;}

arrowTog(objectID,arrow);

}

return false;

}

/*

* adds timestamp to URLs to make them unique

* @param URL String

*/

function uniqueUrl(x){

return urlAppender(x,’.cache’,xmillis());

}

/*

* helps to add parms to the url

* @param URL String

* @param aname String

* @param avalue String

*/

function urlAppender(x,aname,avalue){

var delim = “?”;

if(x.indexOf(“?”) >=0) { delim = “&”; }

return x + delim + aname + ‘=’ + avalue;

}

function xload(){

hideDiv(‘throbber’);

xbusy=false;

}

function testAjax(obj){

var callback=function(){ ajaxResponse(obj,null,’testAjax’); }

var x = ajaxObjNoCache(obj,’/ajax.php’,true,callback);

}

function testAjaxParms(obj){

var callback=function(){ ajaxResponse(obj,null,’testAjaxParms’); }

var x = ajaxObjNoCache(obj,’/ajax.php?testing=Y’,true,callback);

}

function testAjaxXSS(obj){

var callback=function(){ ajaxResponse(obj,null,’testAjaxXSS’); }

var x = ajaxObjNoCache(obj,otherHost+’/ajax.php’,true,callback,otherHost);

}

function testAjaxHook(obj){

var hook=function(){ customAjaxHook(obj,’ajaxhookTestMessageObect’); }

var callback=function(){ ajaxResponse(obj,hook,’testAjaxHook’); }

var x = ajaxObjNoCache(obj,’/ajax.php’,true,callback);

}

function testAjaxHookXSS(obj){

var hook=function(){ customAjaxHook(obj,’ajaxhookXSSMessageObect’); }

var callback=function(){ ajaxResponse(obj,hook,’testAjaxHookXSS’); }

var x = ajaxObjNoCache(obj,otherHost+’/ajax.php’,true,callback);

}

/**

* This is a custom implemenation, the customObj COULD be used for anything (perhaps delay measurement!)

* @param obj – the clicked item

* @param customObj – user defined

*/

function customAjaxHook(obj,customObj){

var xml = xhr.responseXML;// NOTE: xhr.responseText is also valid

var tmp = ‘DEMO customAjaxHook [‘ + customObj + ‘]\n’ + xml;

alert(tmp);

}

</script>

<style type=”text/css”>

.busy { color:red; }

.idle { color:black; }

</style>

</head>

<body onload=”xload();”><!– onbeforeunload=”alert(‘before’);” onunload=”alert(‘after’);” –>

<div id=”throbber”>WORKING!</div>

<div id=”statusdiv” class=”dyn” style=”display:none;”>

<h3><a id=”statusarrow” onclick=”headTog(‘statusdyn’,this);” href=”javascript:void(0);”>Status</a></h3>

<fieldset id=”statusdyn” style=”display:block;background-color:#ffffcc;”>

<div id=”statusBox” class=”box”>

<ul id=”statusul” class=”error”>

<li id=”ajaxStatus” style=”list-style:none;display:none;”>FILLER</li>

</ul>

</div>

</fieldset>

</div>

<h3><a href=”javascript:void(0);” onclick=”testAjax(this);”>TEST</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxParms(this);”>TESTPARMS</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxXSS(this);”>TESTXSS</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxHook(this);”>TESTHOOK</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxHookXSS(this);”>TESTHOOKXSS</a></h3>

<p><a href=”index.php”>RELOAD</a></p>

</body>

</html>

Response program (ajax.php)

<?php

header(“Cache-Control: no-store”);

header(“Charset: UTF-8”);

header(“Content-Type: text/xml”);

echo(“<?xml version=\”1.0\” encoding=\”UTF-8\”?>\n”);

?>

<test><?php echo( gmdate(“D, d M Y H:i:s”) ) ?> </test>

What are the MSIE Bugs you might ask… these are all in the ActiveX implementation of XmlHttpRequest, they are:

  1. Caching – for some reason, unless you define a ‘unique’ URL for each request, MSIE6 will respond from the cache, even if the server is unreachable!
  2. XSS – I discussed this above, MSIE6 will allow you to connect to ANY HOST with AJAX!
  3. Memory leak – if you don’t nullify the XHR object (for MSIE6), the application will leak memory.

Cheers!

Href Links as HTTP POST (complex DOM solution)

The earlier post, while easy to implement, has some well known security issues. Now lets get around them. First we’ll remove the FORM from the HTML itself, and instead build it dynamically and insert it into the BODY via the DOM and then submit it with JavaScript.

Again, if you’ve already implemented my prior solutions, this is just a small code refactor.

<html>
<head>
<title>Example FORM Post - DOM</title>
<script type="text/javascript">
/*
* Uses "location.replace()" vs. "location.href()" for all valid links.
* 'replace' has side-effect of 'restricting' back-button, or 'location'
* @param obj Object clicked
* @param x URL
*/
function xlinkObj(obj,url){
//Consider replacing w/ "return xlinkFrm(obj,url); " if you want POST behavior.
window.location.replace(uniqueUrl(url));
return false;
}
/*
* uses a FORM for the requested URL, could be POSTed!
* This is a simple solution, more complex solution COULD build the FORM and then parse attributes into INPUT's
* NOTE: you probably SHOULD NOT use this for external links, unless you intend for them to receive your params!
* @param obj Object clicked (NOT USED in this Example)
* @param x URL
*/
function xlinkFrm(obj,x){
var url=uniqueUrl(x);
var frmObj=xlinkFrmHelp(url);
if(frmObj!=null){
frmObj.submit();
}else{
alert('ERROR!');
}
return false;
}
/*
* Expects URL with queryString as param href
* @param x URL
* @return obj Object of the generated FORM
*/
function xlinkFrmHelp(x){
var rc=null;
try{
var ar = x.split("?");
var act = ar[0];
var str = ar[1];
var id="frm" + xmillis();
var oFORM=document.createElement("form");
oFORM.setAttribute("id",id);
oFORM.setAttribute("method","post");
oFORM.setAttribute("action",act);
if(str!=null){
var parms=str.split('&');
for(i=0; i < parms.length; i++){
var parm=parms[i];
var pair=parm.split('=');
var oINPUT=document.createElement("input");
oINPUT.setAttribute("type","hidden");
oINPUT.setAttribute("name",pair[0]);
oINPUT.setAttribute("value",pair[1]);
oFORM.appendChild(oINPUT);
}
}
var oBODY=document.getElementsByTagName("body")[0];
oBODY.appendChild(oFORM);
rc=oFORM;
}catch(e){
alert("Error"+e);
}
return rc;
}
/*
* generates a timestamp as a number
*/
function xmillis(){
return new Date().getTime();
}
/*
* adds timestamp to URLs to make them unique
* @param URL String
*/
function uniqueUrl(x){
return urlAppender(x,'time',xmillis());
}
/*
* helps to add parms to the url
* @param URL String
* @param aname String
* @param avalue String
*/
function urlAppender(x,aname,avalue){
var delim = "?";
if(x.indexOf("?") >=0) { delim = "&"; }
return x + delim + aname + '=' + avalue;
}
/*
* Abstracts "document.getElementById()" with appropriate error handling.
* @param id String
* @returns Object (NULL when not found!)
*/
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
var dummy=alert("Error:" + z);
}
return obj;
}
</script>
<a href="javascript:void(0);" onclick="return xlinkObj(this,'index.php');">REFRESH</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'index.php');">TEST-POST</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'index.php?a=b');">TEST-POST2</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'http://www.skotfred.com/hello');">TEST-XSS</a>
</body>
</html>

Cheers!

Href Links as HTTP POST (simple solution)

Another interesting challenge, The standard <a href=”…”></a> style link performs an HTTP GET, however you might want to perform a POST in some cases. HTML does not natively support this behavior, but it can be accomplished in JavaScript. If you have already implemented some of my security ‘hacks’ from previous posts this is only a small change. As usual I’ve included the minimum code required for this task in the example, but you should be able to easily merge the different features back into this!

Later on (in a different post), I’ll expand this to make it even more secure as this solution simply puts all of the URL into the FORM ‘action’ attribute and it would be better to pass them in the FORM body itself to hide them from the URL shown in the browser.

<html>
<head>
<title>Link to FORM POST simple example</title>
<script type="text/javascript">
/*
* Uses "location.replace()" vs. "location.href()" for all valid links.
* 'replace' has side-effect of 'restricting' back-button, or 'location'
* @param obj Object clicked (NOT used in this example)
* @param x URL
*/
function xlinkObj(obj,url){
//Consider replacing w/ "return xlinkFrm(obj,url); " if you want POST behavior in all cases!
window.location.replace(uniqueUrl(url));
return false;
}
/*
* uses a FORM for the requested URL, could be POST'ed!
* This is a simple solution, using an existing empty FORM on the page.
* A more complex and secure solution COULD build the FORM dynamically and then parse attributes into INPUT's
* NOTE: you probably SHOULD NOT use this for external links, unless you intend for them to receive your params!
* @param obj Object clicked (NOT used in this example)
* @param x URL
*/
function xlinkFrm(obj,x){
var frmObj=xgetHelper('frmXlink');
if(frmObj!=null){
frmObj.action=uniqueUrl(x);
frmObj.submit();
}else{
alert('ERROR!');
}
return false;
}
/*
* generates a timestamp as a number
*/
function xmillis(){
return new Date().getTime();
}
/*
* adds timestamp to URLs to make them unique
* @param URL String
*/
function uniqueUrl(x){
return urlAppender(x,'time',xmillis());
}
/*
* helps to add parms to the url
* @param URL String
* @param aname String
* @param avalue String

*/
function urlAppender(x,aname,avalue){
var delim = "?";
if(x.indexOf("?") >=0) { delim = "&"; }
return x + delim + aname + '=' + avalue;
}
/*
* Abstracts "document.getElementById()" with appropriate error handling.
* @param id String
* @returns Object (NULL when not found!)
*/
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
var dummy=alert("Error:" + z);
}
return obj;
}
</script>
</head>
<body>
<form id="frmXlink" action="#" method="post"></form>
<a href="javascript:void(0);" onclick="return xlinkObj(this,'index.html');">REFRESH</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'index.html');">TEST-POST</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'index.html?a=b');">TEST-POST PARMS</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'http://www.giantgeek.com/hello');">TEST-XSS</a>
</body>
</html>

This just uses an “empty” FORM in the page and uses the new ‘xlinkFrm()’ method to copy the URL to the FORM ‘action’.

Like i said, this is a simple solution as the params are still on the URL making them less secure. I’ll be refactoring it to parse the params to dynamically build the FORM (that will no longer be hardcoded).

Cheers!

Clientside Session Timeout’s

There comes a time in web application development that you need to ‘timeout’ idle users. This comes in a variety of ways, here’s a few common reasons that you may desire this activity.

  • Security – you don’t want to leave sensitive data on a users screen when they’ve gone to lunch or left for the day.
  • Server Resources – persisting/keeping an active ‘session’ available on the server takes resources (the exact type varies, but this is usually database, memory or file resources)
  • Server ‘enforced’ session timeout’s and the potential errors and lost data experienced by the users in that circumstance.

My personal approach to this has evolved over time, here’s a brief synopsis:

  1. Use standard server-side session timeout, often leading to a bad user experience when they loose data on a form submit.
  2. Use META REFRESH…where timeout is in seconds, in this example it’s 60 seconds (1 minute).
    <meta http-equiv="refresh" content="60;url=http://www.giantgeek.com/" />
  3. Use javascript 'timeout' (problem is that this is not 'measureable')
    
    <script type="text/javascript">
    setTimeout("javascript:myTimeout();",minutes*60000); // code minutes
    </script>
  4. Use javascript countdown timer and custom code event.

<html>
<head>
<title>Timeout example</title>
<script type=”text/javascript”>
var build=’testing’;
var timerID = 0;
var loadTime = null;
var stopTime = null;
function xload(){
loadTime=grvMillis();
grvWindowStatus(build);
grvSetTimeout();
}
function xclose(){
grvWindowStatus(”);
}
function grvMillis(){
return new Date().getTime();
}
// Start timer
function grvTimerUpdate(){
timerID = grvTimerClear(timerID);
if(loadTime == null){
loadTime=grvMillis();// Start Time
}
// Calculate Current Time in seconds
var timeNow = grvMillis();

var think = calcMinSec( calcTimeDiff(timeNow,loadTime) );
var remain = calcMinSec( calcTimeDiff(stopTime,timeNow) );
grvWindowStatus(build + ” ” + think + ” ” + remain );
timerID = setTimeout(“grvTimerUpdate()”,1000);
}
function calcMinSec(diff){
var mm = removeDecimal(diff/60);
var ss = zeroPad(removeDecimal(diff-(mm*60)),2);
return (mm + “:” + ss);
}
function calcTimeDiff(tmpStart,tmpStop){
var diff = (tmpStart – tmpStop)/1000;
return diff;
}
function removeDecimal(val){
var rc=””;
val = val + “”;
if(val!=””){
var pos = val.indexOf(“.”);
if(pos > -1){
rc=val.substr(0,val.indexOf(“.”));
} else {
rc=val;
}
}
return rc;
}
function zeroPad(x,sz){
x = x + “”;
while(x.length < sz){
x = “0” + x;
}
return x;
}
function grvTimerClear(x){ // this clears a timer from the queue
if(x){
clearTimeout(x);
x = 0;
}
return x;
}
function grvSetTimeout(){
var min=45; xID=grvTimeout(“javascript:grvTimeoutUSER()”,min); // EXAMPLE: this could be conditional!
stopTime = grvCalculateTimeout(min);
grvTimerUpdate();
}
function grvCalculateTimeout(mins){
var timeNow = grvMillis();
var exp = timeNow + (mins*60*1000);
var timeExp = new Date(exp).getTime();
return timeExp;
}
function grvTimeout(x,minutes){ // this sets a timer(request) in a queue
return setTimeout(x,minutes*60000);
}
function grvTimeoutUSER(){
alert(‘Session Inactivity Timeout [USER]’);
// DO WHAT YOU NEED TO HERE!}
function grvWindowStatus(txt){
window.defaultStatus=txt;
}
</script>
</head>
<body onunload=”xclose();” onload=”xload();”>
</body>
</html>

Another benefit of this last solution is that you also have access to the user “Think Time” and can therefore measure how long the user spends on a given page.

Cheers!

Clientside sorting of HTML TABLE in JavaScript

To save network bandwidth and server resources, it is often beneficial to to sorting of tabular data on the client. Here’s a workable solution that I’ve implemented several times.

Additionally, the need to make the solution ‘accessible’ to screen-reader technology and be backward compatible for users without JavaScript often become challenging.

There are some small “quirks” that you should be aware of…

  • The ‘sortTable’ method uses the ID of the ‘TBODY’. Working to remove this requirement through better use of the DOM.
  • Will likely rework this to use Prototype framework which should result in smaller code.
  • Future enhancement will add a class to the header indicating the sort order of the column(s).
  • Creating the ‘SPAN’ for dates to be sorted is best handled by a taglib.
  • Large TABLE’s can take a significant amount of time to sort on a client, so it’s sometimes better to use a server side solution. Developers should use their experience to make this judgment call.

Example code (XHTML logic removed as usual for brevity):

<html>
<head>
<title>Client Side TABLE sorting</title>
<style type=”text/css”>
/* SCROLL */
div.scroll {width:100%;overflow:scroll;}
html>body div.scroll {width:100%;overflow:scroll} /* fixes IE6 hack */
/*** TABULAR ***/
tr.even {background-color:#eee;}
th.first, td.first {border-width:0;}
th.memo {text-align:left;padding:0;}
td.sorted {background-color: #f0f0f0;}
th.sorted {background-color: #99f;}
tr.even td.sorted { background-color: #d0d0d0; }
table.sorted tr.error { background-color:red; }
table.sorted tr.scroll th { background-color:#99f; text-align:left;}
table.sorted tr.scroll th a.sorted { color:#fff; text-decoration:none;}
table.sorted tr.scroll th a.sorted:hover { color:#fff; text-decoration:underline;}
</style>
<script type=”text/javascript”>
//—————————————————————————–
// sortTable(id, col, rev,xcase)
//
// id – ID of the TABLE, TBODY, THEAD or TFOOT element to be sorted.
// col – Index of the column to sort, 0 = first column, 1 = second column, etc.
// rev – If true, the column is sorted in reverse (descending) order initially.
// xcase – makes sort NOT case sensitive.
//
// The following is an example of jsp code for setting up the reformatted copy of a date
// field to allow sorting by date:
//
// <fmt:formatDate var=”varSortDate” value=”${searchResult.dateOfBirth}” pattern=”${sortableDateTimePattern}” />
// <span title=”<c:out value=”${varSortDate}” />”><fmt:formatDate value=”${searchResult.dateOfBirth}” pattern=”${dateFormatPattern}” /></span> | etc.
//
// The above code creates html code that contains, for example, a line like the following:
//
// <span title=”19640101000000″>01/01/1964</span>
//
// This sort routing concatenates the title element and the text node
// content to sort on the following string:
//
// 1964010100000001/01/1964
//
// This effective ignores the date containing slashes and used the yyyyMMdd etc. value.
// Fields that are not dates should not use the span element and title attribute, unless
// it is desired to sort on something other than the text node content.
//—————————————————————————–
function sortTable(id, col, rev, xcase) {
// Get the table or table section to sort.
var tblEl = xgetHelper(id);
if(tblEl != null){
// The first time this function is called for a given table, set up an array of reverse sort flags.
if (tblEl.reverseSort == null) {
tblEl.reverseSort = new Array();
// Also, assume the column zero is initially sorted.
tblEl.lastColumn = 0; // was 1
}

// If this column has not been sorted before, set the initial sort direction.
if (tblEl.reverseSort[col] == null)
tblEl.reverseSort[col] = rev;

// If this column was the last one sorted, reverse its sort direction.
if (col == tblEl.lastColumn)
tblEl.reverseSort[col] = !tblEl.reverseSort[col];

// Remember this column as the last one sorted.
tblEl.lastColumn = col;
// Set the table display style to “none” – necessary for Netscape 6 browsers.
var oldDsply = tblEl.style.display;
tblEl.style.display = “none”;
// Sort the rows based on the content of the specified column using a selection sort.

var tmpEl;
var i, j;
var minVal, minIdx;
var testVal;
var cmp;
for (i = 0; i < tblEl.rows.length – 1; i++) {

// Assume the current row has the minimum value.
minIdx = i;
minVal = getTextValue(tblEl.rows[i].cells[col], xcase);

// Search the rows that follow the current one for a smaller value.
for (j = i + 1; j < tblEl.rows.length; j++) {
testVal = getTextValue(tblEl.rows[j].cells[col], xcase);
cmp = compareValues(minVal, testVal);
// Negate the comparison result if the reverse sort flag is set.
if (tblEl.reverseSort[col])
cmp = -cmp;
// If this row has a smaller value than the current minimum, remember its
// position and update the current minimum value.
if (cmp > 0) {
minIdx = j;
minVal = testVal;
}
}

// By now, we have the row with the smallest value. Remove it from the
// table and insert it before the current row.
if (minIdx > i) {
tmpEl = tblEl.removeChild(tblEl.rows[minIdx]);
tblEl.insertBefore(tmpEl, tblEl.rows[i]);
}
}

// Make it look pretty.
makePretty(tblEl, col);

// Restore the table’s display style.
tblEl.style.display = oldDsply;
}

return false;
}

//—————————————————————————–
// Functions to get and compare values during a sort.
//—————————————————————————–

// This code is necessary for browsers that don’t reflect the DOM constants
// (like IE).
if (document.ELEMENT_NODE == null) {
document.ELEMENT_NODE = 1;
document.TEXT_NODE = 3;
}

function getTextValue(el, xcase){
var i;
var s;
var spanTitleValue;

// Find and concatenate the values of all text nodes contained within the element.
s = “”;

for (i = 0; i < el.childNodes.length; i++) {
if (el.childNodes[i].nodeType == 1) {
if (el.childNodes[i].nodeName != null) {
if (el.childNodes[i].nodeName == “SPAN”) {
spanTitleValue = el.childNodes[i].getAttribute(“Title”);
s += spanTitleValue;
}
}
else {
}
// Use recursion to get text within sub-elements.
s += getTextValue(el.childNodes[i]);
}
else if (el.childNodes[i].nodeType == document.TEXT_NODE) {
s += el.childNodes[i].nodeValue;
}
else {
// Gets here when element is empty! <span title=””></span>
//alert(‘Error — Not element or text node’);
}
}
return normalizeString(s, xcase);
}

function compareValues(v1, v2) {

var f1, f2;
// If the values are numeric, convert them to floats.

f1 = parseFloat(v1);
f2 = parseFloat(v2);
if (!isNaN(f1) && !isNaN(f2)) {
v1 = f1;
v2 = f2;
}

// Compare the two values.
if (v1 == v2)
return 0;
if (v1 > v2)
return 1
return -1;
}

// Regular expressions for normalizing white space.
var whtSpEnds = new RegExp(“^\\s*|\\s*$”, “g”);
var whtSpMult = new RegExp(“\\s\\s+”, “g”);

function normalizeString(s, xcase) {

s = s.replace(whtSpMult, ” “); // Collapse any multiple whites space.
s = s.replace(whtSpEnds, “”); // Remove leading or trailing white space.

var rc = s;
if(xcase == true) {
rc = s.toUpperCase();
}
return rc;
}

//—————————————————————————–
// Functions to update the table appearance after a sort.
//—————————————————————————–

// Style class names.
var rowClsNm = “even”;
var colClsNm = “sorted”;

// Regular expressions for setting class names.
var rowTest = new RegExp(rowClsNm, “gi”);
var colTest = new RegExp(colClsNm, “gi”);

function makePretty(tblEl, col) {
var i, j;
var rowEl, cellEl;

// Set style classes on each row to alternate their appearance.
for (i = 0; i < tblEl.rows.length; i++) {
rowEl = tblEl.rows[i];
rowEl.className = rowEl.className.replace(rowTest, “”);
if (i % 2 != 0)
rowEl.className += ” ” + rowClsNm;
rowEl.className = normalizeString(rowEl.className);
// Set style classes on each column (other than the name column) to
// highlight the one that was sorted.
for (j = 0; j < tblEl.rows[i].cells.length; j++) { /* was j=2 */
cellEl = rowEl.cells[j];
cellEl.className = cellEl.className.replace(colTest, “”);
if (j == col)
cellEl.className += ” ” + colClsNm;
cellEl.className = normalizeString(cellEl.className);
}
}

// Find the table header and highlight the column that was sorted.
var el = tblEl.parentNode.tHead;
rowEl = el.rows[el.rows.length – 1];
// Set style classes for each column as above.
for (i = 2; i < rowEl.cells.length; i++) {
cellEl = rowEl.cells[i];
cellEl.className = cellEl.className.replace(colTest, “”);
// Highlight the header of the sorted column.
if (i == col)
cellEl.className += ” ” + colClsNm;
cellEl.className = normalizeString(cellEl.className);
}
}
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
//var dummy=alert(“Error:” + z);
}
return obj;
}
</script>
</head>
<body>
<fieldset>
<div id=”ex_div” class=”scroll”>
<table summary=”” id=”names” class=”sorted” cellspacing=”0″>
<colgroup>
<col style=”first labels” />
<col style=”form_fields” />
</colgroup>
<thead>
<tr class=”scroll”>
<th scope=”col” id=”ex_0″><a href=”javascript:void(0);” class=”sorted” onclick=”return sortTable(‘ex_sort’,0,true,true);”>Alpha</a></th>
<th scope=”col” id=”ex_1″><a href=”javascript:void(0);” class=”sorted” onclick=”return sortTable(‘ex_sort’,1,true,true);”>Date</a></th>
<th scope=”col” id=”ex_2″><a href=”javascript:void(0);” class=”sorted” onclick=”return sortTable(‘ex_sort’,2,true,true);”>Url</a></th>
</tr>
</thead>
<tbody class=”scroll” id=”ex_sort”>
<tr class=”even”>
<td headers=”ex_0″>Alpha</td>
<td headers=”ex_1″><span title=”20070701″>July 2, 2007</span></td>
<td headers=”ex_2″><a href=”javascript:void(0);” onclick=”alert(‘view.php?userid=6’);”>5</a></td>
</tr>
<tr class=”odd”>
<td headers=”ex_0″>Bravo</td>
<td headers=”ex_1″><span title=”20050903″>Sept 3, 2005</span></td>
<td headers=”ex_2″><a href=”javascript:void(0);” onclick=”alert(‘view.php?userid=8’);”>4</a></td>
</tr>
<tr class=”even”>
<td headers=”ex_0″>Charlie</td>
<td headers=”ex_1″><span title=”19700709″>July 9, 1970</span></td>
<td headers=”ex_2″><a href=”javascript:void(0);” onclick=”alert(‘view.php?userid=4’);”>2</a></td>
</tr>
<tr class=”odd”>
<td headers=”ex_0″>Delta</td>
<td headers=”ex_1″><span title=”20001213″>Dec. 13, 2000</span></td>
<td headers=”ex_2″><a href=”javascript:void(0);” onclick=”alert(‘view.php?userid=5’);”>3</a></td>
</tr>
<tr class=”even”>
<td headers=”ex_0″>Echo</td>
<td headers=”ex_1″><span title=”20010911″>Sept 11, 2001</span></td>
<td headers=”ex_2″><a href=”javascript:void(0);” onclick=”alert(‘view.php?userid=2’);”>1</a></td>
</tr>
</tbody>
</table>
</div>
</fieldset>
</body>
</html>

Cheers!

Detecting browser SSL capability with JavaScript

If you run a secured website using HTTPS (aka SSL) it’s often wise to stop or notify users that are using a browser or client that doesn’t support the proper encryption level required.

Here’s a short method to “sniff” the capabilities prior to forwarding users to the secure area. You could add logic to inform the user of the problem.

As usual I’ve stripped a lot of the XHTML markup for readability.

<html>
<head>
<!– set ‘sslok’ global variable for testing SSL capability –>
<script type=”text/javascript”>
<!–
var sslok = 0;
//–>
</script>
<!– try including source javascript from secure server, this will set “sslok” to 1 if it works –>
<!– note that the /secure directory is protected so that only 128+bit SSL is allowed –>
<script type=”text/javascript” src=”https://www.example.com/secure/ssl-test.js”></script>
<!– if ssl is 1, our javascript include worked, so SSL is successful – redirect to SSL –>
<script type=”text/javascript”>
<!–
if (sslok == ‘1’) {
window.location = ‘https://www.example.com/secure’;
}
//–>
</script>
</head>
<body>
</body>
</html>

Contents of the ‘ssl-test.js’ file:

<!– set sslok to 1, so we know this include succeeded –>
sslok = ‘1’;

NOTE: If you use the same ‘filesystem’ for HTTP & HTTPS you might want to use a server-side program (PHP or Java for example) to generate the JavaScript.  Benefit of that process would be that you could also interrogate and return other SSL attributes such as cypher strength.

Cheers!

Javascript ‘Response Time’ measurement (latency)

Here’s another ‘fun’ trick. When you build complex web applications, performance metrics start to become an issue. Unfortunately, web tools for this are not typically available within the browser itself and ‘testers’ often rely of such non-technical solutions as stop-watch timing. Not only is this time consuming, but it is nearly impossible to reproduce as the ‘time’ includes the hand-eye coordination of the person using the watch.

In defense of the software itself, there are several Mozilla add-ons to help with this issue, but my solution is in your application code and as such can be enhanced to provide notification or logging with some additional work.

If you’ve ever used a 3270 Emulator (Mainframe “Green Screen”) you’re probably familiar with seeing this data as most 3270 clients expose it somewhere on the screen.

The trouble here is that there is no good API to do this, and no javascript variables are persisted across page loads. (cookies would be overkill). What we’re left with is the browser window-name itself, while it’s normally used when naming ‘popup’ windows and is not visible directly to the user, it provides a ‘safe’ place to store this transient data between pages.
When leaving a page we store the current time, to be sure that we can find the correct data in the window name we use unique prefix and suffix values and add the current name so that it can be restored later.

When the new page is loaded we have to compare the current time with the stored time, then we have to be sure to restore the window ‘name’ as can’t be sure about what other reason it may be named, and we don’t want to interfere.

NOTE: If you also have access to the server code (PHP or Servlet for example) or can wrap calls to external systems like databases, you can make this much more granular by including those times. In that case you could report total time, extract database time, then extract servlet time, then ‘deduce’ network latency itself! This goes a long way when troubleshooting vague user experiences when they indicate that the “site is slow” as you can now pinpoint the problem area(s) to focus on.

WARNING: For Mozilla/Firefox the default security settings prevent JavaScript from modifying the ‘window status’ used in the example code. In a real implementation you might consider using DHTML to insert the output somewhere else. Otherwise changing Mozilla Firefox 2.x’s settings is found at Tools, Options, Content Tab, Advanced JavaScript settings.

<html>
<head>
<script>
var build=’GiantGeek Example’; // you could put build label information here 🙂
var PRE=”MMSpre”;
var SUF=”MMSsuf”;

function xload(){
var tim=grvGetDuration();
build=build + tim;
grvWindowStatus(build);
}
function xclose(){
grvWindowStatus(”);
}
function xlinkObj(obj,url){ // this is for all valid links
grvSetTimeclean();
window.location.replace(uniqueUrl(url)); // ‘replace’ has side-effect of ‘restricting’ back-button, or ‘location’
return false;
}
function grvMillis(){
return new Date().getTime();
}
function grvSetTimeclean(){
var dummy=grvGetTimeclean(); // get any remainder (dupes?)
var x=grvGetName() + grvMms();
grvSetName(x);
}
function grvMms(){
return PRE + grvMillis() + SUF;
}
function grvGetDuration(){
var rc=”;
var old=grvGetTimeclean();
if(old!=”){
rc=” [” + (grvMillis() – old)/1000 + “]”;
}
return rc;
}
function grvGetTimeclean(){
var x=grvGetName();
var pre = x.indexOf(PRE);
var suf = x.indexOf(SUF);
var rc=”;
if((pre >= 0) && (suf > pre)){
rc=x.substring(pre+PRE.length,suf);
// get the garbage in the name (around mms)
var p=x.substring(0,pre);
//var s = x.substr(suf+SUF.length);
//assemble new name (without mms)!
grvSetName(p);
}
if(pre==0){
grvSetName(”);
}
return rc;
}
function grvGetName(){
var rc = window.name;
if(rc==undefined){rc=”};
return rc;
}
function grvSetName(x){
window.name=x;
}
function grvWindowStatus(txt){
window.defaultStatus=txt;
}
function uniqueUrl(x){ // this adds a timestamp to URLs
var mms = grvMillis();
var delim = “?”;
if(x.indexOf(“?”) >=0) { delim = “&”; }
return x + delim + ‘time=’ + mms;
}
</script>
</head>
<body onunload=”xclose();” onload=”xload();”>
</body>
</html>

Adding Support for ‘disabled’ OPTION tags in MSIE

This is a very annoying bug/oversight in MSIE (including the recently released MSIE7!).

For some reason, Microsoft didn’t implement the ‘disabled’ attribute on <option> tags.
All other modern (even the old Netscape 4.x) browsers support this, why would they not do the same.
This is probably for the same reason that all versions of MSIE (prior to MSIE7) left the rendering of the SELECT tag to the operating system itself, above the browser HTML.
Thankfully, you can still access the attribute on the DOM element with javascript!

My solution has evolved over time, here’s the current release code.

To emulate this behavior in MSIE, you’ve got several different challenges to overcome.

  1. You must ‘persist’ the current value of the SELECT so that you can ‘restore’ it when the user chooses a disabled field, I do this during the onload event.
  2. When the ‘onchange’ event for the SELECT tag is invoked, the currently selected OPTION’s attributes must be read and checked.
  3. If the selected OPTION is ‘disabled’, then the previous value must be restored.
  • As this solution only stores one value per SELECT, the ‘multiple’ SELECT is not currently supported.
  • In the future I’ll probably do some ‘event injection’ so that the HTML itself is cleaner. Problem is that in more complex solutions like ‘dependent dropdowns’ this becomes tricky.
  • The [if IE] ‘comment’ is critical as it is conditional logic supported only in MSIE and simplifies what was previously done via various ‘browser-sniffing’ tricks.
  • The example code in this example excludes the CDATA escapes and several tags required for valid XHTML for brevity.
  • FYI, the example also contains the MSIE background-image cache fix discussed in a previous post.

<!DOCTYPE >
<html>
<head>
<script type=”text/javascript”>
var isMSIE=false;
<!–[if IE]>
<script type=”text/javascript”>
isMSIE=true;
</script>
<script src=”/js/grv-msie-hacks.js” type=”text/javascript”></script>
<![endif]–>

<script type=”text/javascript”>
function xload(){
if(isMSIE){
grvMsieInitHacks();
}
}
function xchange(obj){
// note: javascript emulation of <option disabled…> (for MSIE)
if(isMSIE){
grvMsieSelectFix_restore(obj);
}
}
</script>
</head>

<body onload=”xload();”>

<form action=”#” method=”GET”>
<select name=”s” id=”s” size=”1″ onchange=”xchange(this);”>
<option value=”n1″>Normal1</option>
<option value=”di” disabled=”disabled”>Dis</option>
<option value=”n1″>Normal2</option>
</select>
</form>
</body>
</html>

JavaScript file (grv-msie-hacks.js):

/*
* Code library to add several ‘broken’ features in MSIE 6 and 7
* @version $Id: $
*/
function grvMsieInitHacks(){
grvMsieCacheFix();
grvMsieSelectFix_init()
}
function grvMsieCacheFix(){
try{
document.execCommand(“BackgroundImageCache”,false,true);
}catch(e)
{}
}
/* Added new functions to support <option disabled…> emulation
*
* First part, necessary for <body onLoad…>
* builds array of all initial selections for <select>s on page (for LT or EQ MSIE7)
* WAS: disabledOptionEmulation();
*/
function grvMsieSelectFix_init() {
if (document.getElementsByTagName) {
var allSelects = document.getElementsByTagName(“select”); // build array of all <select> tags
if (allSelects.length > 0) { // if array has values…
window.allSelectsCurrentIndex = new Array(); // new array to hold initial selections
for (var i=0, individualSelect; individualSelect = allSelects[i]; i++) { // crawl through all <select> tags
individualSelect.onfocus = function(){ window.allSelectsCurrentIndex[this.id] = this.selectedIndex; } // fill array with selectedIndex values
}
}
}
}

/* companion code for grvMsieSelectFix_init()
* resets <option> selection if disabled to last good selection (for LT or EQ MSIE7)
* WAS: restoreSelection(inOptionChoice)
*/
function grvMsieSelectFix_restore(inOptionChoice) {
if (inOptionChoice.options[inOptionChoice.selectedIndex].disabled){
// if new choice is disabled…
inOptionChoice.selectedIndex = window.allSelectsCurrentIndex[inOptionChoice.id]; // deny selection, revert back to last ‘known good’ choice (typically, the initial selection at page load)
} else { // if new choice isn’t disabled…
window.allSelectsCurrentIndex[inOptionChoice.id] = inOptionChoice.selectedIndex; // update array so last ‘known good’ choice is now the latest user selection
}
}

Hopefully, Microsoft will get around to fixing this correctly some day… maybe for MSIE8!

TEXTAREA maxlength – or lack of!

I’m not sure why this was previously overlooked in HTML4/XHTML1, but it’s been a real pain for developers for years. The ‘rows’ and ‘cols’ attributes are useless (like ‘size’ is in the INPUT tag) as they are based on display size of fixed-width fonts like Courier and not the actual input limitations. INPUT has always supported a ‘maxlength’ attribute for this purpose.

Good news, this is part of WebForms2 and HTML5 (proposal) plans to add it!

A decent fix that I currently use… optimally you would use a common ‘maxsize’ function.

[textarea name=”junit” id=”junit” onkeyup=”maxsize(this,100);”][/textarea]
[script type=”text/javascript”]
function maxsize(obj,mx){
if(obj.value.length>mx)
{
obj.value=obj.value.substring(0,mx); }
}
[/script]

WARNING:

You should ALWAYS perform server-side validation of the length too, otherwise you leave the door open for someone to hack your form and submit longer data.

References:

Cheers!

MSIE6 javascript memory leaks

Argh…. yet again, this crappy product has another bug that developers must work around!

It seems that Microsoft doesn’t release memory to javascript objects from memory when created on a page… even when the page is unloaded.

Let’s think about this one for a second, why would you want to keep a javascript variable or DOM reference in memory after the user has navigated away from that page? This violates the stateless paradigm that web applications generally work with, besides… how would a developer be able to get that information (memory) back on the next page anyways? Perhaps, it was some genious that tried to keep state in javascript when the ‘BACK’ button was pressed… we’ll probably never know.

There’s a great quote I found while researching this…

“IE has an issue where it leaks memory when a circular reference is created between a COM object and a javascript object. In IE, the DOM is implemented via COM ….. This memory is not reclaimed until the browser closes. The simplest solution is to pretend there is no garbage collector for objects and make sure you always clean-up after yourself.”

References:

Microsoft ‘chimes in’:

Tools to help:

Cheers!