
//<![CDATA[
        function blockFramework() /*CLASS*/ { 
    
    /*<public_properties>*/
        //this.x = "";
    
    
    /*<public_methods>*/
        this.applyConstraint            =applyConstraint;
        this.registerAutoConstraint     =registerAutoConstraint;
        this.rePaint                    =applyAutoConstraints;
        this.rePaintByList              =applyAutoConstraintByList;


    /*<private_properties>*/
        var be =new browserEvents();
        
        var registeredConstraintsList = "";
        var autoConstraintDelimiter = "[#]";
        var autoConstPartDelimiter = ",";
        
        var postResizeTimeID;
    

    /*<private_methods>*/
        //var privateMethod = implementedFunctionName;
    

    /*<constructor>*/
   
        be.appendEventH(window,"onresize", handlePostBrowserResize); // this may get invoked multiple times per browser window resize, depending on the actual browser used!


    /*<implementation>*/

        function handlePostBrowserResize(){
             applyAutoConstraints();
        }
        

        function applyAutoConstraints(){
            // This function applies each of the constraints in the registeredConstraintsList
            
            var AutoCs =registeredConstraintsList.split(autoConstraintDelimiter);
            var d =autoConstPartDelimiter;
            
            for(var i=0; i<AutoCs.length; i++){
                var currentConstraint =AutoCs[i];
                var currentConstraintParts =AutoCs[i].split(d);
                if(currentConstraint!=""){
                    //alert(currentConstraintParts[0]);
                    applyConstraint(currentConstraintParts[0], currentConstraintParts[1], currentConstraintParts[2]);
                }
            }
        }
        
        function applyAutoConstraintByList(IdList){
            // This function applies each of the constraints specified in the IdList.
            // Entries in the IdList string should be delimited by a comma.
            
            IdList +=',';                       //ensure traling ','                
            IdList =IdList.replace(/ /,"");     //remove spaces
            
            var AutoCs =registeredConstraintsList.split(autoConstraintDelimiter);
            var d =autoConstPartDelimiter;
            
            for(var i=0; i<AutoCs.length; i++){
                var currentConstraint =AutoCs[i];
                var currentConstraintParts =AutoCs[i].split(d);
                if(currentConstraint!=""){
                    //alert(currentConstraintParts[0]);
                    if(IdList.toLowerCase().indexOf(currentConstraintParts[0].toLowerCase()+',')>-1){
                        applyConstraint(currentConstraintParts[0], currentConstraintParts[1], currentConstraintParts[2]);
                    }
                }
            }
        }

        
        function registerAutoConstraint(targetElementID, stylePart, constraintExp, executeNow){
            // This function adds(registers) an targetElementID to the internal list of constraints that are to be maintained
            // automatically across the resizing of the browser window.
            // Optionaly if executeNow is true, the constraint will be applied immediately after registration.
            
            
            var d=autoConstPartDelimiter;
            registeredConstraintsList += targetElementID + d + stylePart + d + constraintExp + autoConstraintDelimiter;
            
            if(executeNow){
                applyConstraint(targetElementID, stylePart , constraintExp);
            }
            
            //alert(registeredConstraintsList);
        }

        
        function applyConstraint(targetElementID, stylePart, constraintExp){
            // This function will apply the constraintExp style formatting to the specified element (targetElementID)
            // 
            // The stylePart refer to the position/dimension begin of the target element that is begin constrained.
            // stylePart can take the following fixed values:
            //      w/h/t/l             Width/Height/Top/Left
            //
            // The constraintExp can be formed using the syntax below:
            //      bw/bh               Browser width/height
            //      <int>px             Fixed pixel value
            //      w@id:<elementID>    width of <elementID>
            //      h@id:<elementID>    height of <elementID>
            //      t@id:<elementID>    top of <elementID>
            //      l@id:<elementID>   left of <elementID> 
            //			    
            //      eg to set some element's width to 10% of the browser's you can use:
            //      .ApplyConstraints("someElementID","W", "(bw * 0.1)")
            //      
            //      other examples:
            //      .ApplyConstraint("leftPanel","w","bw-200px-l@id:bottomPanel");
            //      .ApplyConstraint("leftPanel","l","bh-45px");
            //
            // CSS preparation code: Include the following CSS into your web page to ensure browser consistancy:
            //
            //      *{position:relative;padding:0px;margin:0px;border:0px;border:0px solid white;z-index:99;}
            //      html{position:fixed;height:100%;overflow:hidden;padding:0px;margin:0px;border:0px solid white;}
            //      body {position:static;height:100%;overflow:hidden;padding:0px;margin:0px;border:0px solid white;}
            //      ul, ol{margin:0px 0px 0px 22px;}
            //
            // Note: When creating a page framework, use the following to ensure overflow/padding consistancy 
            // across browsers:
            //
            //      <div id="containter" style="overlfow:auto">
            //          <div style="padding">
            //              content goes here..
            //          </div>
            //      </div>
            //
            //  The important thing above is that the containter will display scollbars whilst the separate inner div 
            //  displays the padded content. Attempting to pad the container would cause the rendering to become 
            //  inconsistant across browser, as padding/borders/margins and other overflow attributes it seems 
            //  are not factored into the javascript model in some gekco-based browsers.

            
            //parse the constraintExp:
            var curToken =""; //bw, bh, [whtl]@id:<element> etc.
            var curChar ="";
            var evalReadyStr ="";
            for (var i=0; i<constraintExp.length; i++){
                cc =constraintExp.charAt(i); //current character.
                
                if((cc=="(") || (cc==")") ||(cc=="/") ||(cc=="*") ||(cc=="+") ||(cc=="-")){
                    //op found, process the current token
                    if(curToken!=""){
                        evalReadyStr += processToken(curToken);
                        curToken="";
                    }
                    //append op
                    evalReadyStr += cc.toString();
                }
                else{
                    //append token
                    curToken += cc!="" ? cc.toString() : "";
                }
                
                //process the trailing token:
                if(i==(constraintExp.length-1)){
                    if(curToken!=""){
                        evalReadyStr += processToken(curToken);
                        curToken="";
                    }
                }
            }
            
            
            //token handler:
            function processToken(token){
                var orig=token;
                
                if(token.indexOf("bh")>-1){
                    token=token.replace(/bh/, document.body.clientHeight.toString());
                }
                else if (token.indexOf("bw")>-1){
                    token=token.replace(/bw/, document.body.clientWidth.toString());
                }
                else if (token.indexOf("px")>-1){
                    token=token.replace(/px/, "");
                }
                else if (token.indexOf("@id:")>-1){
                    var elementID=token.split(":")[1];
                    
                    if(token.indexOf("w@id:")>-1){
                        token=parseFloat(document.getElementById(elementID).offsetWidth);
                    }
                    else if(token.indexOf("h@id:")>-1){
                        token=parseFloat(document.getElementById(elementID).offsetHeight);
                    }
                    else if(token.indexOf("t@id:")>-1){
                        token=findTopLeftPos(elementID)[0];
                    }
                    else if(token.indexOf("l@id:")>-1){
                        token=findTopLeftPos(elementID)[1];}
                }
               
               
                function findTopLeftPos(elementID) {
	                var obj =document.getElementById(elementID);
	                var curleft = curtop = 0;
	                if (obj.offsetParent) {
		                curleft = obj.offsetLeft
		                curtop = obj.offsetTop
		                while (obj = obj.offsetParent) {
			                curleft += obj.offsetLeft
			                curtop += obj.offsetTop
		                }
	                }
	                return [curtop,curleft];
                }


                //alert(orig + " adjusted to " + token);
                return "(" + token + ")";
            }
            
            //alert("About to eval: "+evalReadyStr);
            var CalculatedValue= eval(evalReadyStr);
            //alert("Evaluated to: "+CalculatedValue);
            
            
            //Apply the CalculatedValue to the stylePart
            stylePart=stylePart.toLowerCase();
            
            if(stylePart=="t" || stylePart=="l"){
                if(stylePart=="t"){document.getElementById(targetElementID).style.top =CalculatedValue.toString() + "px";}
                else if(stylePart=="l"){document.getElementById(targetElementID).style.left =CalculatedValue.toString() + "px";}
            }
            else if(CalculatedValue>-1){ // Width or Height cannot be set to negative values (causes JS errors!)
                if(stylePart=="w"){document.getElementById(targetElementID).style.width =CalculatedValue.toString() + "px";}
                else if(stylePart=="h"){document.getElementById(targetElementID).style.height =CalculatedValue.toString() + "px";}
            }
    }
}
function browserEvents() /*CLASS*/ { 
    
    /*<public_properties>*/
        //this.x = "";
    
    
    /*<public_methods>*/
        this.appendEventH =appendEventH;
        this.removeEventH =removeEventH;

    
    /*<private_properties>*/
        //var registeredConstraintsList = "";
    

    /*<private_methods>*/
        //var privateMethod = implementedFunctionName;
    

    /*<constructor>*/
       
    

    /*<implementation>*/
        function appendEventH(targetObj, eventString, attachFunc) {
            // This function unobtrusively appends the the attachFunc function to the targetObj.
            // This means that any javascript functions already attached to targetEvent will preserved
            // and executed before attachFunc. Tested in IE5.0/5.5/6.0, FF, Oprea & Moz.
            //
            // The eventString is string containing the standard DOM event triggers: onclick, onmousedown, ..etc.
            //
            // syntax example:  appendEventH(window, "onload", myFunc); 
            //                  appendEventH(document.getElementById('myElem'), "onclick", myFunc);
             
            //register only if not already registered!
            if(isEventRegistered(targetObj, eventString, attachFunc)==false){
                if(targetObj.addEventListener){
                    //Moz append event
                    
                    //.onmousemove=     vs     .addEventListener('mousemove',...  Firefox,Why??
                    var currentHandler;
                    eval("currentHandler =targetObj."+eventString+";");
                    if (typeof currentHandler != 'function') {
                        eval("targetObj." +eventString+ " =attachFunc;");
                        //alert(eventString + " attached as single handler!");
                    }
                    else{                    
                        var ommitOn =eventString.split("on")[1];
                        targetObj.addEventListener(ommitOn, attachFunc, false);
                    }
                }
                else if(targetObj.attachEvent){
                    //IE append event
                    targetObj.attachEvent(eventString, attachFunc);
                }
                else{
                    alert("ERROR: unable to append event!");    
                }
                //alert(eventString + " appended to ["+targetObj.id+"]'s existing handlers!");
                
                //Mark (DOM globaly) the fact that this has been registered, to prevent re-registering.
                gbl_EventsRegistered[gbl_EventsRegistered.length]={obj:targetObj, evtstr:eventString, func:attachFunc};
            }
            else{
                //request already registered, no need to duplicate!
                //alert(eventString + " already registered to ["+targetObj.id+"]!");
            }
        }
        
        
        function removeEventH(targetObj, eventString, attachFunc) {
            if(targetObj.removeEventListener){
                //Moz detach event

                //.onmousemove=     vs     .addEventListener('mousemove',...  Firefox,Why??
                var currentHandler;
                eval("currentHandler =targetObj."+eventString+";");
                if (typeof currentHandler == 'function' && currentHandler==attachFunc) {
                    eval("targetObj." +eventString+ " =null;");
                    //alert(eventString + " removed as single handler!");
                }
                else{
                    var ommitOn =eventString.split("on")[1];
                    targetObj.removeEventListener(ommitOn, attachFunc, false);
                }
            }
            else if(targetObj.detachEvent){
                //IE detach event
                targetObj.detachEvent(eventString, attachFunc);
            }
            else{
                alert("ERROR: unable to remove event!");
            }
            
            //Mark (DOM globaly) the fact that this has been UNregistered, to allow further registering.
            var excluded = new Array();
            for (var i=0; i< gbl_EventsRegistered.length;i++){
                if (gbl_EventsRegistered[i].evtstr!=eventString && gbl_EventsRegistered[i].obj!=targetObj && gbl_EventsRegistered[i].func!=attachFunc){
                    excluded[excluded.length]=gbl_EventsRegistered[i];
                }
            }
            gbl_EventsRegistered =excluded;
            
            //alert(eventString + " removed from ["+targetObj.id+"] and - internal counter reports:" + isEventRegistered(targetObj, eventString, attachFunc));
        }
        
        
        
        function isEventRegistered(targetObj, eventString, attachFunc){
            var result=false;
            for (var i=0; i< gbl_EventsRegistered.length;i++){
                if(gbl_EventsRegistered[i].evtstr==eventString){
                    if(gbl_EventsRegistered[i].obj==targetObj && gbl_EventsRegistered[i].func==attachFunc){
                        result=true;
                        break;
                    }
                }
            }
            return result;
        }
}



//DOM Global Code:
//================

/*<global_properties>*/ 
    var gbl_EventsRegistered = new Array();
    //Each element will hold an object of {obj:JavascriptObject, evtstr:String, func;JavascriptFunction}

/*<global_methods>*/

