/*
/** vim: set expandtab tabstop=3 shiftwidth=3:
*******************************************************************************
* (c) NEOCO                                                                   *
*                                                                             *
* filename: core.js                                                           *
* purpose:  core javascript function library.                                 *
*                                                                             *
*******************************************************************************

$Revision: 124 $
$Date: 2006-12-05 17:41:10 +0000 (Tue, 05 Dec 2006) $
$LastChangedBy: dave $

*/

var URI_BASE = "/";
var URI_ICONS = URI_BASE+"shared/images/icons/";
var URI_DATARETRIEVAL = URI_BASE+"dr/";

/* ************************************************************************* */
// COOKIE HANDLING FUNCTIONALITY

// http://www.quirksmode.org/js/cookies.html

/* ************************************************************************* */
// function to create a cookie; name/value pair
//  @ name = name    } of name/value pair
//  @ value = value  }
//  @ days = number of days valid for, or false for always
function create_cookie(name, value, days)
{
   if (days)
   {
      var date = new Date();
      date.setTime(date.getTime()+(days*24*60*60*1000));
      var expires = "; expires="+date.toGMTString();
   }
	else
	   var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
} // end FUNCTION

/* ************************************************************************* */
// function to read cookie contents
//  @ name = name of cookie
// returns data if found, or null otherwise
function read_cookie(name)
{
   var nameEQ = name + "=";
   var ca = document.cookie.split(';');
   for(var i=0;i < ca.length;i++)
   {
      var c = ca[i];
      while (c.charAt(0)==' ')
         c = c.substring(1,c.length);
      if (c.indexOf(nameEQ) == 0)
		   return c.substring(nameEQ.length,c.length);
   }
	return null;
} // end FUNCTION

/* ************************************************************************* */
// function to erase a cookie
//  @ name = name of cookie
function erase_cookie(name)
{
   create_cookie(name,"",-1);
} // end FUNCTION


/* ************************************************************************* */
// CROSS BROWSER EVENT HANDLING MODEL

// PPK on JS - good book ;)

/* ************************************************************************* */
// function to register event handler
//  @ obj = the object to attach event handler to
//  @ evt = the event to trap; WITHOUT "on"; ie: click / mouseover
//  @ fn = the function to call when event triggered
function add_event_simple(obj, evt, fn)
{
   if (obj.addEventListener)
      obj.addEventListener(evt, fn, false);
   else if (obj.attachEvent)
      obj.attachEvent('on'+evt, fn);
} // end FUNCTION

/* ************************************************************************* */
// function to deregister event handler
//  @ obj = the object to detach event handler from
//  @ evt = the event; WITHOUT "on"; ie: click / mouseover
//  @ fn = the function called when event triggered
function remove_event_simple(obj, evt, fn)
{
   if (obj.removeEventListener)
      obj.removeEventListener(evt, fn, false);
   else if (obj.detachEvent)
      obj.detachEvent('on'+evt, fn);
} // end FUNCTION


/* ************************************************************************* */
// DATA RETRIEVAL FRAMEWORK

var XMLHttpFactories = [
   function () { return new XMLHttpRequest() },
   function () { return new ActiveXObject('Msxml2.XMLHTTP') },
   function () { return new ActiveXObject('Msxml3.XMLHTTP') },
   function () { return new ActiveXObject('Microsoft.XMLHTTP') }
];

/* ************************************************************************* */
// function to initialise object
function createXMLHTTPObject()
{
   var xmlhttp = false;
   for (var i=0; i<XMLHttpFactories.length; i++)
   {
      try { xmlhttp = XMLHttpFactories[i](); }
      catch (e)   { continue; }
      break;
   }
   return xmlhttp;
} // end FUNCTION

/* ************************************************************************* */
// function to send request
function sendRequest(url,callback,postData)
{
   var req = createXMLHTTPObject();
   if (!req)
      return;
   var method = (postData) ? "POST" : "GET";
   req.open(method,url,true);
   req.setRequestHeader('User-Agent','XMLHTTP');
   if (postData)
      req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
   req.onreadystatechange = function () {
      if (req.readyState != 4) return;
//      if (req.status != 200 && req.status != 304)  { alert('HTTP Error '+req.status); return; }
      // execute callback function, if not null
      if (callback != null)
         callback(req);
   }
   if (req.readyState == 4) return;
   req.send(postData);
   return true;
} // end FUNCTION


/* ************************************************************************* */
// FIND POSITION OF OBJECTS FUNCTIONS 

/* ************************************************************************* */
// function to find position of object (x)
function find_pos_x(obj)
{
	var curleft = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curleft += obj.offsetLeft
			obj = obj.offsetParent;
		}
	}
	else if (obj.x)
		curleft += obj.x;
	return curleft;
} // end FUNCTION