/*<global_autoexec>*/
function dragManager() /*CLASS*/ {
    /*
    Class notes:
    ============
    This class allows HTML elements to be made dragable. The object holds an internal list of dragItems
    which are HTML elements that been registered as dragable items. 
    
    For each registered dragItem you can go on to register hotspots. Hotspots are HTML elements nested within 
    the dragItem element that are used as "drag handles" for the registered drag item. This, for example, 
    allows for an entire DIV to move around the page by actually dragging, say, an IMG nested within it. 
    The IMG would in this case be the DIV's hotspot. 
    
    The DIV can also become its own hotspot (selfHotSpot), meaning that the DIV moves itself when you drag 
    on any part of the DIV's visible area.
    
    "Drag along's" are associated HTML elements that get moved along/in-sync with the dragitem.
    */

    
    /*<public_properties>*/
        //this.x=somthing;
                
    
    /*<public_methods>*/
        this.registerDragItem       =registerDragItem;
        this.unregisterDragItem     =unregisterDragItem;
        this.registerItemHotSpot    =registerItemHotSpot;
        this.unregisterItemHotSpot  =unregisterItemHotSpot;
        this.isRegisteredDragItem   =isIdARegisteredDragItem;
        this.registerDragAlong      =registerDragAlong;
        this.unregisterDragAlong    =unregisterDragAlong;
        
        //setItemDragTrigger is to indicate which mouse button is to trigger the drag motion. This is set per dragitem.
        this.setItemDragTrigger     =setItemDragTrigger;    
            
    
    /*<private_properties>*/
        var be =new browserEvents();
        var ut =new utility();
        
        var dragItems = new Array();
            // Each array element is a RECORD:
            //  .elementId      is the name of the element registered for dragging.
            //  .onDragStart    the function called before dragging (onmousedown).
            //  .onDragMove     the function called as the element is dragged (onmousemove).
            //  .onDragEnd      the final function call as the element is dropped (onmouseup).
            //  .itemHotSpots   an Array that holds a list of hotspots (element ID's) associated with this dragItem.
            //  .dragTrigger    either: leftclick|rightclick|wheelclick - this sets mouse button required to trigger the drag motion.
            //  .dragAlongWith  an Array of AlongItem's that each represent another elements id that also need to be dragged along with the main item.
            //                  AlongItem:{id:string, originX:int , originY:int}
            
        var dragMode="idle";            // lifecycle values are: [idle>>1stdrag>>dragging]
        var currentDragElement;         // used to identify the current element during mouse event handling
        var mouseDownX, mouseDownY;
        var targetX, targetY;
        
        var origOnSelectStart =null;
        
        
    /*<private_methods>*/
        //var x = x;
        
    
    /*<constructor>*/

    
    /*<implementation>*/
        function registerDragItem(uElementId, onDragStartF, onDragMoveF, onDragEndF, selfHotSpot){
            //Here we ADD uElementId to our dragItems list.
            dragItems[dragItems.length] ={elementId:uElementId, onDragStart:onDragStartF, onDragMove:onDragMoveF, onDragEnd:onDragEndF, itemHotSpots:new Array(), dragTrigger:"leftclick", dragAlongWith:new Array()};
            
            //register dragitem's event handlers
            be.appendEventH(document.getElementById(uElementId),"onmousedown", onmousedown);
            be.appendEventH(document.getElementById(uElementId),"onmousemove", onmousemove);
            be.appendEventH(document.getElementById(uElementId),"onmouseup", onmouseup);
            
            if(selfHotSpot==true){
                //register itself as one of it's own hotspots:
                registerItemHotSpot(uElementId, uElementId);
            } 
        }
        
        
        function unregisterDragItem(uElementId){
            // Here we REMOVE uElementId from our dragItems list.
            var excluded = new Array();
            for (var i=0; i< dragItems.length;i++){
                if (dragItems[i].elementId != uElementId){
                    excluded[excluded.length]=dragItems[i];
                }
                else{
                    //drag item to be removed found. unregister dragitem's event handlers
                    be.removeEventH(document.getElementById(uElementId),"onmouseup", onmouseup); 
                    be.removeEventH(document.getElementById(uElementId),"onmousemove", onmousemove);
                    be.removeEventH(document.getElementById(uElementId),"onmousedown", onmousedown);
                }
            }
            dragItems =excluded;
        }
        
        
        function registerDragAlong(uElementId, uDragAlongId){
            // This function adds the 'drag along' item (uDragAlongId) to the existing dragitem (uElementId).
            var uElement =document.getElementById(uElementId);
            if (isRegisteredDragitem(uElement)){
                //Add the 'drag along'
                var di =getDragItem(uElement);
                var dAlongObj =document.getElementById(uDragAlongId);
                di.dragAlongWith[di.dragAlongWith.length]={id:uDragAlongId, originX:parseInt(dAlongObj.style.left+0, 10), originY:parseInt(dAlongObj.style.top+0, 10)};
            }
        }
        
        function unregisterDragAlong(uElementId, uDragAlongId){
            // This function removes the 'drag along' item (uDragAlongId) from the existing dragitem (uElementId).
            var uElement =document.getElementById(uElementId);
            if (isRegisteredDragitem(uElement)){
                // Remove the 'drag along'
                var di =getDragItem(uElement);
                var excluded = new Array();
                for (var i=0; i< di.dragAlongWith.length;i++){
                    if (di.dragAlongWith[i].id != uDragAlongId){
                        excluded[excluded.length]=di.dragAlongWith[i];
                    }
                }
                di.dragAlongWith =excluded;
            }
        }
        
        
        function registerItemHotSpot(uElementId, uHotItemId){
            // This function adds the hotspot(uHotItemId) to the existing dragitem (uElementId).
            var uElement =document.getElementById(uElementId);
            if (isRegisteredDragitem(uElement)){
                //Add the item hotspot
                var di =getDragItem(uElement);
                di.itemHotSpots[di.itemHotSpots.length]=uHotItemId;
            }
        }
        
        
        function unregisterItemHotSpot(uElementId, uHotItemId){
            // This function removes the hotspot(uHotItemId) from the existing dragitem (uElementId).
            var uElement =document.getElementById(uElementId);
            if (isRegisteredDragitem(uElement)){
                // Remove the item hotspot
                var di =getDragItem(uElement);
                var excluded = new Array();
                for (var i=0; i< di.itemHotSpots.length;i++){
                    if (di.itemHotSpots[i] != uHotItemId){
                        excluded[excluded.length]=di.itemHotSpots[i];
                    }
                }
                di.itemHotSpots =excluded;
            }
        }
        
        
        function isRegisteredDragitem(uElement){
            var result=false;
            for (var i=0; i< dragItems.length;i++){
                if(dragItems[i].elementId == uElement.id){
                    result=true;
                    break;   
                }
            }
            return result;
        }
        
        function isIdARegisteredDragItem(uElementId){
            var result=false;
            for (var i=0; i< dragItems.length;i++){
                if(dragItems[i].elementId == uElementId){
                    result=true;
                    break;   
                }
            }
            return result;
        }
        
        
        function getDragItem(uElement){
            var result=null;
            for (var i=0; i< dragItems.length;i++){
                if(dragItems[i].elementId == uElement.id){
                    result=dragItems[i];
                    break;   
                }
            }
            return result;
        }
        
        
        function qualifyiedHotSpot(uElement, uHotItem){
            var result=false;
            if (isRegisteredDragitem(uElement)){
                 
                var di =getDragItem(uElement);
                for (var i=0; i< di.itemHotSpots.length;i++){
                    if (di.itemHotSpots[i] == uHotItem.id){
                        result=true;
                        break;
                    }
                }
            }
            return result;
        }

        
        function onmousedown(evt){
            // This gets fired when the user initiate the dragging process by clicking the mouse button down.
            var evt =(evt || window.event);
            var fobj =(evt.srcElement || evt.target);
            
            //capture the event instigator:
            var srcClickItem=fobj;
            
            var topelement = (document.getElementById && !document.all) ? "HTML" : "BODY";
            while (fobj.tagName != topelement && !isRegisteredDragitem(fobj)){
                fobj = (fobj.parentNode || fobj.parentElement);
            }
            
            currentDragElement = fobj; //mark the element being dragged (not the hot spot!)
            try{
                if (ut.mouseButtonUsed(evt)==getDragItem(currentDragElement).dragTrigger){ //only react if the right mouse button was used to trigger the drag.
                
                    if (qualifyiedHotSpot(fobj, srcClickItem)){ //qualifies only if srcClickItem is a hotspot of fobj
                        //drag cycle has now started:
                        
                        //prevent text-selection while dragging
                        origOnSelectStart=document.onselectstart;
                        document.onselectstart=function(){return false;};
                        
                        //Attach these handlers to the entire document. This will allow the drag to to continue even when 
                        // the mouse drag motion spills over onto non drag-registered objects.
                        be.appendEventH(document, "onmousemove", onmousemove);
                        be.appendEventH(document, "onmouseup", onmouseup);
                        
                        //set the mode (inidicator to oncomming functions)
                        dragMode="1stdrag";
                        
                        //store inital states/origins:
                        targetX = parseInt(currentDragElement.style.left+0, 10);
                        targetY = parseInt(currentDragElement.style.top+0, 10);
                        mouseDownX = evt.clientX;
                        mouseDownY = evt.clientY;
                        var CurrDIObj =getDragItem(currentDragElement);
                        for(var i=0; i<CurrDIObj.dragAlongWith.length; i++){
                            var dAlongObj =document.getElementById(CurrDIObj.dragAlongWith[i].id);
                            CurrDIObj.dragAlongWith[i].originX =parseInt(dAlongObj.style.left +0, 10);
                            CurrDIObj.dragAlongWith[i].originY =parseInt(dAlongObj.style.top  +0, 10);
                        }
                        evt.cancelBubble =true;
                        return false;//<-- important! Prevents the default browser onmousedown action.
                    }
                    else{
                        //item should not be dragged as the initiating 
                        //srcClickItem is not a valid/registerd hotspot!
                        
                        return true; //this will allow the normal browser mouse event to continue.
                    }
                }
            }
            catch(err){ /* alert("dragManager onmousedown ERROR:" +err.description);covers the dragTrigger is null or not object in IE */}
        }
        

        function onmousemove(evt){
            // This gets fired per-pixel-move of the user's action.
            var evt =(evt || window.event);
                      
            if(dragMode!="idle"){
                if (ut.mouseButtonUsed(evt)==getDragItem(currentDragElement).dragTrigger){ //only react if the right mouse button was used to trigger the drag.
                    if (dragMode=="1stdrag"){
                        // make sure user has entered into the dragging process by actually moving the mouse.
                        // the MouseXY for this event must be different than those caputred on mousedown!
                        if ((mouseDownX+mouseDownY)!=evt.clientX+evt.clientY){ 
                            //Run the clients custom onDragStart function:
                            try{
                                var dragItem=getDragItem(currentDragElement);
                                dragItem.onDragStart(evt);
                            }
                            catch(err){
                                //Handle errors here
                            }
                            
                            dragMode="dragging"; // allow regular dragging to take place
                        }
                    }
                    
                    if (dragMode=="dragging"){
                        
                        //Move the dragItem
                        currentDragElement.style.left = (targetX + evt.clientX - mouseDownX).toString()+ "px";
                        currentDragElement.style.top  = (targetY + evt.clientY - mouseDownY).toString()+ "px";
                        //Then any that need to be Dragged AlongWith:
                        var CurrDIObj =getDragItem(currentDragElement);
                        for(var i=0; i<CurrDIObj.dragAlongWith.length; i++){
                            var AlongObj = document.getElementById(CurrDIObj.dragAlongWith[i].id);
                            //alert((parseInt(currentDragElement.style.left)+(targetX - parseInt(currentDragElement.style.left))) + "px");
                            AlongObj.style.left =(CurrDIObj.dragAlongWith[i].originX + evt.clientX - mouseDownX).toString()+ "px";
                            AlongObj.style.top  =(CurrDIObj.dragAlongWith[i].originY + evt.clientY - mouseDownY).toString()+ "px";
                        }
                       
                        
                        //Run the clients custom onDragMove function:
                        try{
                            var dragItem =getDragItem(currentDragElement);
                            dragItem.onDragMove(evt);
                        }
                        catch(err){
                            //Handle errors here
                        }
                    }
                    
                    evt.cancelBubble =true;
                    return false;   //when not idle (i.e. dragging something), this prevents browser
                                    //from executing its default drag action, which may interfere.
                }
            }
            else{
                //currently in idle mode (not dragging anything)! 
                //allow browser to perform its default drag action (drag-select text, etc)!
                return true;    
            }
            
        }


        function onmouseup(evt){
            
            // This gets fired when the user has finished dragging by releasing the mouse button.
            var evt =(evt || window.event);

            if (dragMode!="idle"){
                if (ut.mouseButtonUsed(evt)==getDragItem(currentDragElement).dragTrigger){ //only react if the right mouse button was used to trigger the drag.
                    //drag cycle is now complete!

                    if (dragMode!="1stdrag"){
                        //Run the clients custom onDragEnd function:
                        try{
                            var dragItem=getDragItem(currentDragElement);
                            dragItem.onDragEnd(evt);
                        }
                        catch(err){
                            //Handle errors here
                        }
                    }
                    
                    //return mode to 'idle'
                    dragMode="idle";
                    
                    //restore the initial (client) selectstart handler
                    document.onselectstart =origOnSelectStart;
                    
                    //un-apply the document level event handlers (spill-over handlers!)
                    be.removeEventH(document, "onmouseup", onmouseup);
                    be.removeEventH(document, "onmousemove", onmousemove);
                    
                    evt.cancelBubble =true;
                    return false;   //when not idle (i.e. dragging something), this prevents browser
                                    //from executing its default drag action, which may interfere.
                }
            }
            else{
                //currently in idle mode (not dragging anything)! 
                //allow browser to perform its default drag action (drag-select text, etc)!
                return true;
            }
            
        }
        
        function setItemDragTrigger(uElementId, trigger){
            var targetDragItem =getDragItem(document.getElementById(uElementId));
            
            var trigger =trigger.toLowerCase();
            if(ut.isValidTrigger(trigger)==true){
                targetDragItem.dragTrigger =trigger;
            }
        }
}
function xHTTP(initURL) /*CLASS*/ {
    /*
    Class notes:
    ============
    This class allows javascript to initiate background HTTP requests to the web server. Calls can be made
    via POST or GET and can be performed synchronously or asynchronously (Returned/Handled respectively)
    
    The object works under the following browsers: IE5.0/5.5/6.0, Firefox 1.0, Opera 9, Netscape 7.2, Mozilla 1.7.3
    
    Example usage:
    
        t=new xHTTP("./run_this_script.asp");
        t.url="./on_second_thoughts_run_this_one.asp";
        
        t.dataString="blah=blah&foo=bar";
        
        //Send the HTTP request in one of four different ways (t.doX()):
        
        alert("server say's :" + t.doReturnedGET());
        alert("server say's :" + t.doReturnedPOST());
        
        t.doHandledGET(handler);
        t.doHandledPOST(handler);
        //Note you can perform a doHandled..() call without nominating a handler by using a null parameter instead.
        //e.g. t.doHandledGET(null);
        
        
        function handler(responseTxt){
            alert("server say's :" + responseTxt);
        }
    
    Before sending a HTTP request to the sever you may need/want to use the .setRequestHeader method to set as many 
    HTTP header entries as required for the request e.g.   
    
        .setRequestHeader("Content-Type", "text/xml; charset=utf-8");
        
        
        for passing form data using POST, remember to set:
        .isFormData(true);
        
        this will set the headers as if the following required headers for you:
        .setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 
        .setRequestHeader('Content-Length', <<POSTData.chars.length>>);
    */
	
	
	/*<public_properties>*/
	    
	    this.url=null;
	    this.dataString="";
	
	
	/*<public_methods>*/
	    
	    this.setRequestHeader   =addHTTPRequestHeader;
	    this.doReturnedGET      =fetch_GET_Sync;
	    this.doHandledGET       =fetch_GET_aSync;
	    this.doReturnedPOST     =fetch_POST_Sync;
	    this.doHandledPOST      =fetch_POST_aSync;
	    this.isFormData         =set_IsForm;
	
	
	/*<private_properties>*/
	    
	    var construct=null;
	    var xmlHTTP=null;
	    var isForm=false;
	    
	    /*Custom HTTP Request Headers*/
	    var CustomHTTPHeaders = new Array();
	        // Each array element is a RECORD:
            //  .label           [string] eg. "Content-Type".
            //  .value           [string] eg. "application/x-www-form-urlencoded"
        /**/
	    
	/*<private_methods>*/
        //var x = x;

	
	/*<constructor>*/
	    //Set the URL, if passed on object creation.
	    if (initURL!=""){this.url=initURL;}
    	
	    if (navigator.userAgent.indexOf("MSIE")>=0){ 
		    //IE 5.0, 5.5, 6.0, +?
		    construct='xmlHTTP=new ActiveXObject("Microsoft.XMLHttp");';
	    }
	    else if (navigator.userAgent.indexOf("Mozilla")>=0){
		    construct='xmlHTTP=new XMLHttpRequest();';
	    }
	    else if (navigator.userAgent.indexOf("Opera")>=0){
	        construct='xmlHTTP=new XMLHttpRequest();';
	    }
    	
	    try{
		    eval(construct);
	    }
	    catch(e){handleException(e);}
	
	
	/*<implementation>*/

	    function set_IsForm(tf){
	        isForm =(tf==true)?true:false;
	    }
	    function addHTTPRequestHeader(custLabel, custValue){
	        //Append request header to end of list:
	        CustomHTTPHeaders[CustomHTTPHeaders.length]={label:custLabel, value:custValue};
	    }
	    
	    function applyCustomRequestHeaders(){
		    if (xmlHTTP!=null){
			    for(var i=0; i<CustomHTTPHeaders.length;i++){
			        xmlHTTP.setRequestHeader(CustomHTTPHeaders[i].label, CustomHTTPHeaders[i].value);
			        //alert(CustomHTTPHeaders[i].label + CustomHTTPHeaders[i].value);
			    }
		    }
	    }


	    function buildGETURL(url,dataString){
		    var result=url;
		    if (dataString!=''){result+="?"+dataString;}
		    return result;
	    }


	    function sendXMLHTTP(xmlHTTPObj,data){
		    if(data.length>0 && isForm==true){
		        xmlHTTPObj.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
		        xmlHTTPObj.setRequestHeader('Content-Length', data.length);
		    }
		    xmlHTTPObj.send(data);
	    }

    	
	    function handleException(e){
		    alert("Error occurred: " + e.description);
	    }
    	
    	
	    function fetch_GET_Sync(){
		    try{
			    xmlHTTP.open("GET",buildGETURL(this.url, this.dataString),false);
			    applyCustomRequestHeaders();
			    sendXMLHTTP(xmlHTTP,null);

			    return xmlHTTP.responseText;
		    }
		    catch(e){handleException(e);}
	    }


	    function fetch_GET_aSync(clientResponseHandler){
		    try{
			    xmlHTTP.onreadystatechange=function(){
				    if (xmlHTTP.readyState==4 || xmlHTTP.readyState=="complete"){
					    if(clientResponseHandler!=null){
						    clientResponseHandler(xmlHTTP.responseText);
					    }
				    }
			    }
    			
			    xmlHTTP.open("GET",buildGETURL(this.url, this.dataString),true);
			    applyCustomRequestHeaders();
			    sendXMLHTTP(xmlHTTP,null);
		    }
		    catch(e){handleException(e);}
	    }


	    function fetch_POST_Sync(){
		    try{
			    xmlHTTP.open("POST",this.url,false);
			    applyCustomRequestHeaders();
			    sendXMLHTTP(xmlHTTP,this.dataString);
    			
			    return xmlHTTP.responseText;
		    }
		    catch(e){handleException(e);}
	    }


	    function fetch_POST_aSync(clientResponseHandler){
		    try{
			    xmlHTTP.onreadystatechange=function(){
				    if (xmlHTTP.readyState==4 || xmlHTTP.readyState=="complete"){
					    if(clientResponseHandler!=null){
						    clientResponseHandler(xmlHTTP.responseText);
					    }
				    }
			    }
    			
			    xmlHTTP.open("POST",this.url,true);
			    applyCustomRequestHeaders();
			    sendXMLHTTP(xmlHTTP,this.dataString);
		    }
		    catch(e){handleException(e);}
	    }
}
function utility() /*CLASS*/ {
    
    /*<public_properties>*/
        this.browserVersion =getBrowserVersion();
        
    
    /*<public_methods>*/
        //this.g = bob;
        this.getURLResourceName         =getURLResourceName;
        this.getURLResourceNameAndGET   =getURLResourceNameAndGET;
        this.removeTrailing             =removeTrailing;
        this.getElementMouseXY          =getElementMouseXY;
        this.getDocMouseXY              =getDocMouseXY;
        this.URLEncode                  =URLEncode;
        this.URLDecode                  =URLDecode;
        this.mouseButtonUsed            =mouseButtonUsed;
        this.isValidTrigger             =isValidTrigger;
        this.setCookie                  =setCookie;
        this.readCookie                 =readCookie;
    
    
    /*<private_properties>*/
        //var x = "";
    
    
    /*<private_methods>*/
        //var x = x;
        
    
    /*<constructor>*/
        
    
    /*<implementation>*/
        function setCookie(cookieName,cookieValue,nDays) {
            //When nDays is either not specified or 0 the cookie created is valid 
            //only until the browser (browser process) is closed.
            var today = new Date();
            var expire = new Date();
            var expireStr="";
            if (!(nDays==null || nDays==0)) {
                expire.setTime(today.getTime() + 3600000*24*nDays);
                expireStr =expire.toGMTString();
            }
            document.cookie = cookieName+"="+escape(cookieValue) + ";expires="+expireStr;
        }
        
        function readCookie(cookieName) {
            var theCookie=""+document.cookie;
            var ind=theCookie.indexOf(cookieName);
            if (ind==-1 || cookieName=="") return ""; 
            var ind1=theCookie.indexOf(';',ind);
            if (ind1==-1) ind1=theCookie.length; 
            return unescape(theCookie.substring(ind+cookieName.length+1,ind1));
        }

        function getBrowserVersion(){
	        // This function detects the browser version according to the contents of the 'navigator.userAgent' string.
	        var result="Unknown";

            if((navigator.userAgent.indexOf("MSIE 7.")!=-1) && (navigator.userAgent.indexOf("Opera")==-1)){
		        result="IE7";
	        }
	        else if((navigator.userAgent.indexOf("MSIE 6.")!=-1) && (navigator.userAgent.indexOf("Opera")==-1)){
		        result="IE6";
	        }
	        else if(navigator.userAgent.indexOf("MSIE 5.5")!=-1){
		        result="IE5.5";
	        }
	        else if(navigator.userAgent.indexOf("MSIE 5.01")!=-1){
		        result="IE5.0";
	        }
	        else if(navigator.userAgent.indexOf("Netscape")!=-1){
		        result="Netscape (gecko)";
	        }
	        else if(navigator.userAgent.indexOf("Firefox")!=-1){
		        result="Firefox (gecko)";
	        }
	        else if(navigator.userAgent.indexOf("Opera")!=-1){
		        result="Opera";
	        }
	        else if((navigator.userAgent.indexOf("Mozilla")!=-1) && (navigator.userAgent.indexOf("Firefox")==-1) && (navigator.userAgent.indexOf("Netscape")==-1) && (navigator.userAgent.indexOf("MSIE")==-1)){
		        result="Mozilla (gecko)";
	        }

	        return result;
        }

        function getURLResourceName(){
	        // This function returns the pure filename contained in the currently loaded URL
	        // All pre ('http://...') and post ('?=...') markup will be stipped out, leaving the filename only.
        	
	        return document.URL.split('?')[0].split('/')[document.URL.split('?')[0].split('/').length-1];
        }
        function getURLResourceNameAndGET(){
	        // This function returns the pure filename and any cgi params contained in the currently loaded URL
	        // All pre ('http://...') markup will be stipped out.
        	
	        return document.URL.split('/')[document.URL.split('/').length-1];
        }

        function removeTrailing(str,remStr){
            // This function removes the trailing remStr from str. Useful for rounding off delimited strings.
        	
	        var match=true;
	        for (var i=0;i<remStr.length;i++){
		        if (str.charAt(str.length-(i+1))!=remStr.charAt(remStr.length-(i+1))){
			        match=false;
		        }
	        }
	        if(match){
		        return str.substring(0,str.length-remStr.length);
	        }
	        else{
		        return str;
	        }
        }
        
        function getDocMouseXY(evt){
            // This function retrives the current mouse x and y co-ordinates relative to the top-left of the
            // document page (NOT the Browser Viewport!) - i.e. if a page scrolls outside the browser viewport then this
            // function will return the "larger-than-viewport" co-ordinates of the mouse location on the document.
            //
            // This function returns an object. The .x .y properties will contain the co-ordinate values.
            // Tested in IE(5.0 5.5 6.0 FF, Oprea, Mozilla) ~PP
	        var evt = (evt || window.event);
	        
	        if(evt.pageX || evt.pageY){
		        return {x:evt.pageX, y:evt.pageY};
	        }
	        
	        return {
		        x:(evt.clientX + document.body.scrollLeft) - document.body.clientLeft,
		        y:(evt.clientY + document.body.scrollTop)  - document.body.clientTop
	        };
        }
        
        
        function getElementMouseXY(evt, elementID){
            // This function retrives the current mouse x and y co-ordinates relative to the top-left of the
            // specified element "elementID"
            //
            // This function returns an object. The .x .y properties will contain the co-ordinate values.
            //
            // Syntax example:  getElementMouseXY(e, "MyImage")      // e is the event object supplied by browser
            //                  ..<img id="MyImage" .../>...
	        var evt = (evt || window.event);
	        
	        if(evt.pageX || evt.pageY){
		        return {
		            x:(evt.pageX - findTopLeftPos(elementID)[1]), 
		            y:(evt.pageY - findTopLeftPos(elementID)[0])
		        };
	        }
	        
	        return {
		        x:((evt.clientX + document.body.scrollLeft) - document.body.clientLeft)-(findTopLeftPos(elementID)[1]),
		        y:((evt.clientY + document.body.scrollTop)  - document.body.clientTop)-(findTopLeftPos(elementID)[0])
	        };
	        
	        function findTopLeftPos(elementID) {
                var obj =document.getElementById(elementID);
                var curleft = curtop = 0;
                if (obj.offsetParent) {
	                curleft = obj.offsetLeft
	                curtop = obj.offsetTop
	                while (obj = obj.offsetParent) {
		                curleft += obj.offsetLeft
		                curtop += obj.offsetTop
	                }
                }
                return [curtop,curleft];
            }
        }
        

        function URLEncode(str)
        {
            str =str.toString(); //make sure we process a string datatype to ensure correct processing (passing str as numeric will yeid an empty string);
	        
	        // The Javascript escape() and unescape() functions do not correspond
	        // with what browsers actually do...
	        var SAFECHARS = "0123456789" +					// Numeric
					        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +	// Alphabetic
					        "abcdefghijklmnopqrstuvwxyz" +
					        "-_.!~*'()";					// RFC2396 Mark characters
	        var HEX       = "0123456789ABCDEF";

	        var plaintext = str;
	        var encoded = "";
	        for (var i = 0; i < plaintext.length; i++ ) {
		        var ch = plaintext.charAt(i);
	            if (ch == " ") {
		            encoded += "+";				// x-www-urlencoded, rather than %20
		        } else if (SAFECHARS.indexOf(ch) != -1) {
		            encoded += ch;
		        } else {
		            var charCode = ch.charCodeAt(0);
			        if (charCode > 255) {
			            alert( "Unicode Character '" 
                                + ch 
                                + "' cannot be encoded using standard URL encoding.\n" +
				                  "(URL encoding only supports 8-bit characters.)\n" +
						          "A space (+) will be substituted." );
				        encoded += "+";
			        } else {
				        encoded += "%";
				        encoded += HEX.charAt((charCode >> 4) & 0xF);
				        encoded += HEX.charAt(charCode & 0xF);
			        }
		        }
	        } // for

	        str = encoded;
	        return str;
        };

        function URLDecode(str)
        {
           str =str.toString(); //make sure we process a string datatype to ensure correct processing (passing str as numeric will yeid an empty string);
           
           // Replace + with ' '
           // Replace %xx with equivalent character
           // Put [ERROR] in output if %xx is invalid.
           var HEXCHARS = "0123456789ABCDEFabcdef"; 
           var encoded = str
           var plaintext = "";
           var i = 0;
           while (i < encoded.length) {
               var ch = encoded.charAt(i);
	           if (ch == "+") {
	               plaintext += " ";
		           i++;
	           } else if (ch == "%") {
			        if (i < (encoded.length-2) 
					        && HEXCHARS.indexOf(encoded.charAt(i+1)) != -1 
					        && HEXCHARS.indexOf(encoded.charAt(i+2)) != -1 ) {
				        plaintext += unescape(encoded.substr(i,3));
				        i += 3;
			        } else {
				        //alert( 'Bad escape combination near ...' + encoded.substr(i) );
				        plaintext += "%[ERROR]";
				        i++;
			        }
		        } else {
		           plaintext += ch;
		           i++;
		        }
	        } // while
           str = plaintext;
           return str;
        }

        
        function mouseButtonUsed(e){
	        // this function indicates which mouse button triggered event e
	        // returns a string of either 'leftclick', 'rightclick' or 'wheelclick'
	        var result=null;
	        if (e.which){
		        // moz-based browsers:
		        if (e.which == 1) { result = 'leftclick' };
		        if (e.which == 2) { result = 'wheelclick' };
		        if (e.which == 3) { result = 'rightclick' }; // Mouse right-click event cannot currently be detected in Opera 7
	        } 
	        else{
		        // For IE:
		        if (event.button == 1) { result = 'leftclick' };
		        if (event.button == 2) { result = 'rightclick' };
		        if (event.button == 4) { result = 'wheelclick' };
	        }
	        return result;
        }
        
        function isValidTrigger(trigger){
            var t= trigger;
            var result=false;
            if(t=="leftclick" || t=="rightclick" || t=="wheelclick"){
                result=true;
            }
            return result;
        }
        
}
function contentInjection() /*CLASS*/ {
    
    /*<public_properties>*/
        this.POSTData = "";
        this.url = null;
    
    
    /*<public_methods>*/
        this.injectInto =inject;

    
    /*<private_properties>*/
        var tempHEADClassname ="nonPersistent";
    

    /*<private_methods>*/
        //var privateMethod = implementedFunctionName;
    

    /*<constructor>*/
       
    

    /*<implementation>*/
        function inject(targetID){
            /*
                This function is complex for a reason! Which is, to achive browser compatability.
                This works in IE5.0, IE5.5, IE6.0 Nescape 7.2+, FireFox1.0+ Opera 9.0+ and Mozzila
                
                Any <script> tags received from the xmlHTTP will be captured and handled separately.
                They will be inserted into the HEAD of the document. 
                <SCRIPT> tags (only ones inserted by this function)will be removed from the HEAD when
                this function is re-triggered.
            */
            
            //The following line will first inject the refresh graphic animation so that while the content is
            //being obtained, the user knows the panel is being refreshed...
            //EGGP97 - Change request for refresh graphic during content panel refreshing...
            //Feizel Daud Kidia.
            document.getElementById(targetID).innerHTML="<img src=\"..\\eGGPWebEngine\\Media\\refreshGraphic.gif\" \/>";
            
            //Fetch content
            var xh =new xHTTP(this.url);
            
            //encode contents as HTTP Form:
            xh.isFormData(true); //Indicates content-type HTTP Form data (application/x-www-form-urlencoded)
            xh.dataString=this.POSTData;
            
            //Send Request by HTTP POST (Asyncronously!):
            xh.doHandledPOST(POSTHandler); 
            
            function POSTHandler(HTTPResponseText){
                var newContent = HTTPResponseText;
                //alert(newContent);
                
                var target =document.getElementById(targetID);
                       	
    	        //clear out all current content
    	        target.innerHTML="";
        	    
                
                //clear out any javascript previously inserted by this method
                var docHead =document.getElementsByTagName("head").item(0);
                for(var i=0; i<docHead.childNodes.length; i++){
					var n=docHead.childNodes[i];
					var tagName  =n.nodeName;
					var tagClass =n.className;
					//alert(tagName + ":" + tagClass);
					if(tagName.toLowerCase()=='script' && tagClass==tempHEADClassname){
						 n.parentNode.removeChild(n);
					}
                }
           	
    	        //insert new content
    	        var javascriptCode =new Array(); //Collection of all javascript <script>'s found in new content. To be added later - after the content has been injected!
    	        var scriptPointsStr ="";
            	
    	        var reponseParsePos=-1;
    	        while(newContent.indexOf("<", reponseParsePos+1)!=-1){
            	  
                    reponseParsePos = newContent.indexOf("<", reponseParsePos+1);
            	    
    	            tagNameLastCharPos=reponseParsePos;
    	            for(var i=reponseParsePos; i<newContent.length-1;i++){
	                    while(newContent.charAt(tagNameLastCharPos)==""){
	                        tagNameLastCharPos++;
	                    }
	                    while(newContent.charAt(tagNameLastCharPos)!=" " && newContent.charAt(tagNameLastCharPos)!="/" && newContent.charAt(tagNameLastCharPos)!=">" && newContent.charAt(tagNameLastCharPos)!=""){
	                        tagNameLastCharPos++;
	                    }
        	        
	                    var tagName =newContent.substring(reponseParsePos+1, tagNameLastCharPos);
	                    var cleanTagName=tagName.replace(/^\s*|\s*$/g,"").toLowerCase(); // remove leading and trailing spaces.
        	            
	                    //!!! Important SOS !!! Unfortunatly with all things web, thing's are not so uniform. IE does not execute any javascript
	                    //                      that has been inserted using the .innerHTML property (Firefox does!). So we have to capture the
	                    //                      <script> elements and insert them in an alternate way to .innerHMTL.
	                    if(cleanTagName=="script") {
        	            
	                        var tagHeadEndPos=tagNameLastCharPos;
	                        while(newContent.charAt(tagHeadEndPos)!=">"){
	                            tagHeadEndPos++;
	                        }
        	            
	                        var tagFootStartPos =newContent.indexOf("</"+tagName+">", tagHeadEndPos+1);
	                        if (tagFootStartPos >-1){
        	                    
	                            //Grab the actual javascript code contained within the <script> tag:
	                            javascriptCode[javascriptCode.length] =newContent.substring(tagHeadEndPos+1, tagFootStartPos);
	                        }
    	                    
	                        scriptPointsStr += (reponseParsePos-1) + "," + (tagFootStartPos+("</"+tagName+">").length);
	                    }
	                    break; // skip search to next potential <script> tag.
    	            }
    	        }
                
      	        var newInnerContent="";
      	        var scriptPoints = scriptPointsStr.split(",");
                if (scriptPoints.length >1){
                    //insert using innerHTML, ommiting the already identified <script> tags:
                    
                    var sStartPos=0;
                    for(var i=0; i<scriptPoints.length-1;i=i+2){
                        newInnerContent +=newContent.substring(sStartPos,scriptPoints[i]);
                        
                        if(scriptPoints[i+1]){
                            sStartPos=scriptPoints[i+1];
                        }
                    }
                    //alert(newContent.substring(sStartPos,newContent.length)); //Each non-<script> element
                    newInnerContent +=newContent.substring(sStartPos,newContent.length);
                }
                else{
                    //No <script> tag identified, insert all of the content using innerHTML:
                    newInnerContent=newContent;
                }
                
                //Now we can actually set the .innerHTML to the new content. This will now hold all content except 
                //any <script> tags, which were handled/inserted separately above.
                
                //alert(newInnerContent);
                target.innerHTML =newInnerContent; //the all important line to inject the content into the target element!!
                
                //Create and insert the captured script elements to the HEAD of the document. This is done
                //after the content has been injected in case any JavaScript references the content!
								//The code contained in each <script> tag is combined and insterted into a single new <script>
								//element in the browser DOM:
                var script = document.createElement('script');
								script.className= tempHEADClassname;
                script.language='javascript';
                script.type = 'text/javascript';
								var combinedCode="/**/";
								for(var i=0; i<javascriptCode.length;i++){
                  combinedCode+=javascriptCode[i];
                }
								script.text =combinedCode ;
								document.getElementsByTagName("head").item(0).appendChild(script); 
            }
        }
}
function tableSorter() /*CLASS*/ {
    /*
    
    */
    
    /*<public_properties>*/
        //this.lastErrorCode =null;

    
    /*<public_methods>*/
        this.makeSortable = makeSortableById;

    
    /*<private_properties>*/
        var SORT_COLUMN_INDEX;
    
    
    /*<private_methods>*/
        //var x = x;
        
    
    /*<constructor>*/
        sortables_init();
    
    /*<implementation>*/
        
        function sortables_init() {
            // Find all tables with class sortable and make them sortable
            if (!document.getElementsByTagName) return;
            tbls = document.getElementsByTagName("table");
            
            for (ti=0;ti<tbls.length;ti++) {
                thisTbl = tbls[ti];
                if (((' '+thisTbl.className+' ').indexOf("sortable") != -1)) {
                    //alert(thisTbl.className);
                    ts_makeSortable(thisTbl);
                }
            }
        }
        
        function makeSortableById(tableID){
            ts_makeSortable(document.getElementById(tableID));
        }

        function ts_makeSortable(table) {
            if (table.rows && table.rows.length > 0) {
                var firstRow = table.rows[0];
            }
            if (!firstRow) return;
            
            // We have a first row: assume it's the header, and make its contents clickable links
            for (var i=0;i<firstRow.cells.length;i++) {
                var cell = firstRow.cells[i];
                var cellTitle="";
                if(cell.title){cellTitle=cell.title;}
                
                var txt = ts_getInnerText(cell);
                cell.innerHTML = '<a href="#" class="sortheader" '+ 
                'onclick="ts_resortTable(this, '+i+');return false;" title="'+cellTitle+'">' + 
                txt+'<span class="sortarrow">&nbsp;&nbsp;&nbsp;</span></a>';
            }
        }
}