/* ************************************************************************* */
// function to find position of object (y)
function find_pos_y(obj)
{
	var curtop = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curtop += obj.offsetTop
			obj = obj.offsetParent;
		}
	}
	else if (obj.y)
		curtop += obj.y;
	return curtop;
} // end FUNCTION

/* ************************************************************************* */
// function to find position of object 
function find_pos(obj)
{
   var pos = new Object();
	pos.x = 0;
	pos.y = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			pos.x += obj.offsetLeft;
			pos.y += obj.offsetTop;
			obj = obj.offsetParent;
		}
	}
	else if (obj.x)
	{
		pos.x += obj.x;
		pos.y += obj.y;
	}
	pos.left = pos.x;
	pos.top = pos.y;
	return pos;
} // end FUNCTION


/* ************************************************************************* */
// DRAG AND DROP

// array of all registered drag handlers; [handler_id] = object { handler_id, callbacks }
var drag_handlers = new Array();
// array of all ids registered to various drag handlers; [<id>] = handler_id
var drag_handlers_ids = new Array();     
// object containing info on a "parked" element;
// properties vary; usually include: location_id to indicate when an item can be
// parked (true/false) to indicate IF parked at all
// dragged to MORE than one location
var drag_parked = new Object();

var drag_offset;           // offset into object we are dragging (obj with properties: x, y)
var drag_shadow_obj;       // the drag shadow object (dotted transparent box)
var drag_obj;              // the object that we're dragging (clicked on to start drag)

// constants
var drag_mousetrigger_any = 0;          // any mouse click will start drag
var drag_mousetrigger_leftonly = 1;     // only LEFT mouse click will start drag
var drag_mousetrigger_rightonly = 2;    // only RIGHT mouse click will start drag

// callback functions: (named drag_<handlerID>_<callbackName>) where Names:
//  getobject(targ) -- targ = the target item that was clicked on
//                     returns object with properties: x, y, width, height (dimensions)
//  hideoriginal()  -- should hide original item (at start of drag)
//  showoriginal()  -- should show original item (at end of invalid drag)
//  park()          -- should "park" item, if possible, at current location on page
//  unpark()        -- remove any dom elements added as a result of a "park" operation
//  complete()      -- complete the process completely; take action if in valid location

/* ************************************************************************* */
// function to register a drag / drop handler from a list of object ids
//  @ handler_id = unique id for this handler (short string)
//  @ callbacks = associative array of callback functions; [id] = function
//                ids = getobject, hideoriginal, showoriginal, park, unpark, complete
//  @ mousetrigger = what will trigger the start the drag process for this handler;
//       drag_mousetrigger_any, drag_mousetrigger_leftonly or drag_mousetrigger_rightonly
//  @ obj_ids = array of all object ids that should use this handler when dragged
// returns true on success; false on failure
function drag_register_by_ids(handler_id, callbacks, mousetrigger, obj_ids)
{
   // we MUST have some ids
   if (!is_array(obj_ids) || obj_ids.length == 0)
      return false;
   // must have some handler
   if (handler_id == "")
      return false;
   // register handler; if NOT registered
   if (!drag_handlers[handler_id])
   {
      drag_handlers[handler_id] = new Object();
      drag_handlers[handler_id].handler_id = handler_id;
      drag_handlers[handler_id].callbacks = callbacks;
      drag_handlers[handler_id].mousetrigger = mousetrigger;
   }
   // register ids and attach events
   var obj;
   var success = true;
   for (var i=0; i<obj_ids.length; i++)
   {
      // check object with id exists
      var obj = document.getElementById(obj_ids[i]);
      if (!obj)
         success = false;
      else
      {
         drag_handlers_ids[obj_ids[i]] = handler_id;
         obj.onmousedown = drag_start;
         obj.style.cursor = 'move';
      }
   }
   return success;
} // end FUNCTION

/* ************************************************************************* */
// function to register a drag / drop handler from a tag name and class match
//  @ handler_id = unique id for this handler (short string)
//  @ callbacks = associative array of callback functions; [id] = function
//                ids = getobject, hideoriginal, showoriginal, park, unpark, complete
//  @ mousetrigger = what will trigger the start the drag process for this handler;
//       drag_mousetrigger_any, drag_mousetrigger_leftonly or drag_mousetrigger_rightonly
//  @ tagname = tag name to search for
//  @ classname = class search string that we look for; all matching tags that
//            match this class will be drag-enabled -- REGEXPR (ie: /xyz/)
// returns true on success; false on failure
function drag_register_by_tag_class(handler_id, callbacks, mousetrigger, tagname, classname)
{
   var ids = new Array();
   var tags = document.getElementsByTagName(tagname);
   if (!tags || tags.length == 0)
      return true;
   for (var ti=0; ti<tags.length; ti++)
   {
      // class match?
      if (tags[ti].className.search(classname) != -1 && tags[ti].id != "")
         ids.push(tags[ti].id);
   }
   // register by ids
   return drag_register_by_ids(handler_id, callbacks, mousetrigger, ids);
} // end FUNCTION

/* ************************************************************************* */
// function to register a drag / drop handler from a SINGLE id, a tagname
// and a potential classname match; will match ALL client nodes of a tag
// that match a regexp.
//  @ handler_id = unique id for this handler (short string)
//  @ callbacks = associative array of callback functions; [id] = function
//                ids = getobject, hideoriginal, showoriginal, park, unpark, complete
//  @ mousetrigger = what will trigger the start the drag process for this handler;
//       drag_mousetrigger_any, drag_mousetrigger_leftonly or drag_mousetrigger_rightonly
//  @ obj_id = unique id of object to add children of
//  @ tagname = tag name to search for (for children)
//  @ classname = class search string that we look for; all matching tags that
//            match this class will be drag-enabled -- REGEXPR (ie: /xyz/)
// returns true on success; false on failure
function drag_register_by_id_children(handler_id, callbacks, mousetrigger, obj_id, tagname, classname)
{
   var ids = new Array();
   // fetch obj
   var obj = document.getElementById(obj_id);
   if (!obj)
      return false;
   // fetch tags
   var tags = obj.getElementsByTagName(tagname);
   if (!tags || tags.length == 0)
      return true;
   for (var ti=0; ti<tags.length; ti++)
   {
      // class match?
      if (tags[ti].className.search(classname) != -1 && tags[ti].id != "")
         ids.push(tags[ti].id);
   }
   // register by ids
   return drag_register_by_ids(handler_id, callbacks, mousetrigger, ids);
} // end FUNCTION


/* ************************************************************************* */
// function executed on drag start
//  @ e = event info (mozilla)
// returns true
function drag_start(e)
{
   // fetch event and event target info (QUIRKSMODE)
   var targ;
   if (!e) var e = window.event;
   if (e.target) targ = e.target;
   else if (e.srcElement) targ = e.srcElement;
   if (targ.nodeType == 3) // defeat Safari bug
      targ = targ.parentNode;

   // set original details of our target so we can check against
   var origtarg = new Object();
   origtarg.tagName = targ.tagName;
   origtarg.id = targ.id;
   origtarg.className = targ.className;

   // we MUST have an ID, and it must match registered database of ids; check:
   // NOTE: otherwise we look up to see if we can find
   while ((targ.id == "" || !drag_handlers_ids[targ.id]) && targ.parentNode)
   {
      targ = targ.parentNode;
   }
   if (targ.id == "" || !drag_handlers_ids[targ.id])
      return false;
   
   if (targ.id == "" || !drag_handlers_ids[targ.id])
      return true;

   targ.origtarg = origtarg;

   // are we currently involved in a drag? if YES then cancel that first
   if (drag_obj)
      drag_abandon();

   // remember what we're dragging
   drag_obj = targ;

   // fetch FULL object (location/size) that we are dragging, from handler callback
   // NOTE: this gives opportunity for handler to return FALSE to cancel the drag
   if (drag_handlers[drag_handlers_ids[targ.id]].callbacks["getobject"])
   {
      var obj_dimensions = drag_handlers[drag_handlers_ids[targ.id]].callbacks["getobject"](targ);
      if (!obj_dimensions)
         return true;
   }
   else
   {
      var obj_dimensions = new Object();
      obj_dimensions.x = find_pos_x(drag_obj);
      obj_dimensions.y = find_pos_y(drag_obj);
      obj_dimensions.width = find_pos_x(drag_obj.offsetWidth);
      obj_dimensions.height = find_pos_y(drag_obj.offsetHeight);
   }

   // set drag handler id
   var draghandlerid = drag_handlers_ids[targ.id];

   // only start the drag IF we have a mouse button match for this handler
   switch (drag_handlers[draghandlerid].mousetrigger)
   {
   case drag_mousetrigger_leftonly:
   	if (e.which)
   	   var leftclick = (e.which == 1);
   	else if (e.button)
   	   var leftclick = (e.button != 2); // actually WRONG - only checks NOT right button
   	if (!leftclick)
   	   return true;
      break;

   case drag_mousetrigger_rightonly:
   	if (e.which)
   	   var rightclick = (e.which == 3);
   	else if (e.button)
   	   var rightclick = (e.button == 2);
   	if (!rightclick)
   	   return true;
      break;
   }

   // find click offset into object
	var pos = new Object();
	if (e.pageX || e.pageY)
	{
		pos.x = e.pageX;
		pos.y = e.pageY;
	}
	else if (document.documentElement)
	{
		pos.x = e.clientX + document.documentElement.scrollLeft;
		pos.y = e.clientY + document.documentElement.scrollTop;
	}
	else if (e.clientX || e.clientY)
	{
		pos.x = e.clientX + document.body.scrollLeft;
		pos.y = e.clientY + document.body.scrollTop;
	}
   drag_offset = new Object();
   drag_offset.x = pos.x - obj_dimensions.x;
   drag_offset.y = pos.y - obj_dimensions.y;

   // create new block that we will use to drag about the place
   drag_shadow_obj = document.createElement("div");
   drag_shadow_obj.className = "dragdropshadow";
   drag_shadow_obj.style.width = obj_dimensions.width+"px";
   drag_shadow_obj.style.height = obj_dimensions.height+"px";
   drag_shadow_obj.id = "dragshadowobj";
   drag_shadow_obj.style.top = (pos.y - drag_offset.y) + "px";
   drag_shadow_obj.style.left = (pos.x - drag_offset.x) + "px";
   
   // add element to dom
   var anyshadowblock = document.getElementById("dragshadowobj");
   if (anyshadowblock)
      anyshadowblock.parentNode.replaceChild(anyshadowblock, drag_shadow_obj);
   else
       document.body.appendChild(drag_shadow_obj);

   // hide the column we are actually wanting to move
   if (drag_handlers[draghandlerid].callbacks["hideoriginal"])
      drag_handlers[draghandlerid].callbacks["hideoriginal"]();
   else
      drag_obj.style.display = "none";

   // attempt to park block IMMEDIATELY
/*   var draghandlerid = drag_handlers_ids[drag_obj.id];
   if (drag_handlers[draghandlerid].callbacks["park"])
      drag_handlers[draghandlerid].callbacks["park"](pos);
   */

   // set move / finish events -- use simple model so only one handler
   document.onmousemove = drag_move;
   document.onmouseup = drag_finish;

   if (!e) var e = window.event;
   	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();

   return false;
} // end FUNCTION