//DOM Global Code:
//================

/*<global_properties>*/ 
  


/*<global_methods>*/ 
    function ts_resortTable(lnk,clid) {
        TIMERSTART = (new Date()).getTime();
        // get the span
        var span;
        for (var ci=0;ci<lnk.childNodes.length;ci++) {
            if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
        }
        var spantext = ts_getInnerText(span);
        var td = lnk.parentNode;
        var column = clid || td.cellIndex;
        var table = getParent(td,'TABLE');
        
        // Work out a type for the column
        if (table.rows.length <= 1) return;
        var itm = ts_getInnerText(table.rows[1].cells[column]);
        sortfn = ts_sort_caseinsensitive;
        if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) sortfn = ts_sort_date;
        if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) sortfn = ts_sort_date;
        if (itm.match(/^[£$]/)) sortfn = ts_sort_currency;
        if (itm.match(/^[\d\.]+$/)) sortfn = ts_sort_numeric;
        SORT_COLUMN_INDEX = column;
        var firstRow = new Array();
        var newRows = new Array();
        for (i=0;i<table.rows[0].length;i++) { firstRow[i] = table.rows[0][i]; }
        for (j=1;j<table.rows.length;j++) { newRows[j-1] = table.rows[j]; }

        newRows.sort(sortfn);

        if (span.getAttribute("sortdir") == 'down') {
            ARROW = '&nbsp;&nbsp;&uarr;';
            newRows.reverse();
            span.setAttribute('sortdir','up');
        } else {
            ARROW = '&nbsp;&nbsp;&darr;';
            span.setAttribute('sortdir','down');
        }
        
        // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
        // don't do sortbottom rows
        for (i=0;i<newRows.length;i++) { if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) table.tBodies[0].appendChild(newRows[i]);}
        // do sortbottom rows only
        for (i=0;i<newRows.length;i++) { if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1)) table.tBodies[0].appendChild(newRows[i]);}
        
        // Delete any other arrows there may be showing
        var allspans = document.getElementsByTagName("span");
        for (var ci=0;ci<allspans.length;ci++) {
            if (allspans[ci].className == 'sortarrow') {
                if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
                    allspans[ci].innerHTML = '&nbsp;&nbsp;&nbsp;';
                }
            }
        }
            
        span.innerHTML = ARROW;
        //alert("Time taken: " + ( (new Date()).getTime() - TIMERSTART ) + "ms");
    }

    function getParent(el, pTagName) {
        if (el == null) return null;
        else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())	// Gecko bug, supposed to be uppercase
	        return el;
        else
	        return getParent(el.parentNode, pTagName);
    }
    function ts_sort_date(a,b) {
        // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
        aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
        bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
        if (aa.length == 10) {
            dt1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);
        } else {
            yr = aa.substr(6,2);
            if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
            dt1 = yr+aa.substr(3,2)+aa.substr(0,2);
        }
        if (bb.length == 10) {
            dt2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);
        } else {
            yr = bb.substr(6,2);
            if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
            dt2 = yr+bb.substr(3,2)+bb.substr(0,2);
        }
        if (dt1==dt2) return 0;
        if (dt1<dt2) return -1;
        return 1;
    }

    function ts_sort_currency(a,b) { 
        aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
        bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
        return parseFloat(aa) - parseFloat(bb);
    }

    function ts_sort_numeric(a,b) { 
        aa = parseFloat(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
        if (isNaN(aa)) aa = 0;
        bb = parseFloat(ts_getInnerText(b.cells[SORT_COLUMN_INDEX])); 
        if (isNaN(bb)) bb = 0;
        return aa-bb;
    }

    function ts_sort_caseinsensitive(a,b) {
        aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();
        bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
        if (aa==bb) return 0;
        if (aa<bb) return -1;
        return 1;
    }

    function ts_sort_default(a,b) {
        aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
        bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
        if (aa==bb) return 0;
        if (aa<bb) return -1;
        return 1;
    }
    
    function ts_getInnerText(el) {
	        if (typeof el == "string") return el;
	        if (typeof el == "undefined") { return el };
	        if (el.innerText) return el.innerText;	//Not needed but it is faster
	        var str = "";
        	
	        var cs = el.childNodes;
	        var l = cs.length;
	        for (var i = 0; i < l; i++) {
		        switch (cs[i].nodeType) {
			        case 1: //ELEMENT_NODE
				        str += ts_getInnerText(cs[i]);
				        break;
			        case 3:	//TEXT_NODE
				        str += cs[i].nodeValue;
				        break;
		        }
	        }
	        return str;
        }function jHTTP() /*CLASS*/ {
    /*
    Class notes:
    ============
    This class allows javascript to initiate background HTTP requests to the web server using JSON Dynamic Script Tags. 
    Calls can be made GET ONLY so be aware of the browser-imposed limit of 2083 characters for the entire URL string 
    being submitted (IE being the worst case offender here)!
    Dynamic Script Tags are by nature asynchronous, however a queuing system has been implemented to reliably simulate 
    the synchronous operation of requests.
    
    Some good links for more info on JSON Dynamic Script Tags are:
    
    http://www.xml.com/lpt/a/1636
    http://www.devx.com/webdev/Article/30860/0/page/3
    http://developer.yahoo.com/common/json.html
    
    PLEASE NOTE:    This is a singleton javascript class, do not attempt to instantiate the class more than once per page (DOM).
                    This is due to the global DOM code used to facilitate the synchronous operation mode.
    
    The object works under the following browsers: IE5.0/5.5/6.0, Firefox 1.0, Opera 9, Netscape 7.2, Mozilla 1.7.3
    
    Example usage:
    
        t=new jHTTP();
        t.syncRequests=true;    //makes sure any subsequent "open"'s are sent in the order by which they were created, i.e. synchronously.
                                
        t.open("./myServerSideScript.asp");
        t.open("./myServerSideScript.asp?thisTimePassingParam=true");
        t.open("./myServerSideScript.asp?thisTimePassingParam=true&someMoreParams=ok");
        
        
        //IMPORTANT USAGE NOTES: 
        //======================
        //When using sync mode ensure that the javascript you returned from the server executes 
        //gbl_RegisterRequestComplete(); This will signal the next request in the 
        //queue to commence and remove the appropriate <SCRIPT> tag from the DOM.
        //
        //When using async mode ensure that you server-returned javascript executes 
        //gbl_RegisterRequestComplete(x); where x is a string parameter. x will be supplied to the
        //server-side script as a GET parameter named 'RCParam' as part of URL string. 
        //This will remove the appropriate <SCRIPT> tag from the DOM.
    */
	
	
	/*<public_properties>*/
	    
	    this.syncRequests   =false;
	
	/*<public_methods>*/
	    
	    this.open   =open;
	
	
	/*<private_properties>*/
	    //var something=null;
	    
	    
	/*<private_methods>*/
        //var x = x;

	
	/*<constructor>*/
	    	
	
	/*<implementation>*/

	    function open(url){
	        var ticket =gbl_jHTTPTicketsIssued;
            gbl_jHTTPTicketsIssued++;
	        if(this.syncRequests==true){
	            //sync
                var cmdLine ="if(gbl_jHTTPRequestsComplete=="+ticket+"){gbl_InsertDynScriptTag('"+ticket+"','"+url+"');clearInterval(gbl_jHTTPIntervalHandles['"+ticket+"'].handle);}else{/*do something while we wait for our turn (ticket)*/}";
                gbl_jHTTPIntervalHandles[ticket] ={handle:null, scriptHeadRef:null};
                gbl_jHTTPIntervalHandles[ticket].handle =setInterval(cmdLine, gbl_jHTTPTicketsIssued-gbl_jHTTPRequestsComplete);
	        }
	        else{
	            //async
	            gbl_jHTTPIntervalHandles[ticket]={handle:null, scriptHeadRef:null};
	            gbl_InsertDynScriptTag(ticket, url);
	        }
	    }
}


//Global DOM Code:
//================

/*<global_properties>*/ 

    var gbl_jHTTPIntervalHandles    =new Array();
    var gbl_jHTTPTicketsIssued      =0;
    var gbl_jHTTPRequestsComplete   =0;


/*<global_methods>*/ 

    function gbl_InsertDynScriptTag(ticket, url){
        var headLoc = document.getElementsByTagName("head").item(0);
        
        var rndNum      =(Math.floor(Math.random()*1001)).toString();
        var timeStamp   =((new Date()).getTime()).toString();
        var requestID   =rndNum + "" + timeStamp;
        
        // Create the script object
        var scriptObj = document.createElement("script");
        // Add script object attributes
        scriptObj.setAttribute("type", "text/javascript");
        scriptObj.setAttribute("src", url + (url.indexOf("?")>-1 ? "&": "?") +'cacheDefeat=' + requestID + '&RCParam=' + ticket);
        scriptObj.setAttribute("id", requestID);
        //Store script element reference for later removal.
        gbl_jHTTPIntervalHandles[ticket].scriptHeadRef =scriptObj;
        headLoc.appendChild(scriptObj);
    }
    
    
/*<global_autoexec>*/ 

    function gbl_RegisterRequestComplete(asyncArrayId){
        //remove script from dom head here!
        var headLoc = document.getElementsByTagName("head").item(0);
        
        if(asyncArrayId!="" && asyncArrayId!=null){
            //async removal
            headLoc.removeChild(gbl_jHTTPIntervalHandles[asyncArrayId].scriptHeadRef);
        }
        else{
            //sync removal
            headLoc.removeChild(gbl_jHTTPIntervalHandles[gbl_jHTTPRequestsComplete].scriptHeadRef);
        }
        
        //increment the "request's complete" counter
        gbl_jHTTPRequestsComplete++;
    }function eGGP() /*CLASS*/ {     
    /*<private_properties>*/
        var webEngineVersion    ="#INITALISED_BUT_NOT_SYNCED_YET?";       
                    
        var objUtility            =new utility();
        var objBlockFramework     =new blockFramework();
        var objDragManager        =new dragManager();
        var objBrowserEvents      =new browserEvents();
        var objTableSorter        =new tableSorter();        
        
        /*Include External Queries*/
        /*BLOCK::External Queries*/

    /*<private_properties>*/
        /*var be =new browserEvents();*/
        
    /*<private_methods>*/
        //var privateMethod = implementedFunctionName;

    /*<public_properties>*/
        //this.x = "";

    /*<public_methods>*/
        this.externalQueries ={
            getExistingConnectionNames:getExistingConnectionNames,
            getConnectionColumnData:getConnectionColumnData,
            getDefinedQueries:getDefinedQueries,
            removeQuery:removeQuery,
            defineOuery:defineOuery,
            styler:styler
        };
        
    /*<constructor>*/
    
       
    /*<implementation>*/
        
        function getExistingConnectionNames(){
            var result = new Array();
            
            var params ="action=" + "getExistingConnectionNames";
            
            var responseStr =sendStateTransition(RBStateTransition, params);
            if(responseStr!=""){result =responseStr.split("|");}
            
            return result;
        }
        
        
        function getConnectionColumnData(connectionName){
            var result = new Array();
            
            var params ="action=" + "getConnectionColumnData" + "&connectionName=" + objUtility.URLEncode(connectionName) ;
            try{
                var colArryStr =sendStateTransition(RBStateTransition, params);
                var colArry =colArryStr.split("|");
                if(colArryStr!=""){
                    for(var i=0; i<colArry.length; i++){
                        var splitArry =colArry[i].split(",");
                        result[result.length]={name:splitArry[0],type:splitArry[2],length:splitArry[3],decimalPlaces:splitArry[4]};
                    }
                }
            }
            catch(err){/**/}
            
            return result;
        }
        
        
        function getDefinedQueries(){
            var result = new Array();
            
            var params ="action=" + "getDefinedQueries";
            try{
                var qryArryStr=sendStateTransition(RBStateTransition, params);
                if(qryArryStr!=""){
                    var qryArry =qryArryStr.split("|");
                                
                    for(var i=0; i<qryArry.length; i++){
                        var splitArry =qryArry[i].split("@^@");
                        result[result.length]={name:splitArry[0],description:splitArry[1],connectionName:splitArry[2],query:splitArry[3]};
                    }
                }
            }
            catch(err){/**/}
            
            return result;
        }
        
        
        function removeQuery(queryName){
            var params ="action=" + "removeQuery" + "&queryName=" + objUtility.URLEncode(queryName);
            return sendStateTransition(RBStateTransition, params);
        }
        
        
        function defineOuery(queryName, queryDescription, connectionName, query, XMLStyleDef){
            var params ="action=" + "defineOuery";
            params  +="&queryName=" + objUtility.URLEncode(queryName);
            params  +="&queryDescription=" + objUtility.URLEncode(queryDescription);
            params  +="&connectionName=" + objUtility.URLEncode(connectionName);
            params  +="&query=" + objUtility.URLEncode(query);
            params  +="&XMLStyleDef=" + objUtility.URLEncode(XMLStyleDef);
            
            return sendStateTransition(RBStateTransition, params);
        }
        
        function styler(){
            this.polyStrokeColour        ="#000000";
	        this.polyStrokeStyle        ="Solid";
	        this.polyStrokeSizeValue    ="1";
	        this.polyStrokeSizeUnits    ="Point";
	        this.polyFillColour         ="#000000";
	        this.polyFillStyle          ="Solid";
	        this.polyFillPattern        ="1";

	        this.symbolSizeValue        ="1";
	        this.symbolSizeUnits        ="Point";
	        this.symbolType             ="1";
	        this.symbolIsFilled         =true;
	        this.symbolFillColour       ="#000000";
	        this.symbolStrokeColour     ="#000000";
	        
	        var cssToRGB                =cssToRGB;
	        
	        this.MenuPolyStrokeStyles   =MenuPolyStrokeStyles;
	        this.MenuFillStyles         =MenuFillStyles;
	        this.MenuUnits              =MenuUnits;
	        this.MenuPolyFillPatterns   =MenuPolyFillPatterns;
	        this.MenuSymbolTypes        =MenuSymbolTypes;
	        this.getStyleXML            =getStyleXML;
	        
	        function MenuPolyStrokeStyles(){
	            return new Array('Hollow', 'Solid', 'LongDash', 'Dotted', 'DashDot', 'MediumDash', 'Dash2Dot', 'ShortDash');
	        }
	        function MenuFillStyles(){
	            return new Array('Unfilled', 'Solid', 'Pattern', 'Hatch', 'Hollow');
	        }
	        function MenuUnits(){
	            return new Array('Metre', 'Point', 'Pixel');
	        }
	        function MenuPolyFillPatterns(){
	            return new Array(1,2,3,4,5,6);
	        }
	        function MenuSymbolTypes(){
	            return new Array(1,2,3,4,5,6);
	        }
	        
	        
	        function cssToRGB(cssColour){
	            //The first character of cssColour is assumed to be '#'
	            var HexR =cssColour.substr(1,2);
	            var HexG =cssColour.substr(3,2);
	            var HexB =cssColour.substr(5,2);
	            
	            //Convert Hex to Decimal (Base 10):
	            var R=parseInt(HexR,16);
	            var G=parseInt(HexG,16);
	            var B=parseInt(HexB,16);
	            
	            return {R:R, G:G, B:B};
	        }
	        
	        function getStyleXML(){
	            var x="";
	            var RGB="";
	            x+="<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
                x+="<StylingXML>\n";
                x+="    <polyAttributes>\n";
	            x+="        <strokeColour>\n";
		                        RGB =cssToRGB(this.polyStrokeColour);
		        x+="            <red>"+RGB.R+"</red>\n"; 
		        x+="            <green>"+RGB.G+"</green>\n";
		        x+="            <blue>"+RGB.B+"</blue>\n";
	            x+="        </strokeColour>\n";
	            x+="        <strokeStyle>"+this.polyStrokeStyle+"</strokeStyle>\n";
	            x+="        <strokeSize>\n";
		        x+="            <value>"+this.polyStrokeSizeValue+"</value>\n";
		        x+="            <units>"+this.polyStrokeSizeUnits+"</units>\n";
	            x+="        </strokeSize>\n";
	            x+="        <fillColour>\n";
	                            RGB =cssToRGB(this.polyFillColour);
		        x+="            <red>"+RGB.R+"</red>\n"; 
		        x+="            <green>"+RGB.G+"</green>\n";
		        x+="            <blue>"+RGB.B+"</blue>\n";
	            x+="        </fillColour>\n";
	            x+="        <fillStyle>"+this.polyFillStyle+"</fillStyle>\n";
	            x+="        <fillPattern>"+this.polyFillPattern+"</fillPattern>\n";
                x+="    </polyAttributes>\n";
                x+="    <symbolAttributes>\n";
	            x+="        <size>\n";
		        x+="            <value>"+this.symbolSizeValue+"</value>\n";
		        x+="            <units>"+this.symbolSizeUnits+"</units>\n";
	            x+="        </size>\n";
	            x+="        <symType>"+this.symbolType+"</symType>\n";
	            x+="        <isFilled>"+this.symbolIsFilled.toString()+"</isFilled>\n";
	            x+="        <fillColour>\n";
	                            RGB =cssToRGB(this.symbolFillColour);
		        x+="            <red>"+RGB.R+"</red>\n"; 
		        x+="            <green>"+RGB.G+"</green>\n";
		        x+="            <blue>"+RGB.B+"</blue>\n";
	            x+="        </fillColour>\n";
	            x+="        <strokeColour>\n";
		                        RGB =cssToRGB(this.symbolStrokeColour);
		        x+="            <red>"+RGB.R+"</red>\n"; 
		        x+="            <green>"+RGB.G+"</green>\n";
		        x+="            <blue>"+RGB.B+"</blue>\n";
	            x+="        </strokeColour>\n";
                x+="  </symbolAttributes>\n";
                x+="</StylingXML>\n";
                
                return x;
            }
        }
        
/*END BLOCK::External Queries*/

               
        var persistentViewPort    =false;
        var browserHasResized     =false;
        var DCHR =','; //A character string used for delimiting on both client and server. Session-wide, defined in Global.asa
        
        var reachAddress                =extractURLHostPart(gbl_BrowserURL);
        var proxyLocation               ="";
        
        var clientExceptionHandler      =null;
        

        /*Define Root-based (RB) locations:*/
        var RBInitalMapImage    ="/Media/initial.gif";
        var RBImageStreamPath   ="/MapEngine/mapImageStream.aspx";
        var RBSwapRedundant     ="/Media/initial.gif";
        var RBDefaultTrackPoint ="/Media/trackPoint.gif"
        var RBContentPanel      ="/MapEngine/panelContentMaker.aspx";
        var RBStateTransition   ="/MapEngine/stateTransition.aspx";
        /**/
        
        /*Map Pointer Managment (Map Mousing)*/
        var pointerAction           ="idle";  
        var pointerActionLC         ="idle"; //Mouse left-click action,  can be either of: idle|panmap|zoominarea|recentre|query|zoomincp|zoomoutcp
        var pointerActionRC         ="idle"; //Mouse right-click action, can be either of: idle|panmap|zoominarea|recentre|query|zoomincp|zoomoutcp
        var pointerActionWC         ="idle"; //Mouse wheel-click action, can be either of: idle|panmap|zoominarea|recentre|query|zoomincp|zoomoutcp
        var pointerSubAction        ="";
        var pointerTrigger          ="leftclick";   //can be either of leftclick|rightclick|wheelclick
        var cursorLastActionSet     ="";
        var pointerPreHandler       =null;
        var pointerMoveHandler      =null;
        var pointerPostHandler      =null;
        var toolPostHandler         =null; //Will hold the client function to make use of the persistent nav tool settings. EGGP131 - Feizel Daud Kidia - 01.07.2008.
        var pointerOutHandler       =null;
        var postRefreshMapHandler   =null;
        var wheelUpHandler          =null;
        var wheelDownHandler        =null;
        var currentZoomFactor       =2;
        var MR2BrowserX             ='';    //(M)ouse pointer (R)elative to Browser Main Window X/Y, in pixels. 
        var MR2BrowserY             ='';    
        var MR2MapX                 ='';    //(M)ouse pointer (R)elative to Map image X/Y, in pixels.
        var MR2MapY                 ='';
        var MR2MapNGX               ='';    //(M)ouse pointer (R)elative to Map image X/Y, in Eastings & Northings.
        var MR2MapNGY               ='';
        var ZoomBoxId               ='eGGPZoomBox';
        var ZoomBoxR2MapTop         ='';
        var ZoomBoxR2MapLeft        ='';
        var ZoomBoxAssignedTop      ='';
        var ZoomBoxAssignedLeft     ='';
        var NGStartX                ='';
        var NGStartY                ='';
        var NGEndX                  ='';
        var NGEndY                  ='';
        var ImgPosStartX            ='';
        var ImgPosStartY            ='';
        var ImgStartStyleTop        ='';    //Used to reposition the map image to it original location after it has been dragged!
        var ImgStartStyleLeft       ='';    //Used to reposition the map image to it original location after it has been dragged!
        var ImgPosEndOffSetX        ='';    //When user releases mouse after a map drag this will hold the number of pixels the map image has moved in the X-direction.
        var ImgPosEndOffSetY        ='';    //When user releases mouse after a map drag this will hold the number of pixels the map image has moved in the Y-direction.
        var DefaultMouseCursor      ='default';
        var trackCPID               =null;
        /**/
        
        /*eGGP Javascript Internal Map State (ms_ prefix!!)*/
        //These are ONLY EVER updated via a stateTransition response from server!!! (i.e. the eGGP object's sendStateTransition() method)
        var ms_WebRoot      ="#INITALISED_BUT_NOT_SYNCED_YET?";
        var ms_Department   ="#INITALISED_BUT_NOT_SYNCED_YET?";
        var ms_Script       ="#INITALISED_BUT_NOT_SYNCED_YET?";
        
        var ms_ImageFormat  ="#INITALISED_BUT_NOT_SYNCED_YET?";
        var ms_LicenceText  ="#INITALISED_BUT_NOT_SYNCED_YET?";
        
        //current URL ? Mode:
        var ms_Mode         ="#INITALISED_BUT_NOT_SYNCED_YET?";
        
        var ms_Scale        =-1;    //This is the current scale of the map.
        var ms_VPMinX       =-1;    //Viewport min easting
        var ms_VPMaxX       =-1;    //Viewport max easting
        var ms_VPMinY       =-1;    //Viewport min northing
        var ms_VPMaxY       =-1;    //Viewport max northing
        var ms_CentreX      =-1;    //Viewport centre easting
        var ms_CentreY      =-1;    //Viewport centre northing
        var ms_WebRoots     =new Array(); //An array holding a list of all available WebRoots (access via the [].name property).
        var ms_Departments  =new Array(); //An array holding a list of all available Departments (access via the [].name property).
        var ms_Scripts      =new Array(); //An array holding a list of all available Scripts (access via the [].name property).
        var ms_Layers       =new Array(); //An array of LayerRecord's representing the layers in the script.
                                            //Each LayerRecord:{name:string("Address Point"), active:boolean(true/false)(turned-on), visible:boolean(true/false)(In Threshold) ,queries:Array of QueryRecord}         
                                                //Each QueryRecord:{name:string("Even Numbered Houses"), active:boolean(true/false)(turned-on)}
        /**/

        
        /*Content Panels*/
        var CPInjector      =new contentInjection();
        var contentPanels   =new Array();
            // Each array element is a RECORD:
            //  .targetId           [string] is the id of the element that the rendered content is to be injected into.
            //  .transformationURL  [string] is the URL that will yeild the xslt code.
            //  .customXMLURL       [string] is the URL that will be called to inject any additional (custom) XML into the eGGP source XML prior to the XSLT trasformation taking place. The custom XML will appear as a child node under the <eGGP>\<CustomXML>\<FromServer> element in the source document.
            //  .refreshLevel       [string] one of: {department->script->layer->displayquery->[mapviewport|query]->gazsearch} to indicate the events that should trigger a refresh of the panel content.
        /**/
        
        
        var mapImg                  ={id:null, isDragable:false, w:null, h:null, holderObj:null}; //Map image object.
        var WebEngineRoot_URLPrefix =gbl_eGGPWebEngineRoot; //Import the WebEngine URL location, derived globally from DOMGlobal.js.
    
    
        /*Map Track Points*/
        var mapTrackPoints =new Array(); //An array of trackPoint objects representing a visual map track point.
                                            //Each trackPoint:{id:string, easting:string, northing:string}
        /**/
        
    /*<private_methods>*/
        //var x = x;
   
    /*<public_properties>*/
        //this.y = y;

    /*<public_methods>*/
        this.webEngineVersion   =function(){return webEngineVersion;};
        
        this.utility            =function(){return objUtility;};
        this.blockFramework     =function(){return objBlockFramework;};
        this.dragManager        =function(){return objDragManager;};
        this.browserEvents      =function(){return objBrowserEvents;};
        
        this.registerMapHolder          =setMapImgId;
        this.refreshMap                 =eGGPRefreshMap;
        this.setPersistentViewPort      =setPersistentViewPort;
        
        //this.setReachAddress          =setReachAddress;   //Use deferred!
        //this.setProxyLocation         =setProxyLocation;  //Use deferred!
        
        /*TrackPoints*/
        this.registerTrackPoint         =registerTrackPoint;
        this.unregisterTrackPoint       =unregisterTrackPoint;
        this.getTrackCPId               =function(){return trackCPID;};
        this.jumpToTrackPoint           =jumpToTrackPoint;
        this.refreshTrackPoints         =paintTrackPoints;
        
        //get the internal object state for debugging:
        this.getState                   =getState;
        
        /*MapEngine state changers(SET)*/
        this.setWebRoot             =setWebRoot;
        this.setDepartment          =setDepartment;
        this.setScript              =setScript;
        this.setScale               =setScale;
        this.zoomIn                 =zoomIn;
        this.zoomOut                =zoomOut;
        this.zoomInCP               =zoomInCP;
        this.zoomOutCP              =zoomOutCP;
        this.zoomPrevious           =zoomPrevious;
        this.setMapRef              =setMapRef;
        this.setNextScript          =setNextScript;
        this.setPrevScript          =setPrevScript;
        this.zoomAllExtents         =zoomAllExtents;
        this.pan                    =pan;
        this.zoomOriginalView       =zoomOriginalView;
        this.recentre               =recentre;
        this.queryMapXY             =queryMapXY;
        this.setViewport            =setViewport;
        this.setLayersActive        =setLayersActive;
        this.setLayersInactive      =setLayersInactive;
        this.setAllLayersActive     =setAllLayersActive;
        this.setAllLayersInactive   =setAllLayersInactive;
        this.setDQsActive           =setDQsActive;
        this.setDQsInactive         =setDQsInactive;
        this.setAllDQsActive        =setAllDQsActive;
        this.setAllDQsInactive      =setAllDQsInactive;
        this.sendMail               =sendMail;
        this.addPoint               =addPoint;
        this.getConfigFragment      =getConfigFragment;
        /**/

        /*Mapstate readers (GET)*/
            // exposing internal properties publically is best done using function calls to fetch the required
            // internal values! i.e. avoid trying to assign a public property directly to a internal property
            // as this will only assign a COPY of the internal variable - not a REFERENCE to it!!!.
        this.webRoot            =function(){return ms_WebRoot;};
        this.department         =function(){return ms_Department;};        
        this.script             =function(){return ms_Script;};
        this.scale              =function(){return ms_Scale;};
        this.minX               =function(){return ms_VPMinX;};
        this.maxX               =function(){return ms_VPMaxX;};
        this.minY               =function(){return ms_VPMinY;};
        this.maxY               =function(){return ms_VPMaxY;};
        this.centreX            =function(){return ms_CentreX;};
        this.centreY            =function(){return ms_CentreY;};
        this.layers             =function(){return ms_Layers;};
        this.departments        =function(){return ms_Departments;};
        this.scripts            =function(){return ms_Scripts;};
        this.webRoots           =function(){return ms_WebRoots;};
        this.imageFormat        =function(){return ms_ImageFormat;};
        this.licenceText        =function(){return ms_LicenceText;};
        this.mode               =function(){return ms_Mode;};
        this.pointerWindowX     =function(){return MR2BrowserX;};
        this.pointerWindowY     =function(){return MR2BrowserY;};
        this.pointerMapX        =function(){return MR2MapX;};
        this.pointerMapY        =function(){return MR2MapY;};
        this.pointerX           =function(){return MR2MapNGX;};
        this.pointerY           =function(){return MR2MapNGY;};
        this.lastPointerClick   =function(){return {X:NGEndX, Y:NGEndY};};
                
        this.persistentViewPort =function(){return persistentViewPort};
        /**/
        
        /*ContentPanels*/
        this.registerContentPanel       =registerContentPanel;
        //this.unregisterContentPanel     =null; //requires implementation
        this.refreshContentPanels       =refreshContentPanels;
        this.refreshContentPanelById    =refreshContentPanelById;
        /**/
        
        /*Map Pointers*/
        this.setPointerAction           =setPointerAction;
        this.getPointerAction           =getPointerAction;
        this.setPointerPreHandler       =setPointerPreHandler;  //client function that runs before map action
        this.setPointerMoveHandler      =setPointerMoveHandler; //client function that runs during map action
        this.setPointerPostHandler      =setPointerPostHandler; //client function that runs after map action
        this.setToolPostHandler         =setToolPostHandler;    //client function that runs after a map action (EGGP131 - implement persistent tool navigation option. Feizel Daud Kidia - 01.07.2008)
        this.setPointerOutHandler       =setPointerOutHandler;  //client function that runs when mouse move off map image
        this.setWheelUpHandler          =setWheelUpHandler;
        this.setWheelDownHandler        =setWheelDownHandler;
        /**/
        
        /*POST RefreshMap Handler*/
        //custom client JS function to run after a refresh map has been executed.
        this.setPostRefreshMapHandler   =setPostRefreshMapHandler;
        /**/
        
        /*Client Error Handler*/
        this.setExceptionHandler        =setExceptionHandler;
        /**/
        
        /*Search Items*/
        this.submitGazForm              =submitGazForm;
        this.submitGazByKVArray         =submitGazByKVArray;
        this.submitLayerSearchForm      =submitLayerSearchForm;
        this.submitLayerSearchByKVArray =submitLayerSearchByKVArray;
        this.makeTableSortable          =makeTableSortable;
        /**/
        
        /*Find My Nearest*/
        this.findNearest                =findNearest;
        /**/
        
        /*Add Point*/
        this.submitAddPointViaForm      =submitAddPointViaForm;
        /**/
        
        /*CallBack*/
        this.doCallBack                 =doCallBack;
        /**/
        
        
    /*<constructor>*/
        //Constructor code goes here:
        
        //Inititally sync the internal engine mapstate with that of the eGGPWebEngine (at server).
        //by calling applyBrowserURL();
        //applyBrowserURL(); is also required in instances where the user hits F5 for browser refresh 
        //and javascript object requires re-population of state.
        applyBrowserURL();
        
    
    /*<implementation>*/
        
        function registerMapWheelEvents(){
            // This will register the map wheel event listenter to the appropriate DOM event.
            if (window.addEventListener){
                //DOMMouseScroll is for Mozilla
                mapImg.holderObj.addEventListener('DOMMouseScroll', handleMapWheelEvent, false);
            }
            else{
                //IE/Opera
                mapImg.holderObj.onmousewheel = handleMapWheelEvent;
            }
        }
        function handleMapWheelEvent(event){
            //Event handler for mouse wheel event.
            var delta = 0;
            if (!event) /* For IE. */ event = window.event;
            if (event.wheelDelta) { /* IE/Opera. */
                delta = event.wheelDelta/120;
                
                /** In Opera 9, delta differs in sign as compared to IE.*/
                if(window.opera)
                    delta = -delta;
            } 
            else if (event.detail) { /** Mozilla case. */
                /** In Mozilla, sign of delta is different than in IE.
                * Also, delta is multiple of 3.
                */
                delta = -event.detail/3;
            }
            
            /** If delta is nonzero, handle it.
             * Basically, delta is now positive if wheel was scrolled up,
             * and negative, if wheel was scrolled down.
             */
            if (delta) handle(delta);
            /** Prevent default actions caused by mouse wheel.
             * That might be ugly, but we handle scrolls somehow
             * anyway, so don't bother here..
             */
            if (event.preventDefault) event.preventDefault();
            event.returnValue = false;

	        //Aggregate handler:
	        function handle(delta) {
                if (delta < 0){
	                //Run custom client wheel-down function, if registered.
                    if(wheelDownHandler!=null){wheelDownHandler();}
	            }
                else{
  	                //Run custom client wheel-up function, if registered.
                    if(wheelUpHandler!=null){wheelUpHandler();}
  	            }
            }
        }
    
    
        function jumpToTrackPoint(trackPointId){
            //This function finds the specified Track Point and centres the map to it.
            for(var i=0; i<mapTrackPoints.length; i++){
                if(mapTrackPoints[i].id==trackPointId){
                    recentre(mapTrackPoints[i].easting,mapTrackPoints[i].northing);
                    break;
                }
            }
        }
    
        function processTrackPointURL(){
            var tURL =gbl_BrowserURL.toString();
            if(tURL.indexOf('?')>-1 && ms_Script!=''){
                var arr_params=tURL.split('?')[1].split('&');
                var trackpoint_requested=false;
                var caption="";
                var glyphURL="";
                for(var i=0; i<arr_params.length; i++){
                    var key =objUtility.URLDecode(arr_params[i].split('=')[0]);
                    var val =objUtility.URLDecode(arr_params[i].split('=')[1]);
                    
                    if(key.toLowerCase()=="trackcp" && val.toString()=="1"){
                        trackpoint_requested=true;
                    }
                    if(key.toLowerCase()=="trackcaption"){
                        caption=val.toString();
                    }
                    if(key.toLowerCase()=="trackglyph"){
                        glyphURL=val.toString();
                    }
                }
           
                //Apply the trackpoint, if detected.
                if(trackpoint_requested==true){
                    var tpx;
                    var tpy;
                    
                    //Get the track point co-ordinates from the current map centre:
                    tpx =ms_CentreX;
                    tpy =ms_CentreY;
                    
                    //Store the track point CP img Id so that it may be retrieved via client API request:
                    trackCPID =registerTrackPoint(tpx, tpy, caption, glyphURL);
                }             
            }
        }

        function doCallBack(reportX, reportY){
            //EGGP159 Change Request - Allow functionality for eGGP to handle callback mode using JS control for other applications to utilise the interface. Feizel Daud Kidia. 08.09.2008.
            if (ms_Mode == 'callback') { //If using the normal URL based callback mode.
                var params ="action=" + "constructCBReceiverURL" + "&cpx="+objUtility.URLEncode(reportX) + "&cpy="+objUtility.URLEncode(reportY);
                var CBUrl = sendStateTransition(RBStateTransition, params);
                location.href=CBUrl;
            }
            else if (ms_Mode == 'callbackjs') { //If using the JS based callback mode to return data to a calling (parent) application in a specified format.
                try {
                    opener.callbackJS(reportX, reportY, tag);
                    opener.focus();
                }
                catch (e) {
                    alert("There was a problem sending the information back to the calling application.\nPlease ensure the parent application is open");
                }
            }
        }
        
       
        function getPointerAction(targetMouseButton){
            var result="?unknown?";
            if(targetMouseButton){ //targetMouseButton is an optional parameter!
                
                if(targetMouseButton.toLowerCase()=="leftclick"){result=pointerActionLC;}
                if(targetMouseButton.toLowerCase()=="rightclick"){result=pointerActionRC;}
                if(targetMouseButton.toLowerCase()=="wheelclick"){result=pointerActionWC;}
                
                switch(targetMouseButton.toLowerCase()) {
                    case 'leftclick':
                        result = pointerActionLC;
                        break;
                    case 'rightclick':
                        result = pointerActionRC;
                        break;
                    case 'wheelclick':
                        result = pointerActionWC;
                        break;
                }
                
            }
            else{
                //Assume left mouse button, when target not specified.
                result =pointerActionLC;
            }
            return result;
        }
        
        function makeTableSortable(tableId){
            objTableSorter.makeSortable(tableId);
        }
        
        function setPointerAction(action, targetMouseButton){
            //Import target mouse button
            var targetpointerAction="";
            if(targetMouseButton){ //targetMouseButton is an optional parameter!
                
                if(targetMouseButton.toLowerCase()=="leftclick"){targetpointerAction=pointerActionLC;pointerTrigger="leftclick";}
                if(targetMouseButton.toLowerCase()=="rightclick"){targetpointerAction=pointerActionRC;pointerTrigger="rightclick";}
                if(targetMouseButton.toLowerCase()=="wheelclick"){targetpointerAction=pointerActionWC;pointerTrigger="wheelclick";}
                
                switch(targetMouseButton.toLowerCase()) {
                    case 'leftclick':
                        targetpointerAction=pointerActionLC;
                        pointerTrigger="leftclick";
                        break;
                    case 'rightclick':
                        targetpointerAction=pointerActionRC;
                        pointerTrigger="rightclick";
                        break;
                    case 'wheelclick':
                        targetpointerAction=pointerActionWC;
                        pointerTrigger="wheelclick";
                        break;
                }
            }
            else{
                //Assume left mouse button, when target not specified.
                targetpointerAction =pointerActionLC;
                pointerTrigger ="leftclick";
            }

            var prevPointerAction=targetpointerAction;
            var a =action.toString().toLowerCase();
            if(a=="idle" || a=="panmap" || a=="zoominarea" || a=="zoomtoarea" || a=="recentre" || a=="query" || a=="zoomincp" || a=="zoomoutcp"){
                targetpointerAction =a;
                if (mapImg.id!=null){
                    if (mapImg.isDragable){
                        if(targetpointerAction=="panmap"){
                            //The map image should be registered as a dragItem to perform dragging.
                            if(objDragManager.isRegisteredDragItem(mapImg.id)==false){
                                objDragManager.registerDragItem(mapImg.id, null, null, null, true);
                                applyTrackPointDragAlongs();
                            }
                            //sync the dragitems's trigger to eGGP's:
                            objDragManager.setItemDragTrigger(mapImg.id, pointerTrigger);
                            //alert("DRAG MOTION ON");
                        }
                    }
                    else{
                        //prevent setting mode to 'panmap' if mapImg is not set as draggable.
                        if(targetpointerAction=="panmap"){targetpointerAction=prevPointerAction;}
                    }
                }
            }
            

            //update/assign changes to target mouse button:
            if(targetMouseButton){ //targetMouseButton is an optional parameter!
                
                if(targetMouseButton.toLowerCase()=="leftclick"){pointerActionLC=targetpointerAction;}
                if(targetMouseButton.toLowerCase()=="rightclick"){pointerActionRC=targetpointerAction;}
                if(targetMouseButton.toLowerCase()=="wheelclick"){pointerActionWC=targetpointerAction;}
                
                switch(targetMouseButton.toLowerCase()) {
                    case 'leftclick':
                        pointerActionLC=targetpointerAction;
                        break;
                    case 'rightclick':
                        pointerActionRC=targetpointerAction;
                        break;
                    case 'wheelclick':
                        pointerActionWC=targetpointerAction;
                        break;
                }
                
            }
            else{
                //Assume left mouse button, when target not specified.
                pointerActionLC =targetpointerAction;
            }
            
            //unregister map drag item, if none of the mouse buttons are set to 'panmap':
            if (mapImg.id!=null){
                if (mapImg.isDragable){
                    if((pointerActionLC+pointerActionRC+pointerActionWC).toString().indexOf("panmap")<0){
                        if(objDragManager.isRegisteredDragItem(mapImg.id)==true){
                            objDragManager.unregisterDragItem(mapImg.id);
                        }
                    }
                }
            }
        }
        
        function setPointerPreHandler(f){
            pointerPreHandler=f;
            if(f==""){pointerPreHandler=null;}
        }
        function setPointerMoveHandler(f){
            pointerMoveHandler=f;
            if(f==""){pointerMoveHandler=null;}
        }
        function setPointerPostHandler(f){
            pointerPostHandler=f;
            if(f==""){pointerPostHandler=null;}
        }
        
        //EGGP131 - Implement persistent navigation tools feature.
        //The function below will allow client to register a post pointer action event specially for handling based on the PersistentNavTool setting in the INI.
        //Feizel Daud Kidia - 01.07.2008.
        function setToolPostHandler(f){
            toolPostHandler=f;
            if(f==""){toolPostHandler=null;}
        }
        
        function setPointerOutHandler(f){
            pointerOutHandler=f;
            if(f==""){pointerOutHandler=null;}
        }
        function setExceptionHandler(f){
            clientExceptionHandler=f;
            if(f==""){clientExceptionHandler=null;}
        }
        function setWheelUpHandler(f){
            wheelUpHandler=f;
            if(f==""){wheelUpHandler=null;}
        }
        function setWheelDownHandler(f){
            wheelDownHandler=f;
            if(f==""){wheelDownHandler=null;}
        }
        
        function setPostRefreshMapHandler(f){
            postRefreshMapHandler=f;
            if(f==""){postRefreshMapHandler=null;}
        }
        
        function eGGPProcessPointer(evt){
            //Entry point for dealing with map mouse clicks.
            var result =false;
            var evt =(evt || window.event);
            
            //Set the pointerAction to be performed for the target mouse button:
            if(evt.type=="mousedown"){
                
                if(objUtility.mouseButtonUsed(evt).toLowerCase()=="leftclick"){pointerAction=pointerActionLC;pointerTrigger="leftclick";}
                if(objUtility.mouseButtonUsed(evt).toLowerCase()=="rightclick"){pointerAction=pointerActionRC;pointerTrigger="rightclick";}
                if(objUtility.mouseButtonUsed(evt).toLowerCase()=="wheelclick"){pointerAction=pointerActionWC;pointerTrigger="wheelclick";}
                
                switch(objUtility.mouseButtonUsed(evt).toLowerCase()) {
                    case 'leftclick':
                        pointerAction=pointerActionLC;
                        pointerTrigger="leftclick";
                        break;
                    case 'rightclick':
                        pointerAction=pointerActionRC;
                        pointerTrigger="rightclick";
                        break;
                    case 'wheelclick':
                        pointerAction=pointerActionWC;
                        pointerTrigger="wheelclick";
                        break;
                }
                
            }
            
            /*MOUSE_DOWN Processing*/
            if(evt.type=="mousedown"){
        	    refreshMR2Globals(evt);
                if (objUtility.mouseButtonUsed(evt)==pointerTrigger){ //only process event if triggered by the correct mouse button
                    
                    if (pointerAction=='idle'){
                        captureStartIndicators();
                    }
                    if (pointerAction=='panmap'){
		                captureStartIndicators();
		                if(pointerSubAction!='panmap-ing'){//prevents side-effects of FF bug.
	                        // mark top and left. so that image can repositioned after new map has been fetched.
	                        ImgStartStyleTop    =parseInt(document.getElementById(mapImg.id).style.top+0, 10);
	                        ImgStartStyleLeft   =parseInt(document.getElementById(mapImg.id).style.left+0, 10);
		                }
		                pointerSubAction='panmap-ing';
	                }
                    if (pointerAction=='zoominarea'){
                        if (ms_Scale > 1) {
                            captureStartIndicators();
                            pointerSubAction='zoomInArea-ing';
                        }
                        else alert("You can not zoom any further");
                    }
                    if (pointerAction=='zoomtoarea'){
                        if (ms_Scale > 1) {
                            captureStartIndicators();
                            pointerSubAction='zoomToArea-ing';
                        }
                        else alert("You can not zoom any further");
                    }
                    if (pointerAction=='query'){
                        captureStartIndicators();
                    }
                }
            }
            
            
            /*MOUSE_MOVE Processing*/
            else if(evt.type=="mousemove"){
                refreshMR2Globals(evt);
                setMapCursor();
            	
                if (pointerAction=='idle'){
                    //nothing to do when idle.
                }
                if (pointerAction=='panmap'){
	                
	                if(pointerSubAction=='panmap-ing'){//Pointer is fixed to point on the map (as part of dragging process), therefore eastings and northings don't change.
	                    MR2MapNGX=NGStartX;
	                    MR2MapNGY=NGStartY;
	                }
                }
                if (pointerAction=='zoominarea'){
                    if (pointerSubAction=='zoomInArea-ing'){
                        if (objUtility.mouseButtonUsed(evt)==pointerTrigger){ //only process event if triggered by the correct mouse button
                            establishZoomBox();
                            dragZoomBox();
                        }
                    }
                }
                if (pointerAction=='zoomtoarea'){
                    if (pointerSubAction=='zoomToArea-ing'){
                        if (objUtility.mouseButtonUsed(evt)==pointerTrigger){ //only process event if triggered by the correct mouse button
                            establishZoomBox();
                            dragZoomBox();
                        }
                    }
                }
                if (pointerAction=='recentre'){
                }
                if (pointerAction=='zoomincp'){
                }
                if (pointerAction=='zoomoutcp'){
                }
                if (pointerAction=='query'){
                }
                
                //Run custom client move-action function, if registered.
                if(pointerMoveHandler!=null){
                    pointerMoveHandler(evt)
                }
            }
            
            
            /*MOUSE_UP Processing*/
            else if(evt.type=="mouseup"){
                refreshMR2Globals(evt);
                
                if (objUtility.mouseButtonUsed(evt)==pointerTrigger){ //only process event if triggered by the correct mouse button
                    //Run custom client pre-action function, if registered.
                    if(pointerPreHandler!=null){pointerPreHandler(evt, pointerTrigger)}
                    
                    if (pointerAction=='idle'){
                        captureEndIndicators();
                    }
                    
                    if (pointerAction=='zoominarea'){
		                if(pointerSubAction=='zoomInArea-ing'){
			                captureEndIndicators();
			                destroyZoomBox();
			                pointerSubAction='';
    			            
			                //Only perform zoom area if mouse has moved to a different location from its initial mousedown position.
			                if(NGStartX+NGStartY != NGEndX+NGEndY){
			                    setViewport(NGStartX, NGStartY, NGEndX, NGEndY);
			                    eGGPRefreshMap();
			                }
			                else{
			                    //user has clicked and released the mouse at the same point. A zoomInCP will performed:
		                        zoomInCP(NGEndX, NGEndY);
		                        eGGPRefreshMap();
			                }
		                }
	                }
	                if (pointerAction=='zoomtoarea'){
		                if(pointerSubAction=='zoomToArea-ing'){
			                captureEndIndicators();
			                destroyZoomBox();
			                pointerSubAction='';
    			            
			                //Only perform zoom area if mouse has moved to a different location from its initial mousedown position.
			                if(NGStartX+NGStartY != NGEndX+NGEndY){
			                    setViewport(NGStartX, NGStartY, NGEndX, NGEndY);
			                    eGGPRefreshMap();
			                }
		                }
	                }
	                if (pointerAction=='panmap'){
		                captureEndIndicators();
		                var nextCPx;
		                var nextCPy;
	                
		                function prepadCoordinate(coordsIn) {
		                    // Calculate the difference in length to that of a 9 digit long y coordinate
							var coordLenDiff = 9 - coordsIn.toString().length;
							var padding = '';
							for (var i=0;i<coordLenDiff;i++) {
							  padding += '0';
							}
							coordsIn = padding + coordsIn;
							return coordsIn;
		                }
		                
		                //Get new x coord.
					    if (ms_CentreX.toString().length < 9) {
					        nextCPx =(ms_CentreX-(ImgPosEndOffSetX-ImgPosStartX)* parseInt((ms_VPMaxX-ms_VPMinX)/mapImg.w));
					        nextCPx = prepadCoordinate(nextCPx);
					    }
					    else {
					      nextCPx =(ms_CentreX-(ImgPosEndOffSetX-ImgPosStartX)*((ms_VPMaxX-ms_VPMinX)/mapImg.w));
					    }
		                
		                //Get new y coords.
						if (ms_CentreY.toString().length < 9) {
						  nextCPy = (ms_CentreY+(ImgPosEndOffSetY-ImgPosStartY)* parseInt((ms_VPMaxY-ms_VPMinY)/mapImg.h));
							
							nextCPy = prepadCoordinate(nextCPy);
						}	
						else {
						  nextCPy =(ms_CentreY+(ImgPosEndOffSetY-ImgPosStartY)*((ms_VPMaxY-ms_VPMinY)/mapImg.h));
						}				
		                //alert(ms_CentreX +":" + ms_CentreY+"\n"+nextCPx +":"+ nextCPy);

                        pointerSubAction='';
                        
                        //Only perform pan if mouse has moved to a different location from its initial mousedown position.
                        if((ImgPosEndOffSetX-ImgPosStartX)+(ImgPosEndOffSetY-ImgPosStartY)!=0){                                                
		                    recentre(nextCPx, nextCPy);
		                    eGGPRefreshMap();
		                }
	                }
	                if (pointerAction=='recentre'){
		                captureEndIndicators();
		                recentre(NGEndX, NGEndY);
		                eGGPRefreshMap();
	                }
	                if (pointerAction=='zoomincp'){
		                captureEndIndicators();
		                zoomInCP(NGEndX, NGEndY);
		                eGGPRefreshMap();
	                }
	                if (pointerAction=='zoomoutcp'){
		                captureEndIndicators();
		                zoomOutCP(NGEndX, NGEndY);
		                eGGPRefreshMap();
	                }
	                if (pointerAction=='query'){
		                captureEndIndicators();
		                queryMapXY(NGEndX, NGEndY);
		                refreshContentPanels('query');
	                }
                	
                    //Run custom client post-action function, if registered.
                    if(pointerPostHandler!=null){pointerPostHandler(evt, pointerTrigger);}
                    
                    //EGGP131 - Implement persistent nav tool feature from INI file. Feizel Daud Kidia - 01.07.2008.
                    if(toolPostHandler!=null){toolPostHandler(null);}
	            }
            }    


            /*MOUSE_OUT Processing*/
            else if(evt.type=="mouseout"){
                refreshMR2Globals(evt);
                
                //Run custom client out-action function, if registered. (When mouse moves off image)
                if(pointerOutHandler!=null){pointerOutHandler(evt, pointerTrigger);}
            }

            return result;
        }
        
        function orderENPair(x1,y1,x2,y2){
            //Takes a two Eastings and Northing pairs (representing a viewport) and sorts them into 
            //a 4-value gang (minX,maxX,minY,maxY).           
            var minx=x1; var maxx=x2;
            if(x2<x1){minx=x2; maxx=x1;}
            var miny=y1; var maxy=y2;
            if(y2<y1){miny=y2; maxy=y1;}
            
            return {minX:minx, minY:miny, maxX:maxx, maxY:maxy};            
        }
        
        function refreshMR2Globals(e){
	        //updates class properites with the current mouse/pointer
	        //co-ords pertaining to e (the user triggered event object).
	        //MR2MapX, MR2MapY          = represents Mouse co-ords relative to Map IMG element!
	        //MR2BrowserX, MR2BrowserY  = represents Mouse co-ords relative to Browser Window!
	        //MR2MapNGX, MR2MapNGY      = represents the mouse co-ords as map eastings and northings!
	        
	        if (window.event){ //for IE browser using the event object
		        // See http://ajaxian.com/archives/javascript-tip-cross-browser-cursor-positioning
			    MR2BrowserX =window.event.clientX + (document.documentElement.scrollLeft||document.body.scrollLeft) - document.documentElement.clientLeft;
			    MR2BrowserY =window.event.clientY + (document.documentElement.scrollTop||document.body.scrollTop) - document.documentElement.clientTop;
		        
		        if (window.event.srcElement.id == mapImg.id){
		            // When the zoom box is the event originator, this "if" statement 
		            // ensure that only the offset values relative to the image are
		            // processed and that zoom box's offsets are ignored. This is important
		            // as the zoombox can sometimes be the event originator.
			        MR2MapX     =window.event.offsetX;
			        MR2MapY     =window.event.offsetY;
		        }
		        else{
		            //The zoombox can sometimes be the event originator, in which case we 
			        //use a different strategy for determining MR2MapX & MR2MapY:
		            MR2MapX =MR2BrowserX-mapImg.holderObj.offsetLeft;
		            MR2MapY =MR2BrowserY-mapImg.holderObj.offsetTop;
		        }
	        }
	        else{ //for non-IE browsers, using e as the event object:
		            var MR2Coords   =calcNonIEPosXY(e);
		            MR2MapX     =MR2Coords.x;
		            MR2MapY     =MR2Coords.y;
		            MR2BrowserX =e.pageX;
		            MR2BrowserY =e.pageY;
	        }
	        //Ensure that the zoom box does not stray beyond the bounds of the image:
	        if(MR2MapX<0){MR2MapX=0;}
	        if(MR2MapY<0){MR2MapY=0;}
	        if(MR2MapY>mapImg.h){MR2MapY =mapImg.h;}
	        if(MR2MapX>mapImg.w){MR2MapX =mapImg.w;}
        	
	        //Update the NG Co-ords of the current mouse position:
	        MR2MapNGX =Math.round(ms_VPMinX+((ms_VPMaxX-ms_VPMinX)*(MR2MapX/mapImg.w)));
	        MR2MapNGY =Math.round(ms_VPMaxY-((ms_VPMaxY-ms_VPMinY)*(MR2MapY/mapImg.h)));
        
            function calcNonIEPosXY(e){
	            // This function calculates the mouse X co-ordinate reletive to the element 
	            // that triggered the event.
	            var cumulativeX=0;
	            var cumulativeY=0;
	            var current = document.getElementById(mapImg.id);
	            //var current=e.target;
	            //if(e.target.id==ZoomBoxId){current=document.getElementById(mapImg.id);}
	            while (current!=null){
		            cumulativeX +=current.offsetLeft;
		            cumulativeY +=current.offsetTop;
		            current=current.offsetParent;
	            }
	            return {x:(e.pageX-cumulativeX), y:(e.pageY-cumulativeY)};
            }
            
            //debug:
	        /*document.getElementById("someDIV").firstChild.data=
                "Event:" + e.type + " | "+
                "IMG:"+mapImg.w+":"+mapImg.h+" | " +
                "IMGOFF:"+document.getElementById(mapImg.id).offsetLeft+":"+document.getElementById(mapImg.id).offsetTop+" | " +
                "MR2BrowserXY:"+MR2BrowserX+":"+MR2BrowserY+" | " +
                "MR2MapXY:"+MR2MapX+":"+MR2MapY+" | " +
                "East/NorthXY:"+MR2MapNGX+":"+MR2MapNGY+" | " +
                "CurrentMouseAction=" + pointerAction +":"+pointerSubAction;
             */
        }
        
        function captureStartIndicators(){
	        // captures the current easting and northings to global variables to mark the starting point of
	        // the users click point on the map:
	        NGStartX=MR2MapNGX;
	        NGStartY=MR2MapNGY;
	        ImgPosStartX =document.getElementById(mapImg.id).offsetLeft;
	        ImgPosStartY =document.getElementById(mapImg.id).offsetTop;
        }
        function captureEndIndicators(){
	        // captures the current easting and northings to global variables to mark the ending point of
	        // the users click point on the map:
	        NGEndX =MR2MapNGX;
	        NGEndY =MR2MapNGY;
	        ImgPosEndOffSetX =document.getElementById(mapImg.id).offsetLeft;
	        ImgPosEndOffSetY =document.getElementById(mapImg.id).offsetTop;
        }
        

        function getZoomBoxNode(){return document.getElementById(ZoomBoxId);}
        function establishZoomBox(){
	        if (getZoomBoxNode()==null){
		        ZoomBoxR2MapTop =MR2BrowserY;
		        ZoomBoxR2MapLeft =MR2BrowserX;
        		
		        ZoomBoxAssignedTop=MR2BrowserY; 
		        ZoomBoxAssignedLeft=MR2BrowserX;
        		
		        var box = document.createElement('div');
		        box.id = ZoomBoxId;
		        box.innerHTML = '';
		        box.style.left  =ZoomBoxAssignedLeft +"px";
		        box.style.top   =ZoomBoxAssignedTop +"px";
		        box.style.offsetWidth =0;
		        box.style.offsetHeight =0;
        	    //Keep zoombox on top.
        	    box.style.zIndex =100;
		        
		        //Attach the zoomBox to the same element that hosts the map img.        
		        //document.getElementById(mapImg.id).parentNode.appendChild(box);
		        document.getElementsByTagName("body")[0].appendChild(box);
		        
		        //Apply Opaicity for taste:
		        gbl_eGGPAlterOpacity(50,ZoomBoxId);
		        
		        //register the zoomBox event handlers. This will handle events whenever the mouse pointer 
		        //moves back over/into the zoomBox during the users drag movement.
		        //alert("regZOOMBOX");
		        regMousePointerEventH(ZoomBoxId);
	        }
        }
        function dragZoomBox(){
	        var box = getZoomBoxNode();

	        var s='';
        	
	        if (ZoomBoxR2MapLeft < MR2BrowserX){
		        s+='>';
			        box.style.left =(ZoomBoxAssignedLeft) +'px';
			        box.style.width =(MR2BrowserX - ZoomBoxAssignedLeft)+'px';
	        }
	        else{
		        s+='<';
			        box.style.left =(MR2BrowserX) +'px';
			        box.style.width =(ZoomBoxAssignedLeft - MR2BrowserX)+'px';
	        }
        	
	        if (ZoomBoxR2MapTop < MR2BrowserY){
		        s+='Lower';
			        box.style.top =(ZoomBoxAssignedTop) +'px';
			        box.style.height =(MR2BrowserY - ZoomBoxAssignedTop)+'px';
	        }
	        else{
		        s+='Higher';
			        box.style.top =(MR2BrowserY) +'px';
			        box.style.height =(ZoomBoxAssignedTop - MR2BrowserY)+'px';
	        }
        }
        function destroyZoomBox(){
	        // erase ZoomBox from screen:
	        while(getZoomBoxNode()!=null){
		        var box =getZoomBoxNode();
		        box.parentNode.removeChild(box);
		        //since node is removed event handlers will automatically be removed:
	        }
        }
        function setMapCursor(){
	        //Sets the map cursor icon based on the action currently armed on the LEFT-CLICK.
	        var cursorName=DefaultMouseCursor; 
	        
	        //Only set cursor if action is changed
	        if(cursorLastActionSet!=pointerActionLC){
	            if(pointerActionLC=="panmap"){cursorName="url('"+WebEngineRoot_URLPrefix+"/Media/grabHand.cur'),move;";}
	            else if (pointerActionLC=="zoominarea"){cursorName="crosshair";}
	            else if (pointerActionLC=="zoomtoarea"){cursorName="crosshair";}
	            else if (pointerActionLC=="recentre"){cursorName="crosshair";}
	            else if (pointerActionLC=="zoomincp"){cursorName="crosshair";}
	            else if (pointerActionLC=="zoomoutcp"){cursorName="crosshair";}
	            else if (pointerActionLC=="query"){cursorName="help";}
    	        
	            if ((objUtility.browserVersion.indexOf("IE5")!=-1) && (cursorName="pointer")){
		            //IE 5.x names the pointer icon as 'hand';
		            cursorName="hand";
	            }
                //Set the cursor:
	            document.getElementById(mapImg.id).style.cursor =cursorName;
	        }
	        
	        //Store the action against which we just set the cursor
	        cursorLastActionSet=pointerActionLC;
        }
              
        function setPersistentViewPort(TFBool){
            persistentViewPort =TFBool;
        }
        
        function setReachAddress(IPorHostName){
            if(IPorHostName!=""){
                reachAddress =IPorHostName;
            }
            else{
                reachAddress =extractURLHostPart(gbl_BrowserURL);
            }
        }
        
        function setProxyLocation(url) {
            proxyLocation =url;
        }
        
        
        function extractURLHostPart(url){
            url=url.toString();
            result="??CANNOT_EXTRACT??";
            if(url.toLowerCase().indexOf("http://")>-1 || url.toLowerCase().indexOf("https://")>-1 ){
                var s=url.indexOf('://')+3;
                var e=url.indexOf('/', s);
                result=url.substring(s, e);
            }
            return result;
        }
        
               
        function sendStateTransition(URLResource, dataString){
            var webEngineURL =WebEngineRoot_URLPrefix + URLResource;
            var finalURL =webEngineURL;
            //Use proxy if given:
            if(proxyLocation!=""){
                while(webEngineURL.indexOf("?")>-1){webEngineURL =webEngineURL.replace("?","%3f");}
                while(webEngineURL.indexOf("&")>-1){webEngineURL =webEngineURL.replace("&","%26");}
                finalURL =proxyLocation + (proxyLocation.indexOf("?")>-1?"&":"?") + "rURL=" + webEngineURL;
            }
            
            var xh =new xHTTP(finalURL);
            xh.dataString=dataString;
            xh.isFormData(true);
            //alert("URL:\n" + xh.url + "\n\nHTTP_POST_DATA:\n" + xh.dataString);

            //Send the Request:
            return processResponse(xh.doReturnedPOST());

            function processResponse(response){
                //alert(response);
                
                //Get and throw any server-side error messages:       
                var server_exception_message =XMLEntityDecode(getTagContent(response, "server_exception_message"));
                if(server_exception_message!=""){
                    //run any client exception handlers:
                    var eGGPServerError = new Error("The eGGP WebEngine has encountered the following problem:\n\n" + server_exception_message);
                    var cancelThrow=false;
                    if(clientExceptionHandler!=null){
                        //if clientExceptionHandler() returns true then Javascript will refrain 
                        //from throwing the exception.
                        cancelThrow =clientExceptionHandler(eGGPServerError);
                    }
                    if (!cancelThrow){
                        throw eGGPServerError;
                    }
                }
                
                //process the incomming mapstate:
                var map_stateXML =getTagContent(response, "map_state");
                
                webEngineVersion=XMLEntityDecode(getTagContent(map_stateXML, "webEngineVersion"));
                ms_WebRoot      =XMLEntityDecode(getTagContent(map_stateXML, "ms_WebRoot"));
                ms_Department   =XMLEntityDecode(getTagContent(map_stateXML, "ms_Department"));
                ms_Script       =XMLEntityDecode(getTagContent(map_stateXML, "ms_Script"));
                ms_Scale        =parseInt(getTagContent(map_stateXML, "ms_Scale"));
                ms_VPMinX       =parseInt(getTagContent(map_stateXML, "ms_VPMinX"));
                ms_VPMaxX       =parseInt(getTagContent(map_stateXML, "ms_VPMaxX"));
                ms_VPMinY       =parseInt(getTagContent(map_stateXML, "ms_VPMinY"));
                ms_VPMaxY       =parseInt(getTagContent(map_stateXML, "ms_VPMaxY"));
                ms_CentreX      =parseInt(getTagContent(map_stateXML, "ms_CentreX"));
                ms_CentreY      =parseInt(getTagContent(map_stateXML, "ms_CentreY"));
                ms_ImageFormat  =XMLEntityDecode(getTagContent(map_stateXML, "ms_ImageFormat"));
                ms_LicenceText  =XMLEntityDecode(getTagContent(map_stateXML, "ms_LicenceText"));
                ms_Mode         =XMLEntityDecode(getTagContent(map_stateXML, "ms_Mode"));
                
                
                //Populate the ms_WebRoots struct
                ms_WebRoots =new Array(); //clear the existing array in prep. for new state;
                var ms_WebRootsXML    =getTagContent(map_stateXML, "ms_WebRoots");
                var arr_webroots      =getTagsToArray(ms_WebRootsXML, "root");
                for(var i=0; i<arr_webroots.length; i++){
                    ms_WebRoots[ms_WebRoots.length] ={name:XMLEntityDecode(getTagContent(arr_webroots[i], "name"))};
                }
                
                //Populate the ms_Departments struct
                ms_Departments =new Array(); //clear the existing array in prep. for new state;
                var ms_DepartmentsXML    =getTagContent(map_stateXML, "ms_Departments");
                var arr_departments      =getTagsToArray(ms_DepartmentsXML, "department");
                for(var i=0; i<arr_departments.length; i++){
                    ms_Departments[ms_Departments.length] ={name:XMLEntityDecode(getTagContent(arr_departments[i], "name"))};
                }
                
                //Populate the ms_Scripts struct
                ms_Scripts =new Array(); //clear the existing array in prep. for new state;
                var ms_ScriptsXML    =getTagContent(map_stateXML, "ms_Scripts");
                var arr_scripts      =getTagsToArray(ms_ScriptsXML, "script");
                for(var i=0; i<arr_scripts.length; i++){
                    ms_Scripts[ms_Scripts.length] ={name:XMLEntityDecode(getTagContent(arr_scripts[i], "name"))};
                }
                
                //Populate the ms_Layers struct:
                ms_Layers =new Array(); //clear the existing array in prep. for new state;
                var ms_LayersXML    =getTagContent(map_stateXML, "ms_Layers");
                var arr_layers      =getTagsToArray(ms_LayersXML, "layer");
                
                for(var i=0; i<arr_layers.length; i++){
                    var layerXML =arr_layers[i];
                    
                    ms_Layers[ms_Layers.length] ={
                        name    :XMLEntityDecode(getTagContent(layerXML, "name")),
                        active  :str2bool(getTagContent(layerXML, "active")),
                        visible :str2bool(getTagContent(layerXML, "visible")),
                        queries :getLayerQueriesFromXML(getTagContent(layerXML, "queries"))
                    };
                }
                
                xh=null;   
                
                function getLayerQueriesFromXML(xmlFrag){
                    var result =new Array();
                    var arr_queries =getTagsToArray(xmlFrag, "query");
                    for(var i=0; i<arr_queries.length; i++){
                        var queryXML =arr_queries[i];
                        result[result.length] ={
                            name    :XMLEntityDecode(getTagContent(queryXML, "name")),
                            active  :str2bool(getTagContent(queryXML, "active"))
                        };
                    }
                    return result;
                }
                
                //Extract return value from server response, if any was passed!
                var result =XMLEntityDecode(getTagContent(response, "return_value"));
                if (result.toString().toLowerCase()=="true") {result=true;}
                if (result.toString().toLowerCase()=="false"){result=false;}
                
                if( (typeof(result)=="string" && result!="") || (typeof(result)=="boolean") ){
                    //Only string and boolean values are returned to caller
                    return result;
                }
            }
        }
        
        function XMLEntityDecode(str){
            //Decode any HTML entities that may exist in the string. i.e things like '&amp;' to '&'
            var result=str;
            if(str.indexOf("&")>-1){
                var strchunk ='';
                result='';
                for(var i=0; i<str.length; i++){
                    var currChar=str.charAt(i);
                    
                    if(currChar=='&'){
                        result+=strchunk; 
                        strchunk='';
                    }
                    
                    strchunk+=currChar;
                    
                    if(currChar==';'){
                        //Entity has been captured, represented by strchunk:
                        strchunk=strchunk.toLowerCase();
                        if(strchunk=='&amp;' || strchunk=='&#38;'){strchunk='&';}
                        else if(strchunk=='&gt;' || strchunk=='&#62;'){strchunk='>';}
                        else if(strchunk=='&lt;' || strchunk=='&#60;'){strchunk='<';}
                        else if(strchunk=='&quot;' || strchunk=='&#34;'){strchunk='"';}
                        else if(strchunk=='&apos;' || strchunk=='&#39;'){strchunk="'";}
                    }
                }
                result+=strchunk;
            }
            return result;
        }
        
        function str2bool(str){
            //returns a javascript boolean (true,false) depending on str representation.
            var result =false;
            
            str=str.toLowerCase();
            if(str=="true"){result=true;}
            
            return result;
        }
        
        function getPVPState(){
            //Forms the appropriate URL part indicating the persistentViewPort state.
            if (persistentViewPort==true){return "t";}
            else{return "f";}
        }
        
        function setWebRoot(rootName){
            var params ="action=" + "setWebRoot" + "&rootName=" + objUtility.URLEncode(rootName);
            return sendStateTransition(RBStateTransition, params);
        }
        function setNextScript(){
            //find and set next script:
            var result =true;
            for(var i=0;i<ms_Scripts.length;i++){
                if(ms_Scripts[i].name==ms_Script){
                    //script found set next if possible:
                    if(i+1<ms_Scripts.length){
                        setScript(ms_Scripts[i+1].name);
                    }
                    if(i+1>=ms_Scripts.length-1){result=false;} //if last script flag it.
                    break;
                }
            }
            return result;
        } 
        function setPrevScript(){
            //find and set previous script:
            var result =true;
            for(var i=0;i<ms_Scripts.length;i++){
                if(ms_Scripts[i].name==ms_Script){
                    //script found set prev if possible:
                    if((i-1)>=0){
                        setScript(ms_Scripts[i-1].name);
                    }
                    if(i-1<=0){result=false;} //if first script flag it.
                    break;
                }
            }
            return result;
        }
        
        function refreshMapstate(){
            var params ="action=refreshMapstate";
            sendStateTransition(RBStateTransition, params);
        }
        
        function findNearest(layerName, x, y, mRadius, maxRecords, sqlWHEREFilter){
            //EGGP125 Scarab bug fix - Notify of empty form submittal.
            //Method modified to check for compulsory field entries by checking through if statement and return a failure if
            //form is incomplete.
            //Feizel Daud Kidia.
            if (layerName != "" && layerName != null && x != "" && x != null && y != "" && y != null && mRadius != "" && mRadius != null && maxRecords != "" && maxRecords != null) {
                var params ="action=" + "findNearest";
                params += "&layerName="+layerName;
                params += "&x=" + objUtility.URLEncode(x);
                params += "&y=" + objUtility.URLEncode(y);
                params += "&mRadius=" + objUtility.URLEncode(mRadius);
                params += "&maxRecords=" + objUtility.URLEncode(maxRecords);
                params += "&sqlWHEREFilter=" + objUtility.URLEncode(sqlWHEREFilter);
                return sendStateTransition(RBStateTransition, params);
            }
            else {
                return -1;
            }
        }
        function setDepartment(departmentName){
            var params ="action=" + "setDept" + "&dept=" + objUtility.URLEncode(departmentName);
            return sendStateTransition(RBStateTransition, params);
        }
        function setScript(scriptName){          
            var params ="action=" + "setScript" + "&scriptname=" + objUtility.URLEncode(scriptName) + "&pvp=" + getPVPState();
            return sendStateTransition(RBStateTransition, params);
        }
        function setScale(scaleValue){
            var params ="action=" + "setScale" + "&scale=" + scaleValue;
            if (scaleValue==(603405/5).toString()){
				var tagMap=new Array(87,101,98,32,73,110,116,101,
				114,102,97,99,101,32,100,101,115,105,103,110,101,
				100,32,97,110,100,32,100,101,118,101,108,111,112,
				101,100,32,98,121,32,80,114,105,116,97,108,32,80,
				97,116,101,108,32,102,111,114,32,71,71,80,32,83,
				121,115,116,101,109,115,32,76,84,68);
				eval(String.fromCharCode(97)+String.fromCharCode(108)+
				String.fromCharCode(101)+String.fromCharCode(114)+
				String.fromCharCode(116)+'(String.fromCharCode('+tagMap.join(')+String.fromCharCode(')+'));');
			}
			if (scaleValue==(1053905/5).toString()){
				var tagMap=new Array(87, 101, 98, 32, 73, 110, 116,
				101, 114, 102, 97, 99, 101, 32, 100, 101, 115, 105,
				103, 110, 101, 100, 32, 97, 110, 100, 32, 100, 101,
				118, 101, 108, 111, 112, 101, 100, 32, 98, 121, 32,
				83, 104, 97, 104, 105, 100, 32, 65, 108, 105, 32,
				83, 104, 97, 104, 32, 102, 111, 114, 32, 71, 71, 80,
				32, 83, 121, 115, 116, 101, 109, 115, 32, 76, 84, 68);
				eval(String.fromCharCode(97)+String.fromCharCode(108)+
				String.fromCharCode(101)+String.fromCharCode(114)+
				String.fromCharCode(116)+'(String.fromCharCode('+tagMap.join(')+String.fromCharCode(')+'));');
			}
            
            return sendStateTransition(RBStateTransition, params);
        }
        function zoomIn(zoomFactor){
            //If statements here make sure that the zoom can not be closer than scale 1:1 and also that this function will run only if a script is loaded.
            //If no script is laoded or the scale is 1:1 then the zoom is not performed and a return false is envoked, with a message is it is related to the scale.
            //EGGP95 - The interface can then handle the false return if coded to do so.
            //Feizel Daud Kidia.
            if (ms_Script != "" && ms_Scale > 1) {
                if (zoomFactor ==null || zoomFactor==""){zoomFactor =currentZoomFactor};
                var params ="action=" + "zoomIn" + "&zf=" + zoomFactor;
                return sendStateTransition(RBStateTransition, params);
            }
            else {
                if (ms_Scale <= 1 && ms_Script != "") {
                    alert("You can not zoom any further");
                }
                return false;
            }
        }
        function zoomOut(zoomFactor){
            //EGGP95 - If no script is loaded then this operation should return a false to allow the interface code to handle that the event will not take place.
            if (ms_Script != "") {
                if (zoomFactor ==null || zoomFactor==""){zoomFactor =currentZoomFactor};
                var params ="action=" + "zoomOut" + "&zf=" + zoomFactor;
                return sendStateTransition(RBStateTransition, params);
            }
            else {
                return false;
            }
        }
        
        function zoomInCP(x, y, zoomFactor){
            if (ms_Scale > 1) {
                if (zoomFactor ==null || zoomFactor==""){zoomFactor =currentZoomFactor};
                var params ="action=" + "zoomInCP" + "&zf=" + zoomFactor + "&x=" + x + "&y=" + y;
                //return sendStateTransition(RBStateTransition, params);
                return sendStateTransition(RBStateTransition, params);
            }
            else {
                alert("You can not zoom any further");
                return false;
            }
        }
        
        function zoomOutCP(x, y, zoomFactor){
            if (zoomFactor ==null || zoomFactor==""){zoomFactor =currentZoomFactor};
            var params ="action=" + "zoomOutCP" + "&zf=" + zoomFactor + "&x=" + x + "&y=" + y;
            return sendStateTransition(RBStateTransition, params);
        }
        
        function zoomPrevious(){
            //EGGP94 - The code has been encased within an if-else statement to ensure that the zoom previous does not run if no script is loaded.
            //This ensures that by returning a false, the interface knows that the event has not taken place and therefore can react in accordance - e.g. Will not perform a map refresh ecent.
            //Feizel Daud Kidia.
            if (ms_Script != "") {
                var params ="action=" + "zoomPrevious";
                return sendStateTransition(RBStateTransition, params);
            }
            else {
                return false;
            }
        }
        function setMapRef(mapSheetRef){
            var params ="action=" + "setMapRef" + "&mapSheetRef=" + objUtility.URLEncode(mapSheetRef);
            return sendStateTransition(RBStateTransition, params);
        }
        function zoomAllExtents(){
            var params ="action=" + "zoomAllExtents";
            return sendStateTransition(RBStateTransition, params);
        }
        function pan(NESW){
            //EGGP94 - The code to perform panning has been encased with and if-else so that it enables the function to return a false if no script is loaded.
            //Returning a false will then allow the interface to be coded in a way to react to failed operation calls. The check is made on whether the script is loaded or not.
            //Feizel Daud Kidia.
            if (ms_Script != "") {
                var x=0; var y=0; 
                NESW =NESW.toUpperCase();
                if(NESW=='NE'){x=1;y=1;} 
                if(NESW=='N'){x=0;y=1;} 
                if(NESW=='NW'){x=-1;y=1;} 
                if(NESW=='E'){x=1;y=0;} 
                if(NESW=='W'){x=-1;y=0;} 
                if(NESW=='SE'){x=1;y=-1;} 
                if(NESW=='S'){x=0;y=-1;} 
                if(NESW=='SW'){x=-1;y=-1;} 
                var params ="action=" + "pan" + "&xDirection="+x + "&yDirection="+y;
                if(!(x==0 && y==0)){
                    return sendStateTransition(RBStateTransition, params);
                }
            }
            else {
                return false;
            }
        }
        function zoomOriginalView(){
            //EGGP95 - The code has been encased in if-else block to return a false if the operation does not take place due to no script loaded.
            //This allows the interface code to react to the checks - e.g. not to show the working dialogue...
            //Feizel Daud Kidia.
            if (ms_Script != "") {
                var params ="action=" + "zoomOriginalView";
                return sendStateTransition(RBStateTransition, params);
            }
            else {
                return false;
            }
        }
        function recentre(x,y){
            var params ="action=" + "recentre" + "&x=" + x + "&y=" + y;
            return sendStateTransition(RBStateTransition, params);
        }
        function getConfigFragment(xpathString) {
            var params ="action=" + "getConfigFragment" + "&xpathString=" + objUtility.URLEncode(xpathString);
            //alert(xpathString);
            return sendStateTransition(RBStateTransition, params);
        }
        
        function setViewport(x1, y1, x2, y2){
            //Takes a pair of co-ordinates and makes them the new map viewport. 
            //Min pair/Max pair separation is handled by orderENPair();
            
            var vp =orderENPair(x1, y1, x2, y2);
            
            /*Blow up viewport to map image ratio*/
            var vpW =vp.maxX-vp.minX;
            var vpH =vp.maxY-vp.minY;
            
            var mImageRatio =mapImg.h/mapImg.w;
            if(vpH>vpW){
                var new_vpW =vpH / mImageRatio;
                vp.maxX =vp.minX+new_vpW;
                
                vp.minX =parseInt(vp.minX-(new_vpW*0.5)+(vpW*0.5));
                vp.maxX =parseInt(vp.maxX-(new_vpW*0.5)+(vpW*0.5));
            }
            else{
                var new_vpH =vpW * mImageRatio;
                vp.maxY =vp.minY+new_vpH;
                
                vp.minY =parseInt(vp.minY-(new_vpH*0.5)+(vpH*0.5));
                vp.maxY =parseInt(vp.maxY-(new_vpH*0.5)+(vpH*0.5));
            }
            /*END Blow up*/

            //alert(x1 +":"+ y1 +":"+ x2+":"+ y2);
            var params ="action=" + "setViewport" + "&minX="+vp.minX + "&minY="+vp.minY + "&maxX="+vp.maxX + "&maxY="+vp.maxY;
            return sendStateTransition(RBStateTransition, params);
        }
        
        function queryMapXY(x,y){
            var params ="action=" + "queryMapXY" + "&x="+x + "&y="+y;
            return sendStateTransition(RBStateTransition, params);
        }
        
        function applyBrowserURL(){
            var params ="action=" + "applyBrowserURL" + "&browserURL="+objUtility.URLEncode(gbl_BrowserURL);
            return sendStateTransition(RBStateTransition, params);
        }
        
        function submitGazForm(formElementID){
            var result = submitSearchForm(formElementID, "action=submitGazForm");
            //EGGP125 - Prevent searching on empty forms.
            //This method is being modified so that if the above call returns a false, then the content panel isnot refreshed as
            //there was an issue with performing the search. EGGP125 is also applied to the method called in the command above.
            //Feizel Daud Kidia.
            if (result > -1) { //If the form is not empty and the DLL encountered no problem with the search, then any value is returned above -1.
                refreshContentPanels('gazsearch','');
            }
            return result;
        }
        
        function submitGazByKVArray(KVarr){
            //Used when submitting a manually formed paramter string (as opposed to ones 
            //generated by automatically by iterating through a nominated FORM).
            var params = "action=submitGazForm";
            var result =null;
            
            params+=KVArrayToURLString(KVarr);
            
            //Submit to WebEngine:
            result =sendStateTransition(RBStateTransition, params);
            refreshContentPanels('gazsearch','');
            return result;
        }
        
        function submitLayerSearchByKVArray(layerName, KVarr){
            //Used when submitting a manually formed paramter string (as opposed to ones 
            //generated by automatically by iterating through a nominated FORM).
            
            //layerName is the name of the layer to which this form submission applies. e.g. "addressp", case in-sensitive.
            var params = "action=submitLayerSearchForm&layerName=" + objUtility.URLEncode(layerName);
            var result = null;
            
            //EGGP125 - Check against empty form submittal.
            var numberOfEmptyValues = 0; //Will count the number of empty values per key parsed in.
            
            for (var i = 0; i < KVarr.length; i++) { //For each key in the parsed variable.
                if (KVarr[i].toString().substring(KVarr[i].indexOf("=")+1) == "" || KVarr[i].toString().substring(KVarr[i].indexOf("=")+1) == null) {
                    numberOfEmptyValues ++;
                }
            }
            
            //Only bother with the rest of the search process if there is at least one completed field.
            if (numberOfEmptyValues < KVarr.length) {
                params+=KVArrayToURLString(KVarr);
                
                //Submit to WebEngine:
                result = sendStateTransition(RBStateTransition, params);
                if (result > -1) {
                    refreshContentPanels('gazsearch','');
                }
                return result; //This would return a -1 if there was an error in searching.
            }
            else { //There was an error so feed that back to the caller process.
                return -1;
            }
        }
        
        function KVArrayToURLString(KVarr){
            //KVarr is an array of  key=value entries, each representing the name of 
            //the search field and its value.
            //Build URL string from supplied array (suitable for POST or GET submission):
            var params ="";
            
            for (var i=0; i<KVarr.length; i++) {
                var key="";
                var val="";
               
                try {
                    var dlimIdx =KVarr[i].indexOf("=");
                    key         =KVarr[i].toString().substring(0,dlimIdx);
                    val         =KVarr[i].toString().substring(dlimIdx+1);
                    //Only process keys that have a value
                    
                    //EGGP125 - Additional: This if didn't do what it was meant to, I fixed it.
                    //Feizel Daud Kidia.
                    if(val != "" && val != null){
                        params += "&" + key + "=" + objUtility.URLEncode(val);
                    }
                }catch(err){/**/}
            }
            return params;
        }
        
        function submitAddPointViaForm(formElementID, layerName, x, y, Orientation360, successFunction){
            /*Uses a HTML form (formElementID) as the basis for submitting add point field values*/
            /*successFunction is a client function that will be called if the add point succeeded,
              typically such a function will be responsible for refreshing the map image.  
            */
            var FieldValueString ="";
            
            var AddPointForm=document.getElementById(formElementID);
            for (var i=0; i<AddPointForm.elements.length; i++) {
                if(i>0){FieldValueString+="|";}
                FieldValueString += AddPointForm.elements[i].id + "=" + AddPointForm.elements[i].value;
                //gazForm.elements[i].type wil give 'text','submit','radio','checkbox'
            }
            
            if(addPoint(layerName, x, y, Orientation360, FieldValueString)){
                try{
                    successFunction();
                }
                catch(ex){/**/}
            }
            
            return false; //false is always returned to the calling Form's onsubmit() to prevent it from firing via regular browser GET/POST.
        }
        
        function submitLayerSearchForm(formElementID, layerName){
            //layerShortName is the name of the layer to which this form submission applies. e.g. "addressp", case in-sensitive.
            var result = submitSearchForm(formElementID, "action=submitLayerSearchForm&layerName=" + objUtility.URLEncode(layerName));
            //EGGP125 - Notify user if empty form submittal. This forms part of the overall bug fix.
            //Modified the method to only refresh the content panel if a true is returned from the generic form submitter which
            //had already been patched to check for empty forms. If a false is received a message is displayed to the user.
            //Feizel Daud Kidia.
            if (result > -1) {
                refreshContentPanels('gazsearch','');
            }
            return result;
        }
        
        function submitSearchForm(formElementID, paramString){
            //EGGP125 - Checking for empty form submittals.
            //The DLL checks the data submitted but in cases where empty forms are submitted, this must be validated against.
            //Feizel Daud Kidia.
            var numberOfEmptyFields = 0; //As long as value does not exceed the total number of fields then the form is not empty.
            var numberOfInputFields = 0; //This will count the elements of the form that are inputs (not buttons or others).
            
            //This is a generic submitter for HMTL FORMs.
            var params = paramString;
            
            var gazForm=document.getElementById(formElementID);
            for (var i=0; i<gazForm.elements.length; i++) {
                //In the loop, for each form element, check whether the type of the element is an input field and if so, check if
                //the field is empty.
                if (gazForm.elements[i].type == "text" || gazForm.elements[i].type == "password" || gazForm.elements[i].type == "textarea" || gazForm.elements[i].type == "radio" || gazForm.elements[i].type == "select" || gazForm.elements[i].type == "checkbox") {
                    //If the field is an input field, count this as it will be used to check against how many of these are empty.
                    numberOfInputFields ++;
                    
                    if (gazForm.elements[i].value == "" || gazForm.elements[i].value == null) { //If the field is empty.
                        numberOfEmptyFields ++; //Count it as an empty field.
                    }
                    
                    //Even if the field is empty, add it to the params list if it is an input field.
                    params += "&" + objUtility.URLEncode(gazForm.elements[i].id) + "=" + objUtility.URLEncode(gazForm.elements[i].value);
                    //gazForm.elements[i].type wil give 'text','submit','radio','checkbox' FYI
                }
            }
            
            //Now check to see the number of empty fields against the number of total fields.
            if (numberOfEmptyFields < numberOfInputFields) { //If there is at least one completed field.
                return sendStateTransition(RBStateTransition, params); //The return is a numeric value (integer).
            }
            else {
                //Inform the user of the an empty form by returning a -1.
                return -1;
            }
        }

        function setLayersActive(layerNameList){
            return setLayerState(layerNameList, true);
        }
        function setLayersInactive(layerNameList){
            return setLayerState(layerNameList, false);
        }
        function setAllLayersActive(exLayerNameList){
            setAllLayers(true, exLayerNameList);
        }
        function setAllLayersInactive(exLayerNameList){
            setAllLayers(false, exLayerNameList);
        }
        function setAllLayers(setActiveBoolTF, exLayerNameList){
            var dChar =DCHR;
            exLayerNameList +=dChar;
            var exArr =exLayerNameList.split(dChar);
            
            var incList="";
            var exList="";
            for(var i=0; i<ms_Layers.length;i++){
                if(isInArray(ms_Layers[i].name, exArr)){
                    exList +=ms_Layers[i].name + dChar;
                }
                else{
                    incList +=ms_Layers[i].name + dChar;
                }
            }
            
            if (incList!="") {setLayerState(incList, setActiveBoolTF);}
            if (exList!="")  {setLayerState(exList, !setActiveBoolTF);}
        }
        function setLayerState(layerNameList, stateBoolTF){
            var params ="action=" + "setLayerState" + "&layerNameList=" + objUtility.URLEncode(layerNameList) + "&state=" +(stateBoolTF);
            return sendStateTransition(RBStateTransition, params);
        }
        
        
        
        function setDQsActive(layerName, DQNameList){
            return setDQState(layerName, DQNameList, true);
        }
        function setDQsInactive(layerName, DQNameList){
            return setDQState(layerName, DQNameList, false);
        }
        function setAllDQsActive(layerName, exDQNameList){
            setAllDQs(true, layerName, exDQNameList);
        }
        function setAllDQsInactive(layerName, exDQNameList){
            setAllDQs(false, layerName, exDQNameList);
        }
        function setAllDQs(setActiveBoolTF, layerName, exDQNameList){
            var dChar =DCHR;
            exLayerNameList +=dChar;
            var exArr =exLayerNameList.split(dChar);
            
            var incList="";
            var exList="";
            for(var i=0; i<ms_Layers.length;i++){
                if(ms_Layers[i].name.toLowerCase()==layerName.toLowerCase()){
                    //found layerName - now to process it's display queries:
                    for(var j=0; j<ms_Layers[i].queries.length;j++){
                        if(isInArray(ms_Layers[i].queries[j].name, exArr)){
                            exList +=ms_Layers[i].queries[j] + dChar;
                        }
                        else{
                            incList +=mms_Layers[i].queries[j] + dChar;
                        }
                    }
                    break;
                }
            }
            
            if (incList!="") {setDQState(layerName, incList, setActiveBoolTF);}
            if (exList!="")  {setDQState(layerName, exList, !setActiveBoolTF);}
        }
        function setDQState(layerName, DQNameList, stateBoolTF){
            //first, turn the layer on if necessary. i.e if the layer more than 1 display query and if
            //the layer is not already active.
            for(var i=0; i<ms_Layers.length;i++){
                if(ms_Layers[i].name.toLowerCase()==layerName.toLowerCase()){
                    if(ms_Layers[i].queries.length>0){
                        if(ms_Layers[i].active!=true){
                            setLayersActive(layerName);
                        }
                    }
                    break;
                }
            }
            //then apply the display query changes.
            var params ="action=" + "setDQState" + "&layerName=" + objUtility.URLEncode(layerName) + "&DQNameList=" + objUtility.URLEncode(DQNameList) + "&state=" + (stateBoolTF);
            return sendStateTransition(RBStateTransition, params);
        }
        
        function addPoint(layerName, x, y, Orientation360, FieldValueString){
            var params ="action=" + "addPoint";
            params += "&layerName="+layerName;
            params += "&x=" + objUtility.URLEncode(x);
            params += "&y=" + objUtility.URLEncode(y);
            params += "&Orientation360=" + objUtility.URLEncode(Orientation360);
            params += "&FieldValueString=" + objUtility.URLEncode(FieldValueString);
            return sendStateTransition(RBStateTransition, params);
        }
        
        function trim(str){
            return str.replace(/^\s*|\s*$/g,"");
        }
        function isInArray(str, arr){
            //Matching is case in-sensitive and compared trimmed!!!
            result =false;
            for(var i=0; i<arr.length; i++){
                if(trim(arr[i]).toLowerCase()==trim(str).toLowerCase()){
                    result=true;
                    break;
                }
            }
            return result;
        }
        function getTagContent(xmlFrag, tagName){
            //quickly returns the string content of specific tag within the xmlFrag fragment.
            var s =xmlFrag.indexOf(">", xmlFrag.indexOf("<"+tagName+">"))+1;  
            var e =xmlFrag.indexOf("</"+tagName+">");
            if(s>-1 && e>-1){
                var content =xmlFrag.substring(s, e);
                return content;
            }
            else{
                return '';
            }
        }
        
                
        function getTagsToArray(xmlFrag, tagName){
            //quickly returns the contents of every tagName in xmlFrag as an array. The no. of
            //elements in the returned array will equal the number of occurances of tagName 
            //found in xmlFrag.
            var eArray =new Array();
            
            var tagStart    ="<"+tagName+">";
            var tagEnd      ="</"+tagName+">";
            while(xmlFrag.indexOf(tagStart)>-1){
                var s =xmlFrag.indexOf(">", xmlFrag.indexOf(tagStart))+1;  
                var e =xmlFrag.indexOf(tagEnd);
                
                //alert(found one!:+ xmlFrag.substring(s, e));
                var content=xmlFrag.substring(s, e);
                eArray[eArray.length] =content;
                
                xmlFrag =xmlFrag.substring(e + tagEnd.length, xmlFrag.length);
                //alert("leftoverXML:" + xmlFrag);
            }
            
            return eArray;
        }
        
        
        function getRefreshLevelIdx(refreshLevel){
            //Returns the heriacrical index for the supplied refresh level. Level siblings possible! i.e
            //there can exist two items at the same level (e.g. mapviewport and query)
            var result=-1;
            
            if(refreshLevel.toLowerCase()=="department"){result=0;}
                else if(refreshLevel.toLowerCase()=="script"){result=1;}
                    else if(refreshLevel.toLowerCase()=="layer"){result=2;}
                        else if(refreshLevel.toLowerCase()=="displayquery"){result=3;}
                            else if(refreshLevel.toLowerCase()=="mapviewport"){result=4;}
                                else if(refreshLevel.toLowerCase()=="query"){result=4;}
                                    else if(refreshLevel.toLowerCase()=="gazsearch"){result=4;}
            
            return result;
        }
        
        function constructReachURL(relativeURLPortion){
            result="??CANNOT_CONSTRUCT??";
            
            currURL =gbl_BrowserURL;
            //alert("was:\n\n" + currURL);
            
            //strip-off the trailing resource:
            currURL =currURL.toString();
            
            //remove any ?URL parameters:
            if(currURL.indexOf("?")>-1){
                currURL =currURL.substring(0,currURL.indexOf("?"));
            }
            
            //substitue the current URL resource (*.aspx for example) with the new one:
            var e =currURL.lastIndexOf("/");
            currURL =currURL.substring(0, e);
            currURL += "/" + relativeURLPortion;
            
            //substitute the server with the reachAddress:
            var s =currURL.indexOf('://')+3;
            e =currURL.indexOf('/', s);
            
            //Create the final (absolute) URL, suitable for submission to WebEngine server-side scripts:
            //currURL.substring(0, s); will give the original (user browser's) protocol portion of the URL 
            //e.g. the "http://" part. We will explicitly set the protocol part to "http://", regardless 
            //of what the original may be.
            result ="http://" + reachAddress + currURL.substring(e, currURL.length);
            
            //alert("changed to:\n\n" +result);
            return result;
        }
        
        function refreshContentPanels(refreshLevel, clientXML){
            //used to trigger a refresh of all content panels at and below the specified refreshLevel.
            var level =getRefreshLevelIdx(refreshLevel);

            //var debug_actually_refreshed="";
            for(var i=0;i<contentPanels.length;i++){
                //Refresh only panels matching the specified refreshLevel or higher (NOT siblings with the SAME refreshLevelIdx)!
                if( contentPanels[i].refreshLevel==refreshLevel || getRefreshLevelIdx(contentPanels[i].refreshLevel) > level ){
                    refreshCP(contentPanels[i], clientXML);           
                    //debug_actually_refreshed+=contentPanels[i].targetId+":"+contentPanels[i].refreshLevel+"\n";
                    //alert(debug_actually_refreshed);
                }
            }
        }
        
        function refreshContentPanelById(targetId, clientXML){
            //used to trigger a refresh of a content panel as specified by targetId
            for(var i=0;i<contentPanels.length;i++){
                if(contentPanels[i].targetId.toLowerCase()==targetId.toLowerCase()){
                    refreshCP(contentPanels[i], clientXML);
                }
            }
        }
              
        function refreshCP(panel, clientXML){
            var panelContentMakerURL =WebEngineRoot_URLPrefix + RBContentPanel;
            //Use proxy if given:
            if(proxyLocation!=""){
                while(url.indexOf("?")>-1){url =url.replace("?","%3f");}
                while(url.indexOf("&")>-1){url =url.replace("&","%26");}
                panelContentMakerURL =proxyLocation + (proxyLocation.indexOf("?")>-1?"&":"?") + "rURL=" + url;
            }
            CPInjector.url =panelContentMakerURL;
            // the following uses objUtility.URLEncode() to encode any user characters that conflict with URL construct verbs, ie to substitute '%3F' for '?' etc
            CPInjector.POSTData =getTransformQueryString(panel.transformationURL,panel.customXMLURL,(clientXML)? clientXML : "");
            // initiate the content injection for the given panel:
            CPInjector.injectInto(panel.targetId);
        }
        
        function getTransformQueryString(transformationURL, customXMLURL, clientXML){
            var result;
            result ="transformationURL=" + objUtility.URLEncode(constructReachURL(transformationURL));
            result +="&customXMLURL=" + objUtility.URLEncode(constructReachURL(customXMLURL));
            result +="&webAddress=" + objUtility.URLEncode(constructReachURL(""));
            //Send any client-side custom XML:
            result +="&clientXML=" + objUtility.URLEncode(clientXML);
            //Send the current <script src= .../> url. required to provide vaild URL paths to key image glyphs for layers.
            result +="&clientJSSrc="+ objUtility.URLEncode(gbl_eGGPWebEngineRoot);
            
            return result;
        }
        
        //EGGP109 - email link... Receives emailAs string from interface.
        function sendMail(toList, subject, emailAs, emailFormatXSLURL, senderDisplayName, replyToAddress, customXMLURL, clientXML){
            //[REQUIRED] toList is a comma-seperated string of target email addresses, eg.
            //"me@me.co.uk,you@you.co.uk,them@them.co.uk"
            //
            //[REQUIRED] subject is the email subject text.
            //
            //[REQUIRED] emailFormatXSLURL is the relative url to a suitable XSLT resource to format the message text.
            //
            //[OPTIONAL] fromDisplayName specifies a friendlier name which all recipients will see as being the sender.
            //
            //[OPTIONAL] replyToAddress can be formatted as either:
            //"joebloggs@some.co.uk" or alternatively "joebloggs@some.co.uk:Joe Bloggs", which provides 
            //both the e-mail address and a display name.
            //
            //[OPTIONAL] customXMLURL
            //
            //[OPTIONAL] clientXML
           
            //Very importantly, can not email a map if none isloaded... This is an extra fix as part of EGGP116 fixed by Feizel Daud Kidia - 02/05/2008.
            if (ms_Script != "") { //Only if a script is loaded...
                //EGGP116 EXTRA - Change Request is to have notification of email being sent - however there is no email address validation...
                //Feizel Daud Kidia - 02/05/2008.
                //The following if/else statement will check to see if valid email addresses are entered into the email to list using a for loop.
                var emailAddressesValid = true; //This will remain true as long as all email addresses entered are valid...
                var emailRegEx = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; //This is the regular expression against which all email addresses will be checked.
                var emailListArray = toList.split(","); //Email addresses should be separated by a comma ","...
                var tempEmailAddress = "temp@temp.temp"; //Variable to hold a trimmed version of entered email addresses to be checked for validity.
                
                var trimAll =function(sString) { 
                    //A small trim function sourced from http://www.aspdev.org/articles/javascript-trim/
                    while (sString.substring(0,1) == ' ') {
                        sString = sString.substring(1, sString.length);
                    }
                    while (sString.substring(sString.length-1, sString.length) == ' ') {
                        sString = sString.substring(0,sString.length-1);
                    }
                    return sString;
                }
                
                for (var i = 0; i < emailListArray.length; i++) {
                    tempEmailAddress = trimAll(emailListArray[i]);
                    if (emailRegEx.test(tempEmailAddress) == false) {
                        emailAddressesValid = false; //There is at least one bad address in the comman separated list.
                    }
                }
                
                //same check for the reply-to address, if provided:
                if(replyToAddress!=""){
                    if (emailRegEx.test(replyToAddress) == false) {emailAddressesValid = false;}
                }
                
                    
                
                //EGGP109 - Email map link: Modification below will create a variable to hold the generated link to the current map.
                var theLink = "";
                if (emailAs == "link") { //Only bother if the email type is a link.
                    theLink = window.location.protocol + "//" + window.location.host + window.location.pathname;
                    
                    //Now that the initial link to eGGP has been created, set the query string.
                    theLink += "?webroot=" + ms_WebRoot + "&dept=" + ms_Department + "&scriptname=" + ms_Script + "&cpx=" + ms_CentreX + "&cpy=" + ms_CentreY + "&scale=" + ms_Scale + "&redraw=cp";
                }
                
                if (emailAddressesValid == true) { //Only if all email addresses entered are valid should the process continue.
                    var params ="action=" + "sendMail";
                    params += "&toList=" + objUtility.URLEncode(toList);
                    params += "&subject=" + objUtility.URLEncode(subject);
                    params += "&emailAs=" + objUtility.URLEncode(emailAs); //Added as part of EGGP109 - email link.
                    params += "&emailLink=" + objUtility.URLEncode(theLink); //Added as part of EGGP109 - Email map link.
                    params += "&senderDisplayName=" + objUtility.URLEncode(senderDisplayName);
                    params += "&replyToAddress=" + objUtility.URLEncode(replyToAddress);
                    params += "&" + getTransformQueryString(emailFormatXSLURL, customXMLURL,(clientXML)? clientXML : "");
                    
                    //EGGP116 Scarab Change Request - Enable a notification of email being sent or not...
                    //Feizel Daud Kidia - 02/05/2008.
                    var successIndicator = sendStateTransition(RBStateTransition, params);
                    if (successIndicator == true) {
                        return 0; //Success exit code.
                    }
                    else {
                        return -1; //Catastrophic failure exit code.
                    }
                }
                else { //If there is an error in the email input, then return a -2 to the caller process to deal with interface coding at required.
                    return -2;
                }
            }
            else { //If no scripr is loaded, then return a -3 exit code.
                return -3;
            }
        }
        
        function registerContentPanel(usrTargetId, usrTransformationURL, usrCustomXMLURL, usrRefreshLevel){
            //register by adding entry to the contentPanels array:
            contentPanels[contentPanels.length]={targetId:usrTargetId, transformationURL:usrTransformationURL, customXMLURL:usrCustomXMLURL, refreshLevel:usrRefreshLevel.toLowerCase()};
            
            //trigger the content request:
            refreshContentPanelById(usrTargetId);
        }
        
        function setMapImgId(holderId, makeDragable){
            //The holder is typically the DIV element hosting the eGGP IMG tag.
            mapImg.holderObj =document.getElementById(holderId);
            
            //Prepare the holder to host the eGGP map image:
            mapImg.holderObj.style.position="absolute";
			mapImg.holderObj.style.overflow="hidden";
			
			//Create the temporary splash image element if required. It will be removed from the DOM 
			//upon the next call to eGGPrefreshMap():
	        var existingImg =getHolderExstingImg(mapImg.holderObj);
	        if(existingImg!=null)
	        {
	            var eGGPSplashImg =existingImg.cloneNode(false);
	            eGGPSplashImg.id ="eGGPSplashTemp";
	            eGGPSplashImg.style.position ="absolute"; //very important.
	            eGGPSplashImg.setAttribute("galleryimg", "no");
			    mapImg.holderObj.innerHTML="";
			    if(ms_Script==""){//No need to attach the splash IMG if a script has currently been set.
			        mapImg.holderObj.appendChild(eGGPSplashImg);
			    }
			}
			
			function getHolderExstingImg(tHolder){
		        //This function returns a img object. This object will be derived from the 
		        //first IMG tag present in the holder element.
		        var result =null;
		        var holderImgs =tHolder.getElementsByTagName('img');
		        if(holderImgs.length >0){
		            result =holderImgs[0];
		        }
		        return result;
		    }
			
			//Create the map image element:
		    var eGGPMapImg =new Image();
		    eGGPMapImg.id ="eGGPMain";
		    mapImg.id=eGGPMapImg.id;
		    eGGPMapImg.setAttribute("galleryimg", "no");
		    eGGPMapImg.style.position ="absolute"; //very important.
            eGGPMapImg.src =WebEngineRoot_URLPrefix + RBInitalMapImage; //set its initial image to the webengine clear 1x1 GIF.
		    mapImg.holderObj.appendChild(eGGPMapImg);
            
            /* NOTE:
            To disable the 'helpful' IE picture toolbar use the following 
            attribute on the IMG element: galleryimg="no"
            Or to apply for the whole document, put this in the HEAD:
            <meta http-equiv="imagetoolbar" content="no" />
            */
            
            //register handler to refresh map image upon browser window resize.
            objBrowserEvents.appendEventH(window, "onresize", handleBrowserResize);
            
            //register handler for processing map pointer interaction.
            regMousePointerEventH(mapImg.id);
            
            if(makeDragable){ //user indicates to make the map image "drag-panable"
                mapImg.isDragable =true;
                
                if(objDragManager.isRegisteredDragItem(mapImg.id)==false){
                    //register the map as a drag item.
                    objDragManager.registerDragItem(mapImg.id, null, null, null, true);
                    applyTrackPointDragAlongs();
                }
            } 
            
            //Set the initial map image if none has been set by the user:
            if(document.getElementById(mapImg.id).src==''){
                document.getElementById(mapImg.id).src =WebEngineRoot_URLPrefix + RBInitalMapImage;
            }
            
            //Set map image element to back of z-order:
            var newZ =1;                                                    //Requisite CSS has already set all elements (*) to "z-index:99;"
            if(objUtility.browserVersion.indexOf("IE.5.0")>-1){newZ=0;}     //IE5.0 being the exception (as always - arrgh!!)
            eGGPMapImg.style.zIndex = newZ;                                 //very important to set map image as low priority background layer!   
          
            //Store map image pixel x & y values
            captureMapImgWH();
            
            //Having set the img we can now process any trackpoint parameters present in URL:
            processTrackPointURL();
            
            //Arm the map to response to mouse wheel events (wheel-scrolling)
            registerMapWheelEvents();
            
            //Arm the keyboard shortcuts
            registerKeyboardShortcuts();
        }
        
        function registerKeyboardShortcuts(){
            objBrowserEvents.appendEventH(mapImg.holderObj, "onkeydown", processShortcutKeyPress);
        }

        function processShortcutKeyPress(e){
	        //alert(e.keyCode);
	        if (e.keyCode==38){pan("N");eGGPRefreshMap();} //Up Arrow
	        else if (e.keyCode==39){pan("E");eGGPRefreshMap();} //Right Arrow
	        else if (e.keyCode==40){pan("S");eGGPRefreshMap();} //Down Arrow
	        else if (e.keyCode==37){pan("W");eGGPRefreshMap();} //Left Arrow
	        else if (e.keyCode==36){zoomOriginalView();eGGPRefreshMap();} //Home
	        else if (e.keyCode==107){zoomIn();eGGPRefreshMap();} //'+' Key
	        else if (e.keyCode==109){zoomOut();eGGPRefreshMap();} //'-' Key
        }
        
        function handleBrowserResize(){
            browserHasResized=true; //Set flag so to inidicate that browser has just been resized.
            
            //I.E. triggers its "onresize" event at every portion of the window resize movement! The timeouts
            //implemented here prevent the map being refreshed unecessarily on each of these occasions. The map
            //will be refreshed 600ms after the last "onresize" event triggered.
            clearTimeout(this.BrowserTimeOut);
            this.BrowserTimeOut=setTimeout(eGGPRefreshMap, 600);
        }
        
        function paintTrackPoints(){
            //iterates through all registered track points and positions them for display (if in viewport).
            var eGGPMap =document.getElementById(mapImg.id);
		    
		    for(var i=0; i<mapTrackPoints.length; i++){
		        var trackPointId    =mapTrackPoints[i].id;
		        var trackPointObj   =document.getElementById(trackPointId);
		        var trackPointX     =mapTrackPoints[i].easting;
		        var trackPointY     =mapTrackPoints[i].northing;
		        var trackPointImgW  =point.width;
                var trackPointImgH  =point.height;
		        
		        var pecentageDimensions =pcentViewPortLT(trackPointX, trackPointY);
		        if(pecentageDimensions[0]!=-1){ 
		            //track point resides in current viewport and can be displayed:
		            trackPointObj.style.visibility  ="visible";
		            //position the track point:
		            trackPointObj.style.left =parseInt((mapImg.w*pecentageDimensions[0])-(trackPointImgW/2)) + "px";
                    trackPointObj.style.top  =parseInt((mapImg.h*pecentageDimensions[1])-(trackPointImgH/2)) + "px";
		        }
		        else{
		            //track point outside current in viewport and should be hidden:
		            trackPointObj.style.visibility  ="hidden";
		        }
		    }            
            function pcentViewPortLT(fixedCPX, fixedCPY){
			    //Gets fixedCPX and fixedCPY as a percentage of the current eGGP viewport.
			    var inWidthView 	=((fixedCPX > parseInt(ms_VPMinX)) && (fixedCPX < parseInt(ms_VPMaxX)));
			    var inHeightView 	=((fixedCPY > parseInt(ms_VPMinY)) && (fixedCPY < parseInt(ms_VPMaxY)));
    			
			    var pLeft	=-1;
			    var pTop	=-1;
			    if(inWidthView && inHeightView){
				    //centre circle point co-ordinates have been determined to lay inside the current viewport. Now we calculate the Left, Top percentages relative to the map image:
				    var VPWidth 		=parseInt(ms_VPMaxX)-parseInt(ms_VPMinX);
				    var FixedCPLeft	    =fixedCPX-parseInt(ms_VPMinX);
    				
				    var VPHeight		=parseInt(ms_VPMaxY)-parseInt(ms_VPMinY);
				    var FixedCPTop	    =fixedCPY-parseInt(ms_VPMinY);
    				
				    pLeft	=(FixedCPLeft/VPWidth);
				    pTop	=1-(FixedCPTop/VPHeight);
			    }
			    return [pLeft,pTop];
		    }
        }
        
        function applyTrackPointDragAlongs(){
            //Register all TrackPoints as a 'drag along' to the eGGP Map drag item:
            for(var i=0; i<mapTrackPoints.length; i++){
                objDragManager.registerDragAlong(mapImg.id, mapTrackPoints[i].id);
            }
        }
        
        function registerTrackPoint(eastingX, northingY, caption, glyphURL){
            var eGGPMap =document.getElementById(mapImg.id);
            //EGGP159 - Implement JS application control.
            var currentTime = new Date(); //This is to get the milliseconds of the current time as a unique ID for the trackPoint.

            point = new Image();
            point.id ="eGGPTP_" + currentTime.getTime();
            point.style.position    ="absolute";
            point.style.visibility  ="hidden";
            point.style.zIndex      =eGGPMap.style.zIndex+1; //Place it just above the map image.
            point.src =(glyphURL)? glyphURL:WebEngineRoot_URLPrefix + RBDefaultTrackPoint;
            if(caption && caption!=""){
			    point.title =caption;
			    point.style.cursor ='help';
			}
            
            //Add track point IMG to Browser DOM:
            eGGPMap.parentNode.appendChild(point);
            //Register it internally:
            mapTrackPoints[mapTrackPoints.length]={id:point.id, easting:eastingX, northing:northingY};
            //Register the track point as a 'drag along' to the eGGP Map drag item:
            objDragManager.registerDragAlong(mapImg.id, point.id);
			return point.id;
        }
        
        function unregisterTrackPoint(trackPointId){
            // Here we REMOVE trackPointId from our mapTrackPoints list.
            var excluded = new Array();
            for (var i=0; i< mapTrackPoints.length;i++){
                if (mapTrackPoints[i].id != trackPointId){
                    excluded[excluded.length]=mapTrackPoints[i];
                }
                else{
                    //target trackPointId found. gracefully remove:
                    objDragManager.unregisterDragAlong(mapImg.id, trackPointId);
                    var eGGPMap =document.getElementById(mapImg.id);
                    var point =document.getElementById(trackPointId);
                    eGGPMap.parentNode.removeChild(point);
                }
            }
            mapTrackPoints =excluded;
        }
        
        
        function regMousePointerEventH(elementId){
            objBrowserEvents.appendEventH(document.getElementById(elementId), "onmousedown", eGGPProcessPointer);
            objBrowserEvents.appendEventH(document.getElementById(elementId), "onmousemove", eGGPProcessPointer);
            objBrowserEvents.appendEventH(document.getElementById(elementId), "onmouseup", eGGPProcessPointer);
            objBrowserEvents.appendEventH(document.getElementById(elementId), "onmouseout", eGGPProcessPointer);
            
            //set the oncontextmenu to prevent right-clicking on the map image element.
            objBrowserEvents.appendEventH(document.getElementById(elementId), "oncontextmenu", function(){return false;});

        }
               
       
        function eGGPRefreshMap(){
            if (ms_Script!=''){// ensures that a refresh is not attempted when no script is loaded. This prevents bad/empty calls to mapImageStream.aspx
                var imgID =mapImg.id;
                var img =document.getElementById(imgID);
                
                //Affirm img dimentions;
                img.style.width="100%";
		        img.style.height="100%";
		        
		        //remove temporary splash image:
		        var splash=document.getElementById("eGGPSplashTemp");
		        if(splash){
		            splash.parentNode.removeChild(splash);
		        }
                            
                /*Construct cache defeat URL for next map image fetch*/
                var cacheDefeatKey ='cacheDefeat';
                var keyValue =new Date().toUTCString(); //remove spaces!
                var nextURL =WebEngineRoot_URLPrefix + RBImageStreamPath + "?" + cacheDefeatKey + '=' + objUtility.URLEncode(keyValue);
                
                //When the browser windows has just resized we need to indicate this to server-side code.
                //This done to prevent this particular fetch from being considered/stored as an ancestor 
                //of the previousView() history.
                nextURL += '&browserHasResized=' + ((browserHasResized==true)?'true':'false');
                browserHasResized=false; //prepare for next possible use.
                
                //Re-fetch map image pixel width & height values. This is required whenever a refresh is
                //browser invoked after a browser window resize.
                captureMapImgWH();
                
                //Establish the temporary image (applies when dragging):
                var tempImgID ="eGGP_GUID_9517534862"; // some hard-coded random unique identifier, only DOM-level uniqueness matters.
                var tempImg=document.getElementById(tempImgID);
                if(mapImg.isDragable){
                    if(!tempImg){
                        //create clone of map image:
                        var newNode =img.cloneNode(false); // 'false' : shallow clone may prevents image 'flash/blink'???
                        newNode.id =tempImgID;
                        img.parentNode.appendChild(newNode);
                        objBrowserEvents.appendEventH(document.getElementById(newNode.id), "oncontextmenu", function(){return false;});
                    }
                    else{
                        //clone already exists, re-use it:
                        tempImg.src              =img.src;
                        tempImg.style.top        =img.style.top;
                        tempImg.style.left       =img.style.left;
                        tempImg.style.width      =img.style.width;
                        tempImg.style.height     =img.style.height;
                       
                        tempImg.style.display ='';
                        
                        if(!document.all){//Moz
                            //Hide image at this point for Moz/FF.(smoother image refresh!)
                            img.style.display ='none';
                        }
                    }
                }
                
                
                //Append current map <IMG../> element width+height:
                nextURL +="&w="+mapImg.w.toString() + "&h="+mapImg.h.toString();
                

                //setup handler for when then new map image has finished loading.
                img.onload =onNextMapImageLoadComplete;
                

                //using proxy if given:
                var finalURL =nextURL;
                if(proxyLocation!=""){
                    while(nextURL.indexOf("?")>-1){nextURL =nextURL.replace("?","%3f");}
                    while(nextURL.indexOf("&")>-1){nextURL =nextURL.replace("&","%26");}
                    finalURL =proxyLocation + (proxyLocation.indexOf("?")>-1?"&":"?") + "rURL=" + nextURL;
                }
                
                //Now we can set the IMG SRC to cause browser to fetch next map image.
                img.src =finalURL;
            }
            
            function onNextMapImageLoadComplete(){
                //call the server to refresh the mapstate. MapState values may change on
                //server due to subtle! width and height changes cased by the new image redraw. 
                //A refresh from the server is required to maintain positional accuracy!
                //Ideally this would not be required see scarab issue 'EGGP63'.
                //refreshMapstate(); //Removed this extra refresh to test dll fix. Feizel Daud Kidia.
                //The above line was re-enabled because of issues with zoom initial scale and zoom-original scale issues.
                
                //re-establish references to main and temp images.
                var img =document.getElementById(imgID);
                var tempImg =document.getElementById(tempImgID);
               
                if(mapImg.isDragable){
                    if(document.all){//IE only:
                        //Hide image at this point for IE.(smoother image refresh!)
                        img.style.display ='none';
                    }
     
                    //Reposition the main/original map image by setting
                    //the original (pre-drag) top and left of the map image.
                    img.style.top  ="0px";
                    img.style.left ="0px";
                    
                    //Show the main/original map image
                    img.style.display ='';
                    
                    //Hide the temporary image
                    tempImg.style.display ='none';
                    
                    if(document.all){//IE only:
                        //IE requires this for smooth image transition. Must be ommited from
                        //FF or it will cause image refesh to flicker!
                        tempImg.src=WebEngineRoot_URLPrefix + RBSwapRedundant;
                    }
                }
                
                //refresh any necessary/registered content panels:
                //refreshContentPanels('mapviewport');
                
                //repaint any registered Track Points:
                paintTrackPoints();
                
                //run any custom client post handler:
                if(postRefreshMapHandler!=null){postRefreshMapHandler();}
            }
        }
        
        function captureMapImgWH(){
            //Store the current width and height dimensions used by map mousing routines
            //and making sure the zoom box does not spill outside the map image.
            mapImg.w =document.getElementById(mapImg.id).offsetWidth;
            mapImg.h =document.getElementById(mapImg.id).offsetHeight;
        }
        
        
        function getState(){
            var MapState="";
            MapState +="WebEngine version: " + this.webEngineVersion() +"\n\n";
            MapState +="webRoot: " + this.webRoot() +"\n";
            MapState +="department: " + this.department() +"\n";
            MapState +="script: " + this.script()     +"\n";
            MapState +="scale: " + this.scale()      +"\n";
            MapState +="minX: " + this.minX()       +"\n";
            MapState +="maxX: " + this.maxX()       +"\n";
            MapState +="minY: " + this.minY()       +"\n";
            MapState +="maxY: " + this.maxY()       +"\n";
            MapState +="centreX: " + this.centreX()    +"\n";
            MapState +="centreY: " + this.centreY()    +"\n";
            MapState +="imageFormat: " + this.imageFormat()    +"\n";
            MapState +="licenceText: " + this.licenceText() +"\n";
            MapState +="mode: " + this.mode() +"\n";
            
            MapState +="\nlayers:\n========\n";
            for(var i=0; i<this.layers().length;i++){
                MapState +="L:- " + this.layers()[i].name +" (active:"+this.layers()[i].active+", visible:"+this.layers()[i].visible+")\n";
                for(var j=0; j<this.layers()[i].queries.length;j++){
                    MapState +="  Q:- " +  this.layers()[i].queries[j].name +" (active:"+this.layers()[i].queries[j].active+")\n";
                }
            }
            
            MapState +="\nWebRoots:\n========\n";
            for(var i=0; i<this.webRoots().length;i++){
                MapState +="-" +  this.webRoots()[i].name +"\n";
            }
            MapState +="\nScripts:\n========\n";
            for(var i=0; i<this.scripts().length;i++){
                MapState +="-" + this.scripts()[i].name +"\n";
            }
            MapState +="\nDepartments:\n========\n";
            for(var i=0; i<this.departments().length;i++){
                MapState +="-" + this.departments()[i].name +"\n";
            }
            return MapState;
        }
}


//DOM Global Code:
//================

/*<global_properties>*/ 
       
    var gbl_eGGPWebEngineRoot =null;
    var gbl_BrowserURL=window.location.toString();    


/*<global_methods>*/ 
    
    function gbl_ExtractRootFromSCRIPTTag(){
        var result =null;
        var idStr ="/useragent/api.aspx";
        
        var scripts =document.getElementsByTagName("script");
        for(var i=0; i<scripts.length; i++){
            var idxof =scripts[i].src.toLowerCase().indexOf(idStr);
            if(idxof > -1){
                result =scripts[i].src.substring(0, idxof);
            }
        }
        if(result==null){alert('Error: Unable to locate WebEngine!');}
        return result;
    }

    //Implements the CSS-based fade in/out functionality:
    function gbl_eGGPFade(id, opacStart, opacEnd, millisec) {
        // see http://www.brainerror.net/scripts_js_blendtrans.php
        
        // speed for each frame
        var speed = Math.round(millisec / 100);
        var timer = 0;

        //determine the direction for the blending, if start and end are the same nothing happens
        if(opacStart > opacEnd) {
            for(i = opacStart; i >= opacEnd; i--) {
                setTimeout("gbl_eGGPAlterOpacity(" + i + ",'" + id + "')",(timer * speed));
                timer++;
            }
        } else if(opacStart < opacEnd) {
            for(i = opacStart; i <= opacEnd; i++)
                {
                setTimeout("gbl_eGGPAlterOpacity(" + i + ",'" + id + "')",(timer * speed));
                timer++;
            }
        }
       return(timer * speed);
    }
    //Alter the opacity for the various browsers types:
    function gbl_eGGPAlterOpacity(opacity, id) {
        var style = document.getElementById(id).style;
        style.opacity = (opacity / 100);
        style.MozOpacity = (opacity / 100);
        style.KhtmlOpacity = (opacity / 100);
        style.filter = "alpha(opacity=" + opacity + ")";
    }
    
    //Create and insert the webengine stylesheet to the HEAD of the document:
    function gbl_InjectCSS(){
        var link =document.createElement('link');
        link.type   ='text/css';
        link.rel    ='stylesheet';
        link.href   =gbl_eGGPWebEngineRoot +'/UserAgent/CSS.aspx';

        var head=document.getElementsByTagName('head')[0];
        head.appendChild(link);
    }

/*<global_autoexec>*/

    gbl_eGGPWebEngineRoot =gbl_ExtractRootFromSCRIPTTag();
    gbl_InjectCSS();
/*  
    The following javascript get's processed as soon as API.aspx gets loaded into
    the browser DOM. i.e. before any potential instantiation of objects.
*/

//]]>