/* ************************************************************************* */
// function executed when draggable object is moved
//  @ e = event info (mozilla)
function drag_move(e)
{
   // fetch event and event target info (QUIRKSMODE)
   var targ;
   if (!e) var e = window.event;
   if (e.target) targ = e.target;
   else if (e.srcElement) targ = e.srcElement;
   if (targ.nodeType == 3) // defeat Safari bug
      targ = targ.parentNode;

   // get current pos
   var pos = new Object();
   pos.x = 0;
   pos.y = 0;
	if (e.pageX || e.pageY)
	{
		pos.x = e.pageX;
		pos.y = e.pageY;
	}
	else if (document.documentElement)
	{
		pos.x = e.clientX + document.documentElement.scrollLeft;
		pos.y = e.clientY + document.documentElement.scrollTop;
	}
	else if (e.clientX || e.clientY)
	{
		pos.x = e.clientX + document.body.scrollLeft;
		pos.y = e.clientY + document.body.scrollTop;
	}
   drag_shadow_obj.style.top = (pos.y - drag_offset.y) + 'px';
   drag_shadow_obj.style.left = (pos.x - drag_offset.x) + 'px';

   // attempt to park block
   var draghandlerid = drag_handlers_ids[drag_obj.id];
   if (drag_handlers[draghandlerid].callbacks["park"])
      drag_handlers[draghandlerid].callbacks["park"](pos);

   // execute park completion
   drag_park_completed();

   return true;
} // end FUNCTION

/* ************************************************************************* */
// function executed when draggable object has been parked/unparked
function drag_park_completed()
{
   // fetch "shadow" block
   var shadowblock = document.getElementById("dragshadowobj");
   if (!shadowblock)
      return true;
   // have we parked?
   if (drag_parked.parked && shadowblock.className.search(/dragok/) == -1)
      shadowblock.className += " dragok";
   else if (!drag_parked.parked)
      shadowblock.className = shadowblock.className.replace(/dragok/gi, "");
   return true;
} // end FUNCTION

/* ************************************************************************* */
// function executed when draggable object is moved
function drag_finish()
{
   // cancel drag
   document.onmousemove = null;
   document.onmouseup = null;

   // do we have somewhere VALID to put the block?
   var draghandlerid = drag_handlers_ids[drag_obj.id];
   if (!drag_handlers[draghandlerid].callbacks["complete"] || !drag_handlers[draghandlerid].callbacks["complete"]())
   {
      // unpark anything that is parked
      if (drag_handlers[draghandlerid].callbacks["unpark"])
         drag_handlers[draghandlerid].callbacks["unpark"]();

      if (drag_handlers[draghandlerid].callbacks["showoriginal"])
         drag_handlers[draghandlerid].callbacks["showoriginal"]();
      else
         drag_obj.style.display = "block";
   } // end error COMPLETING if

   // remove the shadow block
   drag_shadow_obj.parentNode.removeChild(drag_shadow_obj);

   // unset drag object/vars ready for next time
   drag_obj = null;
   drag_shadow_obj = null;
   drag_parked = new Object();
   drag_offset = null;

   return true;
} // end FUNCTION

/* ************************************************************************* */
// function executed when draggable object is found even though
// we are supposed to be JUST starting drag; ie: abandon some previous drag
function drag_abandon()
{  
   // remove any shadow object; if present
   if (drag_shadow_obj && drag_shadow_obj.parentNode)
      drag_shadow_obj.parentNode.removeChild(drag_shadow_obj);

   // unpark; if parked
   var draghandlerid = drag_handlers_ids[drag_obj.id];
   if (drag_handlers[draghandlerid].callbacks["unpark"])
      drag_handlers[draghandlerid].callbacks["unpark"]();

   // unset drag object
   drag_obj = null;
   drag_shadow_obj = null;
   drag_parked = new Object();
   drag_offset = null;

   return true;
} // end FUNCTION


/* ************************************************************************* */
// CONTEXT (RIGHT-CLICK) MENU

// array of all registered context menu handlers; [handler_id] = object { handler_id, callbacks }
var cmenu_handlers = new Array();
// object that will contain TARGET information on what was clicked on to launch context menu
var cmenu_target;

// attach context menu event
document.oncontextmenu = cmenu_activate;
// attach cance menu event -- to document CLICK
add_event_simple(document, "mousedown", cmenu_click_deactivate);
// attach cance menu event -- to document MOUSE DOWN
add_event_simple(document, "keydown", cmenu_escape_deactivate);

// context menu item types
var cmenu_item_type_item = 0;
var cmenu_item_type_title = 1;
var cmenu_item_type_break = 2;

// callback functions: (named cmenu_<handlerID>_<callbackName>) where Names:
//  buildmenu()     -- function to build the context menu items
//  click()         -- function called when user clicks on a menu item

// context menu array is built up with one entry per item; each item an object with properties:
//  @ item_id = required unique id for this item
//  @ handler_id = the context menu handler id for this item
//  @ title = title text for menu item
//  @ type = a "type" for this item; this is used to define a classname which, in turn, influences any icon displayed
//  @ href = optional page href to attach; if NOT present will call the click callback with appropriate ID

/* ************************************************************************* */
// function to register a context menu handler from a list of object ids
//  @ handler_id = unique id for this handler (short string)
//  @ callbacks = associative array of callback functions; [id] = function
//                ids = buildmenu, click
//  @ obj_ids = array of all object ids that should use this handler when right click activated
// returns true on success; false on failure
function cmenu_register_by_ids(handler_id, callbacks, obj_ids)
{
   // we MUST have some ids
   if (!is_array(obj_ids) || obj_ids.length == 0)
      return false;
   // must have some handler
   if (handler_id == "")
      return false;
   // register handler; if NOT registered
   if (!cmenu_handlers[handler_id])
   {
      cmenu_handlers[handler_id] = new Object();
      cmenu_handlers[handler_id].handler_id = handler_id;
      cmenu_handlers[handler_id].callbacks = callbacks;
   }
   // register ids and attach events
   var obj;
   var success = true;
   for (var i=0; i<obj_ids.length; i++)
   {
      // check object with id exists
      var obj = document.getElementById(obj_ids[i]);
      if (!obj)
         success = false;
      else
      {
         // attach contextHandler property
         if (!obj.contextHandlers)
            obj.contextHandlers = new Array();
         obj.contextHandlers[obj.contextHandlers.length] = handler_id;
      }
   } // end loop through objects
   
   return success;
} // end FUNCTION

/* ************************************************************************* */
// function to register a context menu handler from a tag name and class match
//  @ handler_id = unique id for this handler (short string)
//  @ callbacks = associative array of callback functions; [id] = function
//                ids = buildmenu, click
//  @ tagname = tag name to search for
//  @ classname = class search string that we look for; all matching tags that
//            match this class will be drag-enabled -- REGEXPR (ie: /xyz/)
// returns true on success; false on failure
function cmenu_register_by_tag_class(handler_id, callbacks, tagname, classname)
{
   // must have some handler
   if (handler_id == "")
      return false;
   // register handler; if NOT registered
   if (!cmenu_handlers[handler_id])
   {
      cmenu_handlers[handler_id] = new Object();
      cmenu_handlers[handler_id].handler_id = handler_id;
      cmenu_handlers[handler_id].callbacks = callbacks;
   }
   // fetch tags
   var tags = document.getElementsByTagName(tagname);
   if (!tags || tags.length == 0)
      return true;
   for (var ti=0; ti<tags.length; ti++)
   {
      // class match?
      if (classname == "" || tags[ti].className.search(classname) != -1)
      {
         // attach contextHandler property
         if (!tags[ti].contextHandlers)
            tags[ti].contextHandlers = new Array();
         tags[ti].contextHandlers[tags[ti].contextHandlers.length] = handler_id;
      }
   } // end loop through tags
   return true;
} // end FUNCTION

/* ************************************************************************* */
// function to register a context menu handler from a SINGLE id, a tagname
// and a potential classname match; will match ALL client nodes of a tag
// that match a regexp.
//  @ handler_id = unique id for this handler (short string)
//  @ callbacks = associative array of callback functions; [id] = function
//                ids = buildmenu, click
//  @ obj_id = unique id of object to add children of
//  @ tagname = tag name to search for (for children)
//  @ classname = class search string that we look for; all matching tags that
//            match this class will be drag-enabled -- REGEXPR (ie: /xyz/)
// returns true on success; false on failure
function cmenu_register_by_id_children(handler_id, callbacks, obj_id, tagname, classname)
{
   // must have some handler
   if (handler_id == "")
      return false;
   // register handler; if NOT registered
   if (!cmenu_handlers[handler_id])
   {
      cmenu_handlers[handler_id] = new Object();
      cmenu_handlers[handler_id].handler_id = handler_id;
      cmenu_handlers[handler_id].callbacks = callbacks;
   }
   // fetch obj
   var obj = document.getElementById(obj_id);
   if (!obj)
      return false;
   // fetch tags
   var tags = obj.getElementsByTagName(tagname);
   if (!tags || tags.length == 0)
      return true;
   for (var ti=0; ti<tags.length; ti++)
   {
      // class match?
//      if ((classname == "" || tags[ti].className.search(classname) != -1 && tags[ti].id != "")
      var regexp = new RegExp(classname);
      if (classname == "" || tags[ti].className.search(regexp) != -1)
      {
         // attach contextHandler property
         if (!tags[ti].contextHandlers)
            tags[ti].contextHandlers = new Array();
         tags[ti].contextHandlers[tags[ti].contextHandlers.length] = handler_id;
      }
   } // end loop through tags
   return true;
} // end FUNCTION

/* ************************************************************************* */
// function executed when context menu fired
//  @ e = event information (mozilla)
// returns true if NOT showing any custom context menu
function cmenu_activate(e)
{
   // fetch event and event target info (QUIRKSMODE)
   var targ;
   if (!e) var e = window.event;
   if (e.target) targ = e.target;
   else if (e.srcElement) targ = e.srcElement;
   if (targ.nodeType == 3) // defeat Safari bug
      targ = targ.parentNode;

   // has this target got any handlers registered?
   if (!targ.contextHandlers)
      return true;

   // set target object (remember this)
   cmenu_target = targ;

   // execute callback to build menu object
   var contextmenu = new Array();
   for (var i=0; i<targ.contextHandlers.length; i++)
   {
      // execute callback
      cmenu_handlers[targ.contextHandlers[i]].callbacks["buildmenu"](cmenu_target, contextmenu, targ.contextHandlers[i]);
   }

   // any menu items?
   if (contextmenu.length == 0)
      return true;   // fire default

   // make sure no menu at present 
   cmenu_deactivate();

   // construct menu from object
   var menuobj = document.createElement("UL");
   menuobj.id = "contextmenu";
   
   for (var i=0; i<contextmenu.length; i++)
   {
      var element = document.createElement("LI");
      // what TYPE of element?
      if (contextmenu[i].type && contextmenu[i].type == cmenu_item_type_title)
      {
         // title item
         element.className = "title";
         element.innerHTML = contextmenu[i].title;
      }
      else if (contextmenu[i].type && contextmenu[i].type == cmenu_item_type_break)
      {
         // line break item
         element.className = "break";
      }
      else
      {
         // standard menu item

         element.itemId = contextmenu[i].item_id;
         element.contextHandler = contextmenu[i].handler_id;
         // attach onclick to item; if not a straight link
         if (!contextmenu[i].href)
            element.onclick = cmenu_click;
         // add in classname, if we have a type
         if (contextmenu[i].type)
         {
            if (element.className == "")
               element.className = "context-"+contextmenu[i].type;
            else
               element.className += " context-"+contextmenu[i].type;
         }
         var title =  document.createElement("A");
         if (contextmenu[i].href)
            title.href = contextmenu[i].href;
         else
            title.href = "#";
         title.innerHTML = contextmenu[i].title;
         element.appendChild(title);
      } // end TYPE of item if
      // if this is the LAST element, add in "noborder" class
      if (i == (contextmenu.length - 1))
         element.className += " noborder";
      // add the element to our context menu
      menuobj.appendChild(element);
   } // end loop through items

   // position menu
   var pos = new Object();
   pos.x = 0;
   pos.y = 0;
	if (e.pageX || e.pageY)
	{
		pos.x = e.pageX;
		pos.y = e.pageY;
	}
	else if (document.documentElement)
	{
		pos.x = e.clientX + document.documentElement.scrollLeft;
		pos.y = e.clientY + document.documentElement.scrollTop;
	}
	else if (e.clientX || e.clientY)
	{
		pos.x = e.clientX + document.body.scrollLeft;
		pos.y = e.clientY + document.body.scrollTop;
	}
	
	menuobj.style.top = (pos.y)+'px';
	menuobj.style.left = (pos.x)+'px';

   // add menu to the document
   document.body.appendChild(menuobj);

   return false;
} // end FUNCTION

/* ************************************************************************* */
// function executed when context menu is clicked on
//  @ e = event information (mozilla)
// returns true
function cmenu_click(e)
{
   // fetch event and event target info (QUIRKSMODE)
   var targ;
   if (!e) var e = window.event;
   if (e.target) targ = e.target;
   else if (e.srcElement) targ = e.srcElement;
   if (targ.nodeType == 3) // defeat Safari bug
      targ = targ.parentNode;
   
   // fetch LI for item we have clicked on
   var li = targ;
   var protect = 0;
   while (li.tagName.search(/li/i) == -1 && protect < 5 && li.parentNode)
   {
      li = li.parentNode;
      protect++;
   }
   if (li.tagName.search(/li/i) == -1)
      return false;

   // execute callback for click
   cmenu_handlers[li.contextHandler].callbacks["click"](cmenu_target, li);

   // close menu
   cmenu_deactivate();

   return true;
} // end FUNCTION

/* ************************************************************************* */
// function executed when key pressed on main window; use to CLOSE MENU
// if ESCAPE KEY is pressed
//  @ e = event info (mozilla)
// returns true
function cmenu_escape_deactivate(e)
{
	if (!e) var e = window.event;
	if (e.keyCode) code = e.keyCode;
	else if (e.which) code = e.which;
   if (code == 27)
      cmenu_deactivate();
   return true;
} // end FUNCTION

/* ************************************************************************* */
// function executed when mouse clicked within main window; use to CLOSE MENU
// if ANY mouse button is pressed down
// returns true
function cmenu_click_deactivate(e)
{
   // fetch event and event target info (QUIRKSMODE)
   var targ;
   if (!e) var e = window.event;
   if (e.target) targ = e.target;
   else if (e.srcElement) targ = e.srcElement;
   if (targ.nodeType == 3) // defeat Safari bug
      targ = targ.parentNode;

   // is targ part of our menu?
   while (targ.id != "contextmenu" && targ.parentNode)
   {
      targ = targ.parentNode;
   }
   
   if (targ && targ.id == "contextmenu")
      return true;

   return cmenu_deactivate();
} // end FUNCTION


/* ************************************************************************* */
// function executed when context menu closed / deactivated
// returns true
function cmenu_deactivate()
{
   // hide menu
   var menuobj = document.getElementById("contextmenu");
   if (menuobj)
      menuobj.parentNode.removeChild(menuobj);
   return true;
} // end FUNCTION


/* ************************************************************************* */
// CLEVER ALERT

var fn_ca_callback;           // define var we use to track callback

add_event_simple(window, "load", initialise_clever_alerts);

/* ************************************************************************* */
// function to initialise clever-alerts from the main cms responses
// this adjusts inline alerts and makes them clever alerts
function initialise_clever_alerts()
{
   var divs = document.getElementsByTagName("div");
   var found = false;
   for (var i=0; i<divs.length && !found; i++)
   {
      // match?
      if (divs[i].className.search(/takefs/gi) != -1)
      {
         // extract type
         var type="error";
         var regexp = /alert-(error|warning|info)/i;
         var results = regexp.exec(divs[i].className);
         if (results && results.length > 1)
            type = results[1];
         // extract title/description
         var h1s = divs[i].getElementsByTagName("h1");
         if (h1s && h1s.length == 1)
            var title = h1s[0].innerHTML;
         else
            var title = "";
         var ps = divs[i].getElementsByTagName("p");
         if (ps && ps.length == 1)
            var description = ps[0].innerHTML;
         else
            var description = "";
         // remove original
         divs[i].parentNode.removeChild(divs[i]);
         // trigger alert
         clever_alert(type, title, description, false, false);
         // set found
         found = true;
      }
   } // end loop through divs
   return true;
} // end FUNCTION

/* ************************************************************************* */
// function to initialise clever alert box
// @ s_type = the type of this clever alert box; will be appended to "alert-" to
//            formulate class name attached; error, success, info
// @ title = title
// @ description = main text body
// @ buttons = the buttons to output; [numeric-id] = title; OR false for "use default"
//   commonly used ids: 101=ok, 200=cancel
//   NOTE: ANY ID ENDING WITH "1" WILL BE THE DEFAULT ACTION (ie 101)
// returns true
function clever_alert(s_type, s_title, s_description, a_buttons, fn_callback)
{
   // we can only ever have ONE clever alert box
   var exists = document.getElementById("cleveralert");
   if (exists)
      clever_close();

   // create our clever alert container and set props
   var cleveralert = document.createElement("DIV");
   cleveralert.id = "cleveralert";
   cleveralert.className = "alert";

   // set width/height
   var winsize = get_window_size();
   cleveralert.style.height = winsize.height+"px";
   cleveralert.style.width = winsize.width+"px";

   // alertbox
   var alertbox = document.createElement("DIV");
   alertbox.className = "alert-area alert-"+s_type;

   // create title element inside our clever alert
   var element = document.createElement("H1");
   element.innerHTML = s_title;
   alertbox.appendChild(element);

   // create description element inside our clever alert
   var element = document.createElement("P");
   element.innerHTML = s_description;
   alertbox.appendChild(element);

   var buttons = document.createElement("UL");
   // create button elements
   if (!a_buttons)
   {
      a_buttons = new Object();
      a_buttons['101'] = "OK";
   }

   for (var id in a_buttons)
   {
      var buttonitem = document.createElement("LI");
      var button = document.createElement("INPUT");
      button.type = "submit";
      button.id = "cab"+id;
      if (id & 1)
         button.className = "btn-main";
      else
         button.className = "btn-sub";
      button.alt = a_buttons[id];
      button.value = a_buttons[id];
      add_event_simple(button, "click", clever_click);
      buttonitem.appendChild(button);
      buttons.appendChild(buttonitem);
   } // end loop buttons

   // attach buttons to alert box
   alertbox.appendChild(buttons);
   
   // append alert box to clever alert
   cleveralert.appendChild(alertbox);

   // add to body
   document.body.appendChild(cleveralert);

   // adjust body
   document.getElementsByTagName('html')[0].style.overflow = 'hidden';
/*   document.body.style.overflow = "hidden"; */
   if (window.scrollTo)
      window.scrollTo(0, 0);

   // register callback
   fn_ca_callback = fn_callback;
   
   // for IE6 we need to hide select 
   if (navigator.userAgent && navigator.userAgent.search(/MSIE 6/gi) != -1)
   {
      var selects = document.getElementsByTagName("select")
      for (var s=0; s<selects.length; s++)
      {
         selects[s].style.visibility = 'hidden';
      }
   }
   
   return true;
} // end FUNCTION

/* ************************************************************************* */
// function executed when user clicks a button from "clever" alert box
//  @ e = event information
function clever_click(e)
{
   // fetch event and event target info (QUIRKSMODE)
   var targ;
   if (!e) var e = window.event;
   if (e.target) targ = e.target;
   else if (e.srcElement) targ = e.srcElement;
   if (targ.nodeType == 3) // defeat Safari bug
      targ = targ.parentNode;

   // do we have any callback?
   if (fn_ca_callback)
      fn_ca_callback(targ.id.replace(/cab/i, ""));
   // cancel box
   clever_close();
   return true;
} // end FUNCTION

/* ************************************************************************* */
// function to close a clever alert box
function clever_close()
{
   // close window
   var cleveralert = document.getElementById("cleveralert");
   if (cleveralert)
      cleveralert.parentNode.removeChild(cleveralert);
   document.getElementsByTagName('html')[0].style.overflow = 'auto';
//   document.body.style.overflow = "auto";

   // for IE6 we need to show select 
   if (navigator.userAgent && navigator.userAgent.search(/MSIE 6/gi) != -1)
   {
      var selects = document.getElementsByTagName("select")
      for (var s=0; s<selects.length; s++)
      {
         selects[s].style.visibility = 'visible';
      }
   }

   return true;
} // end FUNCTION


/* ************************************************************************* */
// FUNCTION LIBARY

/* ************************************************************************* */
// function to check of object is an array
//  @ test = variable to test
// returns true if array, false if not
function is_array(test)
{
   if (test.constructor.toString().indexOf("Array") == -1)
      return false;
   else
      return true;
} // end FUNCTION

/* ************************************************************************* */
// function to fetch window size
function get_window_size()
{
  var myWidth = 0, myHeight = 0;
  if( typeof( window.innerWidth ) == 'number' ) {
    //Non-IE
    myWidth = window.innerWidth;
    myHeight = window.innerHeight;
  } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    //IE 6+ in 'standards compliant mode'
    myWidth = document.documentElement.clientWidth;
    myHeight = document.documentElement.clientHeight;
  } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    //IE 4 compatible
    myWidth = document.body.clientWidth;
    myHeight = document.body.clientHeight;
  }
  return { "width" : myWidth, "height" : myHeight };
} // end FUNCTION

